forked from OpenGamers/abaddon
Merge branch 'master' into keychain
This commit is contained in:
commit
573a619191
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -73,6 +73,7 @@ jobs:
|
||||
cp -r res/css res/res res/fonts build/artifactdir/bin
|
||||
cp /mingw64/share/glib-2.0/schemas/gschemas.compiled build/artifactdir/share/glib-2.0/schemas
|
||||
cat "ci/msys-deps.txt" | sed 's/\r$//' | xargs -I % cp /mingw64% build/artifactdir/bin || :
|
||||
cp /usr/bin/msys-ffi-8.dll build/artifactdir/bin/libffi-8.dll
|
||||
mkdir -p build/artifactdir/share/icons/Adwaita
|
||||
cd build/artifactdir/share/icons/Adwaita
|
||||
mkdir -p 16x16/actions 24x24/actions 32x32/actions 48x48/actions 64x64/actions 96x96/actions scalable/actions
|
||||
@ -117,6 +118,7 @@ jobs:
|
||||
run: |
|
||||
brew install gtkmm3
|
||||
brew install nlohmann-json
|
||||
brew install jpeg
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@v3
|
||||
|
@ -34,6 +34,12 @@ if (WIN32)
|
||||
add_compile_definitions(NOMINMAX)
|
||||
endif ()
|
||||
|
||||
include(TestBigEndian)
|
||||
test_big_endian(IS_BIG_ENDIAN)
|
||||
if (IS_BIG_ENDIAN)
|
||||
add_compile_definitions(ABADDON_IS_BIG_ENDIAN)
|
||||
endif ()
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/src/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||
|
||||
file(GLOB_RECURSE ABADDON_SOURCES
|
||||
|
231
README.md
231
README.md
@ -25,13 +25,15 @@ Current features:
|
||||
* Thread support<sup>3</sup>
|
||||
* Animated avatars, server icons, emojis (can be turned off)
|
||||
|
||||
1 - Abaddon tries its best to make Discord think it's a legitimate web client. Some of the things done to do this
|
||||
1 - Abaddon tries its best (though is not perfect) to make Discord think it's a legitimate web client. Some of the
|
||||
things done to do this
|
||||
include: using a browser user agent, sending the same IDENTIFY message that the official web client does, using API v9
|
||||
endpoints in all cases, and not using endpoints the web client does not normally use. There are still a few smaller
|
||||
inconsistencies, however. For example the web client sends lots of telemetry via the `/science` endpoint (uBlock origin
|
||||
stops this) as well as in the headers of all requests. **In any case,** you should use an official client for joining
|
||||
servers, sending new DMs, or managing your friends list if you are afraid of being caught in Discord's spam filters
|
||||
(unlikely).
|
||||
stops this) as well as in the headers of all requests.<br>
|
||||
|
||||
**See [here](#the-spam-filter)** for things you might want to avoid if you are worried about being caught in the spam
|
||||
filter.
|
||||
|
||||
2 - Unicode emojis are substituted manually as opposed to rendered by GTK on non-Windows platforms. This can be changed
|
||||
with the `stock_emojis` setting as shown at the bottom of this README. A CBDT-based font using Twemoji is provided to
|
||||
@ -52,6 +54,7 @@ the result of fundamental issues with Discord's thread implementation.
|
||||
* mingw-w64-x86_64-curl
|
||||
* mingw-w64-x86_64-zlib
|
||||
* mingw-w64-x86_64-gtkmm3
|
||||
* mingw-w64-x86_64-libhandy
|
||||
2. `git clone --recurse-submodules="subprojects" https://github.com/uowuo/abaddon && cd abaddon`
|
||||
3. `mkdir build && cd build`
|
||||
4. `cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo ..`
|
||||
@ -60,15 +63,18 @@ the result of fundamental issues with Discord's thread implementation.
|
||||
#### Mac:
|
||||
|
||||
1. `git clone https://github.com/uowuo/abaddon --recurse-submodules="subprojects" && cd abaddon`
|
||||
2. `brew install gtkmm3 nlohmann-json`
|
||||
2. `brew install gtkmm3 nlohmann-json libhandy`
|
||||
3. `mkdir build && cd build`
|
||||
4. `cmake ..`
|
||||
5. `make`
|
||||
|
||||
#### Linux:
|
||||
|
||||
1. Install dependencies: `libgtkmm-3.0-dev`, `libcurl4-gnutls-dev`,
|
||||
and [nlohmann-json](https://github.com/nlohmann/json)
|
||||
1. Install dependencies
|
||||
* On Ubuntu 20.04 (Focal) and newer:
|
||||
```Shell
|
||||
$ sudo apt install g++ cmake libgtkmm-3.0-dev libcurl4-gnutls-dev libsqlite3-dev libssl-dev nlohmann-json3-dev
|
||||
```
|
||||
2. `git clone https://github.com/uowuo/abaddon --recurse-submodules="subprojects" && cd abaddon`
|
||||
3. `mkdir build && cd build`
|
||||
4. `cmake ..`
|
||||
@ -86,13 +92,28 @@ Latest release version: https://github.com/uowuo/abaddon/releases/latest
|
||||
- Linux: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-linux-MinSizeRel.zip) unpackaged (for now),
|
||||
requires gtkmm3. built on Ubuntu 18.04 + gcc9
|
||||
|
||||
⚠️ If you use Windows, make sure to start from the `bin` directory
|
||||
> **Warning**: If you use Windows, make sure to start from the `bin` directory
|
||||
|
||||
On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/usr/share/abaddon`
|
||||
|
||||
`abaddon.ini` will also be automatically used if located at `~/.config/abaddon/abaddon.ini` and there is
|
||||
no `abaddon.ini` in the working directory
|
||||
|
||||
#### The Spam Filter
|
||||
|
||||
Discord likes disabling accounts/forcing them to reset their passwords if they think the user is a spam bot or
|
||||
potentially had their account compromised. While the official client still often gets users caught in the spam filter,
|
||||
third party clients tend to upset the spam filter more often. If you get caught by it, you can
|
||||
usually [appeal](https://support.discord.com/hc/en-us/requests/new?ticket_form_id=360000029731) it and get it restored.
|
||||
Here are some things you might want to do with the official client instead if you are particularly afraid of evoking the
|
||||
spam filter's wrath:
|
||||
|
||||
* Joining or leaving servers (usually main cause of getting caught)
|
||||
* Frequently disconnecting and reconnecting
|
||||
* Starting new DMs with people
|
||||
* Managing your friends list
|
||||
* Managing your user profile while connected to a third party client
|
||||
|
||||
#### Dependencies:
|
||||
|
||||
* [gtkmm](https://www.gtkmm.org/en/)
|
||||
@ -101,6 +122,7 @@ no `abaddon.ini` in the working directory
|
||||
* [libcurl](https://curl.se/)
|
||||
* [zlib](https://zlib.net/)
|
||||
* [SQLite3](https://www.sqlite.org/index.html)
|
||||
* [libhandy](https://gnome.pages.gitlab.gnome.org/libhandy/) (optional)
|
||||
|
||||
### TODO:
|
||||
|
||||
@ -113,90 +135,90 @@ no `abaddon.ini` in the working directory
|
||||
|
||||
#### CSS selectors
|
||||
|
||||
.app-window - Applied to all windows. This means the main window and all popups
|
||||
.app-popup - Additional class for `.app-window`s when the window is not the main window
|
||||
**`.app-window`** - Applied to all windows. This means the main window and all popups
|
||||
**`.app-popup`** - Additional class for `.app-window`s when the window is not the main window
|
||||
|
||||
.channel-list - Container of the channel list
|
||||
**`.channel-list`** - Container of the channel list
|
||||
|
||||
.messages - Container of user messages
|
||||
.message-container - The container which holds a user's messages
|
||||
.message-container-author - The author label for a message container
|
||||
.message-container-timestamp - The timestamp label for a message container
|
||||
.message-container-avatar - Avatar for a user in a message
|
||||
.message-container-extra - Label containing BOT/Webhook
|
||||
.message-text - The text of a user message
|
||||
.pending - Extra class of .message-text for messages pending to be sent
|
||||
.failed - Extra class of .message-text for messages that failed to be sent
|
||||
.message-attachment-box - Contains attachment info
|
||||
.message-reply - Container for the replied-to message in a reply (these elements will also have .message-text set)
|
||||
.message-input - Applied to the chat input container
|
||||
.replying - Extra class for chat input container when a reply is currently being created
|
||||
.reaction-box - Contains a reaction image and the count
|
||||
.reacted - Additional class for reaction-box when the user has reacted with a particular reaction
|
||||
.reaction-count - Contains the count for reaction
|
||||
**`.messages`** - Container of user messages
|
||||
**`.message-container`** - The container which holds a user's messages
|
||||
**`.message-container-author`** - The author label for a message container
|
||||
**`.message-container-timestamp`** - The timestamp label for a message container
|
||||
**`.message-container-avatar`** - Avatar for a user in a message
|
||||
**`.message-container-extra`** - Label containing BOT/Webhook
|
||||
**`.message-text`** - The text of a user message
|
||||
**`.pending`** - Extra class of .message-text for messages pending to be sent
|
||||
**`.failed`** - Extra class of .message-text for messages that failed to be sent
|
||||
**`.message-attachment-box`** - Contains attachment info
|
||||
**`.message-reply`** - Container for the replied-to message in a reply (these elements will also have .message-text set)
|
||||
**`.message-input`** - Applied to the chat input container
|
||||
**`.replying`** - Extra class for chat input container when a reply is currently being created
|
||||
**`.reaction-box`** - Contains a reaction image and the count
|
||||
**`.reacted`** - Additional class for reaction-box when the user has reacted with a particular reaction
|
||||
**`.reaction-count`** - Contains the count for reaction
|
||||
|
||||
.completer - Container for the message completer
|
||||
.completer-entry - Container for a single entry in the completer
|
||||
.completer-entry-label - Contains the label for an entry in the completer
|
||||
.completer-entry-image - Contains the image for an entry in the completer
|
||||
**`.completer`** - Container for the message completer
|
||||
**`.completer-entry`** - Container for a single entry in the completer
|
||||
**`.completer-entry-label`** - Contains the label for an entry in the completer
|
||||
**`.completer-entry-image`** - Contains the image for an entry in the completer
|
||||
|
||||
.embed - Container for a message embed
|
||||
.embed-author - The author of an embed
|
||||
.embed-title - The title of an embed
|
||||
.embed-description - The description of an embed
|
||||
.embed-field-title - The title of an embed field
|
||||
.embed-field-value - The value of an embed field
|
||||
.embed-footer - The footer of an embed
|
||||
**`.embed`** - Container for a message embed
|
||||
**`.embed-author`** - The author of an embed
|
||||
**`.embed-title`** - The title of an embed
|
||||
**`.embed-description`** - The description of an embed
|
||||
**`.embed-field-title`** - The title of an embed field
|
||||
**`.embed-field-value`** - The value of an embed field
|
||||
**`.embed-footer`** - The footer of an embed
|
||||
|
||||
.members - Container of the member list
|
||||
.members-row - All rows within the members container
|
||||
.members-row-label - All labels in the members container
|
||||
.members-row-member - Rows containing a member
|
||||
.members-row-role - Rows containing a role
|
||||
.members-row-avatar - Contains the avatar for a row in the member list
|
||||
**`.members`** - Container of the member list
|
||||
**`.members-row`** - All rows within the members container
|
||||
**`.members-row-label`** - All labels in the members container
|
||||
**`.members-row-member`** - Rows containing a member
|
||||
**`.members-row-role`** - Rows containing a role
|
||||
**`.members-row-avatar`** - Contains the avatar for a row in the member list
|
||||
|
||||
.status-indicator - The status indicator
|
||||
.online - Applied to status indicators when the associated user is online
|
||||
.idle - Applied to status indicators when the associated user is away
|
||||
.dnd - Applied to status indicators when the associated user is on do not disturb
|
||||
.offline - Applied to status indicators when the associated user is offline
|
||||
**`.status-indicator`** - The status indicator
|
||||
**`.online`** - Applied to status indicators when the associated user is online
|
||||
**`.idle`** - Applied to status indicators when the associated user is away
|
||||
**`.dnd`** - Applied to status indicators when the associated user is on do not disturb
|
||||
**`.offline`** - Applied to status indicators when the associated user is offline
|
||||
|
||||
.typing-indicator - The typing indicator (also used for replies)
|
||||
**`.typing-indicator`** - The typing indicator (also used for replies)
|
||||
|
||||
Used in reorderable list implementation:
|
||||
.drag-icon .drag-hover-top .drag-hover-bottom
|
||||
**`.drag-icon`** **`.drag-hover-top`** **`.drag-hover-bottom`**
|
||||
|
||||
Used in guild settings popup:
|
||||
.guild-settings-window
|
||||
.guild-members-pane-list - Container for list of members in the members pane
|
||||
.guild-members-pane-info - Container for member info
|
||||
.guild-roles-pane-list - Container for list of roles in the roles pane
|
||||
**`.guild-settings-window`**
|
||||
**`.guild-members-pane-list`** - Container for list of members in the members pane
|
||||
**`.guild-members-pane-info`** - Container for member info
|
||||
**`.guild-roles-pane-list`** - Container for list of roles in the roles pane
|
||||
|
||||
Used in profile popup:
|
||||
.mutual-friend-item - Applied to every item in the mutual friends list
|
||||
.mutual-friend-item-name - Name in mutual friend item
|
||||
.mutual-friend-item-avatar - Avatar in mutual friend item
|
||||
.mutual-guild-item - Applied to every item in the mutual guilds list
|
||||
.mutual-guild-item-name - Name in mutual guild item
|
||||
.mutual-guild-item-icon - Icon in mutual guild item
|
||||
.mutual-guild-item-nick - User nickname in mutual guild item
|
||||
.profile-connection - Applied to every item in the user connections list
|
||||
.profile-connection-label - Label in profile connection item
|
||||
.profile-connection-check - Checkmark in verified profile connection items
|
||||
.profile-connections - Container for profile connections
|
||||
.profile-notes - Container for notes in profile window
|
||||
.profile-notes-label - Label that says "NOTE"
|
||||
.profile-notes-text - Actual note text
|
||||
.profile-info-pane - Applied to container for info section of profile popup
|
||||
.profile-info-created - Label for creation date of profile
|
||||
.user-profile-window
|
||||
.profile-main-container - Inner container for profile
|
||||
.profile-avatar
|
||||
.profile-username
|
||||
.profile-switcher - Buttons used to switch viewed section of profile
|
||||
.profile-stack - Container for profile info that can be switched between
|
||||
.profile-badges - Container for badges
|
||||
.profile-badge
|
||||
**`.mutual-friend-item`** - Applied to every item in the mutual friends list
|
||||
**`.mutual-friend-item-name`** - Name in mutual friend item
|
||||
**`.mutual-friend-item-avatar`** - Avatar in mutual friend item
|
||||
**`.mutual-guild-item`** - Applied to every item in the mutual guilds list
|
||||
**`.mutual-guild-item-name`** - Name in mutual guild item
|
||||
**`.mutual-guild-item-icon`** - Icon in mutual guild item
|
||||
**`.mutual-guild-item-nick`** - User nickname in mutual guild item
|
||||
**`.profile-connection`** - Applied to every item in the user connections list
|
||||
**`.profile-connection-label`** - Label in profile connection item
|
||||
**`.profile-connection-check`** - Checkmark in verified profile connection items
|
||||
**`.profile-connections`** - Container for profile connections
|
||||
**`.profile-notes`** - Container for notes in profile window
|
||||
**`.profile-notes-label`** - Label that says "NOTE"
|
||||
**`.profile-notes-text`** - Actual note text
|
||||
**`.profile-info-pane`** - Applied to container for info section of profile popup
|
||||
**`.profile-info-created`** - Label for creation date of profile
|
||||
**`.user-profile-window`**
|
||||
**`.profile-main-container`** - Inner container for profile
|
||||
**`.profile-avatar`**
|
||||
**`.profile-username`**
|
||||
**`.profile-switcher`** - Buttons used to switch viewed section of profile
|
||||
**`.profile-stack`** - Container for profile info that can be switched between
|
||||
**`.profile-badges`** - Container for badges
|
||||
**`.profile-badge`**
|
||||
|
||||
### Settings
|
||||
|
||||
@ -212,43 +234,46 @@ For example, memory_db would be set by adding `memory_db = true` under the line
|
||||
|
||||
#### discord
|
||||
|
||||
* gateway (string) - override url for Discord gateway. must be json format and use zlib stream compression
|
||||
* api_base (string) - override base url for Discord API
|
||||
* memory_db (true or false, default false) - if true, Discord data will be kept in memory as opposed to on disk
|
||||
* token (string) - Discord token used to login, this can be set from the menu
|
||||
* prefetch (true or false, default false) - if true, new messages will cause the avatar and image attachments to be
|
||||
* **`gateway`** (string) - override url for Discord gateway. must be json format and use zlib stream compression
|
||||
* **`api_base`** (string) - override base url for Discord API
|
||||
* **`memory_db`** (true or false, `default: false`) - if true, Discord data will be kept in memory as opposed to on disk
|
||||
* **`token`** (string) - Discord token used to login, this can be set from the menu
|
||||
* **`prefetch`** (true or false, `default: false`) - if true, new messages will cause the avatar and image attachments to be
|
||||
automatically downloaded
|
||||
|
||||
#### http
|
||||
|
||||
* user_agent (string) - sets the user-agent to use in HTTP requests to the Discord API (not including media/images)
|
||||
* concurrent (int, default 20) - how many images can be concurrently retrieved
|
||||
* **`user_agent`** (string) - sets the user-agent to use in HTTP requests to the Discord API (not including media/images)
|
||||
* **`concurrent`** (int, `default: 20`) - how many images can be concurrently retrieved
|
||||
|
||||
#### gui
|
||||
|
||||
* member_list_discriminator (true or false, default true) - show user discriminators in the member list
|
||||
* stock_emojis (true or false, default true) - allow abaddon to substitute unicode emojis with images from emojis.bin,
|
||||
* **`member_list_discriminator`** (true or false, `default: true`) - show user discriminators in the member list
|
||||
* **`stock_emojis`** (true or false, `default: true`) - allow abaddon to substitute unicode emojis with images from emojis.bin,
|
||||
must be false to allow GTK to render emojis itself
|
||||
* custom_emojis (true or false, default true) - download and use custom Discord emojis
|
||||
* css (string) - path to the main CSS file
|
||||
* animations (true or false, default true) - use animated images where available (e.g. server icons, emojis, avatars).
|
||||
* **`custom_emojis`** (true or false, `default: true`) - download and use custom Discord emojis
|
||||
* **`css`** (string) - path to the main CSS file
|
||||
* **`animations`** (true or false, `default: true`) - use animated images where available (e.g. server icons, emojis, avatars).
|
||||
false means static images will be used
|
||||
* animated_guild_hover_only (true or false, default true) - only animate guild icons when the guild is being hovered
|
||||
* **`animated_guild_hover_only`** (true or false, `default: true`) - only animate guild icons when the guild is being hovered
|
||||
over
|
||||
* owner_crown (true or false, default true) - show a crown next to the owner
|
||||
* unreads (true or false, default true) - show unread indicators and mention badges
|
||||
* **`owner_crown`** (true or false, `default: true`) - show a crown next to the owner
|
||||
* **`unreads`** (true or false, `default: true`) - show unread indicators and mention badges
|
||||
* **`save_state`** (true or false, `default: true`) - save the state of the gui (active channels, tabs, expanded channels)
|
||||
* **`alt_menu`** (true or false, `default: false`) - keep the menu hidden unless revealed with alt key
|
||||
* **`hide_to_tray`** (true or false, `default: false`) - hide abaddon to the system tray on window close
|
||||
|
||||
#### style
|
||||
|
||||
* linkcolor (string) - color to use for links in messages
|
||||
* expandercolor (string) - color to use for the expander in the channel list
|
||||
* nsfwchannelcolor (string) - color to use for NSFW channels in the channel list
|
||||
* channelcolor (string) - color to use for SFW channels in the channel list
|
||||
* mentionbadgecolor (string) - background color for mention badges
|
||||
* mentionbadgetextcolor (string) - color to use for number displayed on mention badges
|
||||
* unreadcolor (string) - color to use for the unread indicator
|
||||
* **`linkcolor`** (string) - color to use for links in messages
|
||||
* **`expandercolor`** (string) - color to use for the expander in the channel list
|
||||
* **`nsfwchannelcolor`** (string) - color to use for NSFW channels in the channel list
|
||||
* **`channelcolor`** (string) - color to use for SFW channels in the channel list
|
||||
* **`mentionbadgecolor`** (string) - background color for mention badges
|
||||
* **`mentionbadgetextcolor`** (string) - color to use for number displayed on mention badges
|
||||
* **`unreadcolor`** (string) - color to use for the unread indicator
|
||||
|
||||
### Environment variables
|
||||
|
||||
* ABADDON_NO_FC (Windows only) - don't use custom font config
|
||||
* ABADDON_CONFIG - change path of configuration file to use. relative to cwd or can be absolute
|
||||
* **`ABADDON_NO_FC`** (Windows only) - don't use custom font config
|
||||
* **`ABADDON_CONFIG`** - change path of configuration file to use. relative to cwd or can be absolute
|
||||
|
@ -14,7 +14,7 @@
|
||||
/bin/libdeflate.dll
|
||||
/bin/libepoxy-0.dll
|
||||
/bin/libexpat-1.dll
|
||||
/bin/libffi-7.dll
|
||||
/bin/libffi-8.dll
|
||||
/bin/libfontconfig-1.dll
|
||||
/bin/libfreetype-6.dll
|
||||
/bin/libfribidi-0.dll
|
||||
@ -42,7 +42,7 @@
|
||||
/bin/libpangoft2-1.0-0.dll
|
||||
/bin/libpangomm-1.4-1.dll
|
||||
/bin/libpangowin32-1.0-0.dll
|
||||
/bin/libpcre-1.dll
|
||||
/bin/libpcre2-8-0.dll
|
||||
/bin/libpixman-1-0.dll
|
||||
/bin/libpng16-16.dll
|
||||
/bin/libpsl-5.dll
|
||||
@ -56,3 +56,4 @@
|
||||
/bin/libwinpthread-1.dll
|
||||
/bin/libzstd.dll
|
||||
/bin/zlib1.dll
|
||||
/../usr/bin/msys-2.0.dll
|
||||
|
@ -120,6 +120,8 @@
|
||||
|
||||
.message-input-browse-icon {
|
||||
color: #b9bbbe;
|
||||
margin-left: 5px;
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
/* i dont think theres a way to circumvent having to do this to adjust around the browse icon */
|
||||
|
132
src/abaddon.cpp
132
src/abaddon.cpp
@ -6,7 +6,6 @@
|
||||
#include "discord/discord.hpp"
|
||||
#include "dialogs/token.hpp"
|
||||
#include "dialogs/editmessage.hpp"
|
||||
#include "dialogs/joinguild.hpp"
|
||||
#include "dialogs/confirm.hpp"
|
||||
#include "dialogs/setstatus.hpp"
|
||||
#include "dialogs/friendpicker.hpp"
|
||||
@ -17,6 +16,7 @@
|
||||
#include "windows/profilewindow.hpp"
|
||||
#include "windows/pinnedwindow.hpp"
|
||||
#include "windows/threadswindow.hpp"
|
||||
#include "startup.hpp"
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
#include <handy.h>
|
||||
@ -68,14 +68,13 @@ Abaddon &Abaddon::Get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
#ifdef _WIN32
|
||||
#ifdef _WIN32
|
||||
constexpr static guint BUTTON_BACK = 4;
|
||||
constexpr static guint BUTTON_FORWARD = 5;
|
||||
#else
|
||||
#else
|
||||
constexpr static guint BUTTON_BACK = 8;
|
||||
constexpr static guint BUTTON_FORWARD = 9;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static bool HandleButtonEvents(GdkEvent *event, MainWindow *main_window) {
|
||||
if (event->type != GDK_BUTTON_PRESS) return false;
|
||||
@ -85,6 +84,7 @@ static bool HandleButtonEvents(GdkEvent *event, MainWindow *main_window) {
|
||||
auto *window = gtk_widget_get_toplevel(widget);
|
||||
if (static_cast<void *>(window) != static_cast<void *>(main_window->gobj())) return false; // is this the right way???
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
switch (event->button.button) {
|
||||
case BUTTON_BACK:
|
||||
main_window->GoBack();
|
||||
@ -93,6 +93,7 @@ static bool HandleButtonEvents(GdkEvent *event, MainWindow *main_window) {
|
||||
main_window->GoForward();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -108,6 +109,15 @@ static bool HandleKeyEvents(GdkEvent *event, MainWindow *main_window) {
|
||||
const bool ctrl = (event->key.state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK;
|
||||
const bool shft = (event->key.state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK;
|
||||
|
||||
constexpr static guint EXCLUDE_STATES = GDK_CONTROL_MASK | GDK_SHIFT_MASK;
|
||||
|
||||
if (!(event->key.state & EXCLUDE_STATES) && event->key.keyval == GDK_KEY_Alt_L) {
|
||||
if (Abaddon::Get().GetSettings().AltMenu) {
|
||||
main_window->ToggleMenuVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
if (ctrl) {
|
||||
switch (event->key.keyval) {
|
||||
case GDK_KEY_Tab:
|
||||
@ -134,6 +144,7 @@ static bool HandleKeyEvents(GdkEvent *event, MainWindow *main_window) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -143,7 +154,6 @@ static void MainEventHandler(GdkEvent *event, void *main_window) {
|
||||
if (HandleKeyEvents(event, static_cast<MainWindow *>(main_window))) return;
|
||||
gtk_main_do_event(event);
|
||||
}
|
||||
#endif
|
||||
|
||||
int Abaddon::StartGTK() {
|
||||
m_gtk_app = Gtk::Application::create("com.github.uowuo.abaddon");
|
||||
@ -228,7 +238,6 @@ int Abaddon::StartGTK() {
|
||||
m_main_window->signal_action_disconnect().connect(sigc::mem_fun(*this, &Abaddon::ActionDisconnect));
|
||||
m_main_window->signal_action_set_token().connect(sigc::mem_fun(*this, &Abaddon::ActionSetToken));
|
||||
m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS));
|
||||
m_main_window->signal_action_join_guild().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinGuildDialog));
|
||||
m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus));
|
||||
m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient));
|
||||
m_main_window->signal_action_view_pins().connect(sigc::mem_fun(*this, &Abaddon::ActionViewPins));
|
||||
@ -247,12 +256,29 @@ int Abaddon::StartGTK() {
|
||||
m_main_window->GetChatWindow()->signal_action_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionRemove));
|
||||
|
||||
ActionReloadCSS();
|
||||
if (m_settings.GetSettings().HideToTray) {
|
||||
m_tray = Gtk::StatusIcon::create("discord");
|
||||
m_tray->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_tray_click));
|
||||
m_tray->signal_popup_menu().connect(sigc::mem_fun(*this, &Abaddon::on_tray_popup_menu));
|
||||
}
|
||||
m_tray_menu = Gtk::make_managed<Gtk::Menu>();
|
||||
m_tray_exit = Gtk::make_managed<Gtk::MenuItem>("Quit", false);
|
||||
|
||||
m_tray_exit->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_tray_menu_click));
|
||||
|
||||
m_tray_menu->append(*m_tray_exit);
|
||||
m_tray_menu->show_all();
|
||||
|
||||
m_main_window->signal_hide().connect(sigc::mem_fun(*this, &Abaddon::on_window_hide));
|
||||
m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::OnShutdown), false);
|
||||
|
||||
m_main_window->UpdateMenus();
|
||||
|
||||
m_gtk_app->hold();
|
||||
m_main_window->show();
|
||||
|
||||
RunFirstTimeDiscordStartup();
|
||||
|
||||
return m_gtk_app->run(*m_main_window);
|
||||
}
|
||||
|
||||
@ -390,6 +416,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
|
||||
for (const auto child : m_user_menu_roles_submenu->get_children())
|
||||
delete child;
|
||||
|
||||
if (guild.has_value() && user.has_value()) {
|
||||
const auto roles = user->GetSortedRoles();
|
||||
m_user_menu_roles->set_visible(!roles.empty());
|
||||
@ -412,7 +439,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
if (me == id) {
|
||||
m_user_menu_ban->set_visible(false);
|
||||
m_user_menu_kick->set_visible(false);
|
||||
m_user_menu_open_dm->set_visible(false);
|
||||
m_user_menu_open_dm->set_sensitive(false);
|
||||
} else {
|
||||
const bool has_kick = m_discord.HasGuildPermission(me, guild_id, Permission::KICK_MEMBERS);
|
||||
const bool has_ban = m_discord.HasGuildPermission(me, guild_id, Permission::BAN_MEMBERS);
|
||||
@ -420,7 +447,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
|
||||
m_user_menu_kick->set_visible(has_kick && can_manage);
|
||||
m_user_menu_ban->set_visible(has_ban && can_manage);
|
||||
m_user_menu_open_dm->set_visible(true);
|
||||
m_user_menu_open_dm->set_sensitive(m_discord.FindDM(id).has_value());
|
||||
}
|
||||
|
||||
m_user_menu_remove_recipient->hide();
|
||||
@ -434,6 +461,48 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
m_user_menu->popup_at_pointer(event);
|
||||
}
|
||||
|
||||
void Abaddon::RunFirstTimeDiscordStartup() {
|
||||
DiscordStartupDialog dlg(*m_main_window);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
|
||||
std::optional<std::string> cookie;
|
||||
std::optional<uint32_t> build_number;
|
||||
|
||||
dlg.signal_response().connect([&](int response) {
|
||||
if (response == Gtk::RESPONSE_OK) {
|
||||
cookie = dlg.GetCookie();
|
||||
build_number = dlg.GetBuildNumber();
|
||||
}
|
||||
});
|
||||
|
||||
dlg.run();
|
||||
|
||||
Glib::signal_idle().connect_once([this, cookie, build_number]() {
|
||||
if (cookie.has_value()) {
|
||||
m_discord.SetCookie(*cookie);
|
||||
} else {
|
||||
ConfirmDialog confirm(*m_main_window);
|
||||
confirm.SetConfirmText("Cookies could not be fetched. This may increase your chances of being flagged by Discord's anti-spam");
|
||||
confirm.SetAcceptOnly(true);
|
||||
confirm.run();
|
||||
}
|
||||
|
||||
if (build_number.has_value()) {
|
||||
m_discord.SetBuildNumber(*build_number);
|
||||
} else {
|
||||
ConfirmDialog confirm(*m_main_window);
|
||||
confirm.SetConfirmText("Build number could not be fetched. This may increase your chances of being flagged by Discord's anti-spam");
|
||||
confirm.SetAcceptOnly(true);
|
||||
confirm.run();
|
||||
}
|
||||
|
||||
// autoconnect
|
||||
if (cookie.has_value() && build_number.has_value() && GetSettings().Autoconnect && !GetDiscordToken().empty()) {
|
||||
ActionConnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Abaddon::ShowGuildVerificationGateDialog(Snowflake guild_id) {
|
||||
VerificationGateDialog dlg(*m_main_window, guild_id);
|
||||
if (dlg.run() == Gtk::RESPONSE_OK) {
|
||||
@ -468,7 +537,7 @@ void Abaddon::SetupUserMenu() {
|
||||
m_user_menu_ban = Gtk::manage(new Gtk::MenuItem("Ban"));
|
||||
m_user_menu_kick = Gtk::manage(new Gtk::MenuItem("Kick"));
|
||||
m_user_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
|
||||
m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Open DM"));
|
||||
m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Go to DM"));
|
||||
m_user_menu_roles = Gtk::manage(new Gtk::MenuItem("Roles"));
|
||||
m_user_menu_info = Gtk::manage(new Gtk::MenuItem("View Profile"));
|
||||
m_user_menu_remove_recipient = Gtk::manage(new Gtk::MenuItem("Remove From Group"));
|
||||
@ -579,18 +648,9 @@ void Abaddon::on_user_menu_copy_id() {
|
||||
|
||||
void Abaddon::on_user_menu_open_dm() {
|
||||
const auto existing = m_discord.FindDM(m_shown_user_menu_id);
|
||||
if (existing.has_value())
|
||||
if (existing.has_value()) {
|
||||
ActionChannelOpened(*existing);
|
||||
else
|
||||
m_discord.CreateDM(m_shown_user_menu_id, [this](DiscordError code, Snowflake channel_id) {
|
||||
if (code == DiscordError::NONE) {
|
||||
// give the gateway a little window to send CHANNEL_CREATE
|
||||
auto cb = [this, channel_id] {
|
||||
ActionChannelOpened(channel_id);
|
||||
};
|
||||
Glib::signal_timeout().connect_once(sigc::track_obj(cb, *this), 200);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Abaddon::on_user_menu_remove_recipient() {
|
||||
@ -646,22 +706,18 @@ void Abaddon::ActionSetToken() {
|
||||
m_main_window->UpdateMenus();
|
||||
}
|
||||
|
||||
void Abaddon::ActionJoinGuildDialog() {
|
||||
JoinGuildDialog dlg(*m_main_window);
|
||||
auto response = dlg.run();
|
||||
if (response == Gtk::RESPONSE_OK) {
|
||||
auto code = dlg.GetCode();
|
||||
m_discord.JoinGuild(code);
|
||||
}
|
||||
}
|
||||
|
||||
void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) {
|
||||
if (!id.IsValid() || id == m_main_window->GetChatActiveChannel()) return;
|
||||
if (!id.IsValid()) {
|
||||
m_discord.SetReferringChannel(Snowflake::Invalid);
|
||||
return;
|
||||
}
|
||||
if (id == m_main_window->GetChatActiveChannel()) return;
|
||||
|
||||
m_main_window->GetChatWindow()->SetTopic("");
|
||||
|
||||
const auto channel = m_discord.GetChannel(id);
|
||||
if (!channel.has_value()) {
|
||||
m_discord.SetReferringChannel(Snowflake::Invalid);
|
||||
m_main_window->UpdateChatActiveChannel(Snowflake::Invalid, false);
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
return;
|
||||
@ -710,6 +766,7 @@ void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) {
|
||||
}
|
||||
|
||||
m_main_window->UpdateMenus();
|
||||
m_discord.SetReferringChannel(id);
|
||||
}
|
||||
|
||||
void Abaddon::ActionChatLoadHistory(Snowflake id) {
|
||||
@ -898,6 +955,21 @@ EmojiResource &Abaddon::GetEmojis() {
|
||||
return m_emojis;
|
||||
}
|
||||
|
||||
void Abaddon::on_tray_click() {
|
||||
m_main_window->set_visible(!m_main_window->is_visible());
|
||||
}
|
||||
void Abaddon::on_tray_menu_click() {
|
||||
m_gtk_app->quit();
|
||||
}
|
||||
void Abaddon::on_tray_popup_menu(int button, int activate_time) {
|
||||
m_tray->popup_menu_at_position(*m_tray_menu, button, activate_time);
|
||||
}
|
||||
void Abaddon::on_window_hide() {
|
||||
if (!m_settings.GetSettings().HideToTray) {
|
||||
m_gtk_app->quit();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (std::getenv("ABADDON_NO_FC") == nullptr)
|
||||
Platform::SetupFonts();
|
||||
|
@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@ -93,6 +94,8 @@ public:
|
||||
static std::string GetStateCachePath(const std::string &path);
|
||||
|
||||
protected:
|
||||
void RunFirstTimeDiscordStartup();
|
||||
|
||||
void ShowGuildVerificationGateDialog(Snowflake guild_id);
|
||||
|
||||
void CheckMessagesForMembers(const ChannelData &chan, const std::vector<Message> &msgs);
|
||||
@ -114,6 +117,8 @@ protected:
|
||||
Gtk::MenuItem *m_user_menu_roles;
|
||||
Gtk::MenuItem *m_user_menu_remove_recipient;
|
||||
Gtk::Menu *m_user_menu_roles_submenu;
|
||||
Gtk::Menu *m_tray_menu;
|
||||
Gtk::MenuItem *m_tray_exit;
|
||||
|
||||
void on_user_menu_insert_mention();
|
||||
void on_user_menu_ban();
|
||||
@ -121,6 +126,10 @@ protected:
|
||||
void on_user_menu_copy_id();
|
||||
void on_user_menu_open_dm();
|
||||
void on_user_menu_remove_recipient();
|
||||
void on_tray_click();
|
||||
void on_tray_popup_menu(int button, int activate_time);
|
||||
void on_tray_menu_click();
|
||||
void on_window_hide();
|
||||
|
||||
private:
|
||||
SettingsManager m_settings;
|
||||
@ -139,5 +148,6 @@ private:
|
||||
Glib::RefPtr<Gtk::Application> m_gtk_app;
|
||||
Glib::RefPtr<Gtk::CssProvider> m_css_provider;
|
||||
Glib::RefPtr<Gtk::CssProvider> m_css_low_provider; // registered with a lower priority to allow better customization
|
||||
Glib::RefPtr<Gtk::StatusIcon> m_tray;
|
||||
std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
|
||||
};
|
||||
|
@ -176,11 +176,10 @@ bool ChatInputTextContainer::GetChildPosition(Gtk::Widget *child, Gdk::Rectangle
|
||||
Gtk::Requisition min, req;
|
||||
child->get_preferred_size(min, req);
|
||||
|
||||
// yummy hardcoded values
|
||||
pos.set_x(5);
|
||||
// let css move it around
|
||||
pos.set_x(0);
|
||||
pos.set_y(0);
|
||||
pos.set_width(std::max(min.width, std::min(main_alloc.get_width(), req.width)));
|
||||
|
||||
pos.set_y(12);
|
||||
pos.set_height(std::max(min.height, std::min(main_alloc.get_height(), req.height)));
|
||||
|
||||
return true;
|
||||
|
@ -32,7 +32,6 @@ ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(const Message &d
|
||||
|
||||
if (!data.Content.empty() || data.Type != MessageType::DEFAULT) {
|
||||
container->m_text_component = container->CreateTextComponent(data);
|
||||
container->AttachEventHandlers(*container->m_text_component);
|
||||
container->m_main.add(*container->m_text_component);
|
||||
}
|
||||
|
||||
@ -101,7 +100,6 @@ void ChatMessageItemContainer::UpdateContent() {
|
||||
|
||||
if (!data->Embeds.empty()) {
|
||||
m_embed_component = CreateEmbedsComponent(data->Embeds);
|
||||
AttachEventHandlers(*m_embed_component);
|
||||
m_main.add(*m_embed_component);
|
||||
m_embed_component->show_all();
|
||||
}
|
||||
@ -152,12 +150,12 @@ void ChatMessageItemContainer::UpdateAttributes() {
|
||||
|
||||
void ChatMessageItemContainer::AddClickHandler(Gtk::Widget *widget, const std::string &url) {
|
||||
// clang-format off
|
||||
widget->signal_button_press_event().connect([url](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
widget->signal_button_release_event().connect([url](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) {
|
||||
LaunchBrowser(url);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, false);
|
||||
// clang-format on
|
||||
}
|
||||
@ -174,6 +172,8 @@ Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message &data
|
||||
tv->set_halign(Gtk::ALIGN_FILL);
|
||||
tv->set_hexpand(true);
|
||||
|
||||
tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnTextViewButtonPress), false);
|
||||
|
||||
UpdateTextComponent(tv);
|
||||
|
||||
return tv;
|
||||
@ -281,8 +281,6 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
|
||||
tag->property_weight() = Pango::WEIGHT_BOLD;
|
||||
m_channel_tagmap[tag] = *data->MessageReference->ChannelID;
|
||||
b->insert_with_tag(iter, data->Content, tag);
|
||||
|
||||
tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnClickChannel), false);
|
||||
} else {
|
||||
b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span><b>" + Glib::Markup::escape_text(data->Content) + "</b></i>");
|
||||
}
|
||||
@ -297,12 +295,10 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedsComponent(const std::vector<E
|
||||
if (IsEmbedImageOnly(embed)) {
|
||||
auto *widget = CreateImageComponent(*embed.Thumbnail->ProxyURL, *embed.Thumbnail->URL, *embed.Thumbnail->Width, *embed.Thumbnail->Height);
|
||||
widget->show();
|
||||
AttachEventHandlers(*widget);
|
||||
box->add(*widget);
|
||||
} else {
|
||||
auto *widget = CreateEmbedComponent(embed);
|
||||
widget->show();
|
||||
AttachEventHandlers(*widget);
|
||||
box->add(*widget);
|
||||
}
|
||||
}
|
||||
@ -361,8 +357,8 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb
|
||||
if (embed.URL.has_value()) {
|
||||
AddPointerCursor(*title_ev);
|
||||
auto url = *embed.URL;
|
||||
title_ev->signal_button_press_event().connect([url = std::move(url)](GdkEventButton *event) -> bool {
|
||||
if (event->button == GDK_BUTTON_PRIMARY) {
|
||||
title_ev->signal_button_release_event().connect([url = std::move(url)](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) {
|
||||
LaunchBrowser(url);
|
||||
return true;
|
||||
}
|
||||
@ -493,12 +489,22 @@ Gtk::Widget *ChatMessageItemContainer::CreateImageComponent(const std::string &p
|
||||
Gtk::EventBox *ev = Gtk::manage(new Gtk::EventBox);
|
||||
Gtk::Image *widget = Gtk::manage(new LazyImage(proxy_url, w, h, false));
|
||||
ev->add(*widget);
|
||||
ev->set_halign(Gtk::ALIGN_START);
|
||||
widget->set_halign(Gtk::ALIGN_START);
|
||||
widget->set_size_request(w, h);
|
||||
|
||||
AttachEventHandlers(*ev);
|
||||
AddClickHandler(ev, url);
|
||||
|
||||
const auto on_button_press_event = [this, url](GdkEventButton *e) -> bool {
|
||||
if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) {
|
||||
m_selected_link = url;
|
||||
m_link_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(e));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
ev->signal_button_press_event().connect(on_button_press_event, false);
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
@ -510,9 +516,18 @@ Gtk::Widget *ChatMessageItemContainer::CreateAttachmentComponent(const Attachmen
|
||||
ev->get_style_context()->add_class("message-attachment-box");
|
||||
ev->add(*btn);
|
||||
|
||||
AttachEventHandlers(*ev);
|
||||
AddClickHandler(ev, data.URL);
|
||||
|
||||
const auto on_button_press_event = [this, url = data.URL](GdkEventButton *e) -> bool {
|
||||
if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) {
|
||||
m_selected_link = url;
|
||||
m_link_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(e));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
ev->signal_button_press_event().connect(on_button_press_event, false);
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
@ -534,7 +549,6 @@ Gtk::Widget *ChatMessageItemContainer::CreateStickersComponent(const std::vector
|
||||
|
||||
box->show();
|
||||
|
||||
AttachEventHandlers(*box);
|
||||
return box;
|
||||
}
|
||||
|
||||
@ -641,13 +655,19 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data)
|
||||
const auto role = discord.GetRole(role_id);
|
||||
if (role.has_value()) {
|
||||
const auto author = discord.GetUser(author_id);
|
||||
if (author.has_value()) {
|
||||
return "<b><span color=\"#" + IntToCSSColor(role->Color) + "\">" + author->GetEscapedString() + "</span></b>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto author = discord.GetUser(author_id);
|
||||
if (author.has_value()) {
|
||||
return author->GetEscapedBoldString<false>();
|
||||
}
|
||||
|
||||
return "<b>Unknown User</b>";
|
||||
};
|
||||
|
||||
// if the message wasnt fetched from store it might have an un-fetched reference
|
||||
@ -659,15 +679,15 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data)
|
||||
}
|
||||
|
||||
if (data.Interaction.has_value()) {
|
||||
const auto user = *discord.GetUser(data.Interaction->User.ID);
|
||||
|
||||
if (data.GuildID.has_value()) {
|
||||
lbl->set_markup(get_author_markup(user.ID, *data.GuildID) +
|
||||
lbl->set_markup(get_author_markup(data.Interaction->User.ID, *data.GuildID) +
|
||||
" used <span color='#697ec4'>/" +
|
||||
Glib::Markup::escape_text(data.Interaction->Name) +
|
||||
"</span>");
|
||||
} else if (const auto user = discord.GetUser(data.Interaction->User.ID); user.has_value()) {
|
||||
lbl->set_markup(user->GetEscapedBoldString<false>());
|
||||
} else {
|
||||
lbl->set_markup(user.GetEscapedBoldString<false>());
|
||||
lbl->set_markup("<b>Unknown User</b>");
|
||||
}
|
||||
} else if (referenced_message.has_value()) {
|
||||
if (referenced_message.value() == nullptr) {
|
||||
@ -956,7 +976,6 @@ void ChatMessageItemContainer::HandleChannelMentions(const Glib::RefPtr<Gtk::Tex
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::HandleChannelMentions(Gtk::TextView *tv) {
|
||||
tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnClickChannel), false);
|
||||
HandleChannelMentions(tv->get_buffer());
|
||||
}
|
||||
|
||||
@ -990,6 +1009,20 @@ bool ChatMessageItemContainer::OnClickChannel(GdkEventButton *ev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatMessageItemContainer::OnTextViewButtonPress(GdkEventButton *ev) {
|
||||
// run all button press handlers and propagate if none return true
|
||||
if (OnLinkClick(ev)) return true;
|
||||
if (OnClickChannel(ev)) return true;
|
||||
|
||||
if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_SECONDARY) {
|
||||
// send the event upward skipping TextView's handler because we dont want it
|
||||
gtk_propagate_event(GTK_WIDGET(m_main.gobj()), reinterpret_cast<GdkEvent *>(ev));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::on_link_menu_copy() {
|
||||
Gtk::Clipboard::get()->set_text(m_selected_link);
|
||||
}
|
||||
@ -997,8 +1030,6 @@ void ChatMessageItemContainer::on_link_menu_copy() {
|
||||
void ChatMessageItemContainer::HandleLinks(Gtk::TextView &tv) {
|
||||
const auto rgx = Glib::Regex::create(R"(\bhttps?:\/\/[^\s]+\.[^\s]+\b)");
|
||||
|
||||
tv.signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnLinkClick), false);
|
||||
|
||||
auto buf = tv.get_buffer();
|
||||
Glib::ustring text = GetText(buf);
|
||||
|
||||
@ -1070,18 +1101,6 @@ ChatMessageItemContainer::type_signal_action_reaction_remove ChatMessageItemCont
|
||||
return m_signal_action_reaction_remove;
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::AttachEventHandlers(Gtk::Widget &widget) {
|
||||
const auto on_button_press_event = [this](GdkEventButton *e) -> bool {
|
||||
if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) {
|
||||
event(reinterpret_cast<GdkEvent *>(e)); // illegal ooooooh
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
widget.signal_button_press_event().connect(on_button_press_event, false);
|
||||
}
|
||||
|
||||
ChatMessageHeader::ChatMessageHeader(const Message &data)
|
||||
: m_main_box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_content_box(Gtk::ORIENTATION_VERTICAL)
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include <gtkmm.h>
|
||||
#include "discord/discord.hpp"
|
||||
|
||||
class ChatMessageItemContainer : public Gtk::Box {
|
||||
class ChatMessageItemContainer : public Gtk::EventBox {
|
||||
public:
|
||||
Snowflake ID;
|
||||
Snowflake ChannelID;
|
||||
@ -44,6 +44,7 @@ protected:
|
||||
void HandleChannelMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf);
|
||||
void HandleChannelMentions(Gtk::TextView *tv);
|
||||
bool OnClickChannel(GdkEventButton *ev);
|
||||
bool OnTextViewButtonPress(GdkEventButton *ev);
|
||||
|
||||
// reused for images and links
|
||||
Gtk::Menu m_link_menu;
|
||||
@ -57,8 +58,6 @@ protected:
|
||||
std::map<Glib::RefPtr<Gtk::TextTag>, std::string> m_link_tagmap;
|
||||
std::map<Glib::RefPtr<Gtk::TextTag>, Snowflake> m_channel_tagmap;
|
||||
|
||||
void AttachEventHandlers(Gtk::Widget &widget);
|
||||
|
||||
Gtk::EventBox *_ev;
|
||||
Gtk::Box m_main;
|
||||
Gtk::Label *m_attrib_label = nullptr;
|
||||
|
@ -34,3 +34,7 @@ ConfirmDialog::ConfirmDialog(Gtk::Window &parent)
|
||||
void ConfirmDialog::SetConfirmText(const Glib::ustring &text) {
|
||||
m_label.set_text(text);
|
||||
}
|
||||
|
||||
void ConfirmDialog::SetAcceptOnly(bool accept_only) {
|
||||
m_cancel.set_visible(!accept_only);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ class ConfirmDialog : public Gtk::Dialog {
|
||||
public:
|
||||
ConfirmDialog(Gtk::Window &parent);
|
||||
void SetConfirmText(const Glib::ustring &text);
|
||||
void SetAcceptOnly(bool accept_only);
|
||||
|
||||
protected:
|
||||
Gtk::Label m_label;
|
||||
|
@ -1,97 +0,0 @@
|
||||
#include "joinguild.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
|
||||
JoinGuildDialog::JoinGuildDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Join Server", parent, true)
|
||||
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_ok("OK")
|
||||
, m_cancel("Cancel")
|
||||
, m_info("Enter code") {
|
||||
set_default_size(300, 50);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
|
||||
Glib::signal_idle().connect(sigc::mem_fun(*this, &JoinGuildDialog::on_idle_slot));
|
||||
|
||||
m_entry.signal_changed().connect(sigc::mem_fun(*this, &JoinGuildDialog::on_entry_changed));
|
||||
|
||||
m_ok.set_sensitive(false);
|
||||
|
||||
m_ok.signal_clicked().connect([&]() {
|
||||
response(Gtk::RESPONSE_OK);
|
||||
});
|
||||
|
||||
m_cancel.signal_clicked().connect([&]() {
|
||||
response(Gtk::RESPONSE_CANCEL);
|
||||
});
|
||||
|
||||
m_entry.set_hexpand(true);
|
||||
m_layout.add(m_entry);
|
||||
m_lower.set_hexpand(true);
|
||||
m_lower.pack_start(m_info);
|
||||
m_info.set_halign(Gtk::ALIGN_START);
|
||||
m_lower.pack_start(m_ok, Gtk::PACK_SHRINK);
|
||||
m_lower.pack_start(m_cancel, Gtk::PACK_SHRINK);
|
||||
m_ok.set_halign(Gtk::ALIGN_END);
|
||||
m_cancel.set_halign(Gtk::ALIGN_END);
|
||||
m_layout.add(m_lower);
|
||||
get_content_area()->add(m_layout);
|
||||
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void JoinGuildDialog::on_entry_changed() {
|
||||
std::string s = m_entry.get_text();
|
||||
std::regex invite_regex(R"((https?:\/\/)?discord\.(gg(\/invite)?\/|com\/invite\/)([A-Za-z0-9\-]+))", std::regex_constants::ECMAScript);
|
||||
std::smatch match;
|
||||
bool full_url = std::regex_search(s, match, invite_regex);
|
||||
if (full_url || IsCode(s)) {
|
||||
m_code = full_url ? match[4].str() : s;
|
||||
m_needs_request = true;
|
||||
m_ok.set_sensitive(false);
|
||||
} else {
|
||||
m_ok.set_sensitive(false);
|
||||
}
|
||||
}
|
||||
|
||||
void JoinGuildDialog::CheckCode() {
|
||||
auto cb = [this](const std::optional<InviteData> &invite) {
|
||||
if (invite.has_value()) {
|
||||
m_ok.set_sensitive(true);
|
||||
if (invite->Guild.has_value()) {
|
||||
if (invite->MemberCount.has_value())
|
||||
m_info.set_text(invite->Guild->Name + " (" + std::to_string(*invite->MemberCount) + " members)");
|
||||
else
|
||||
m_info.set_text(invite->Guild->Name);
|
||||
} else {
|
||||
m_info.set_text("Group DM (" + std::to_string(*invite->MemberCount) + " members)");
|
||||
}
|
||||
} else {
|
||||
m_ok.set_sensitive(false);
|
||||
m_info.set_text("Invalid invite");
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().FetchInvite(m_code, sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
bool JoinGuildDialog::IsCode(std::string str) {
|
||||
return str.length() >= 2 && std::all_of(str.begin(), str.end(), [](char c) -> bool { return std::isalnum(c) || c == '-'; });
|
||||
}
|
||||
|
||||
std::string JoinGuildDialog::GetCode() {
|
||||
return m_code;
|
||||
}
|
||||
|
||||
static const constexpr int RateLimitMS = 1500;
|
||||
bool JoinGuildDialog::on_idle_slot() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (m_needs_request && ((now - m_last_req_time) > std::chrono::milliseconds(RateLimitMS))) {
|
||||
m_needs_request = false;
|
||||
m_last_req_time = now;
|
||||
CheckCode();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
class JoinGuildDialog : public Gtk::Dialog {
|
||||
public:
|
||||
JoinGuildDialog(Gtk::Window &parent);
|
||||
std::string GetCode();
|
||||
|
||||
protected:
|
||||
void on_entry_changed();
|
||||
static bool IsCode(std::string str);
|
||||
|
||||
Gtk::Box m_layout;
|
||||
Gtk::Button m_ok;
|
||||
Gtk::Button m_cancel;
|
||||
Gtk::Box m_lower;
|
||||
Gtk::Label m_info;
|
||||
Gtk::Entry m_entry;
|
||||
|
||||
void CheckCode();
|
||||
|
||||
// needs a rate limit cuz if u hit it u get ip banned from /invites for a long time :(
|
||||
bool m_needs_request = false;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_last_req_time;
|
||||
bool on_idle_slot();
|
||||
|
||||
private:
|
||||
std::string m_code;
|
||||
};
|
@ -30,6 +30,7 @@ void DiscordClient::Start() {
|
||||
if (m_client_started) return;
|
||||
|
||||
m_http.SetBase(GetAPIURL());
|
||||
SetHeaders();
|
||||
|
||||
std::memset(&m_zstream, 0, sizeof(m_zstream));
|
||||
inflateInit2(&m_zstream, MAX_WBITS + 32);
|
||||
@ -572,10 +573,6 @@ void DiscordClient::SendThreadLazyLoad(Snowflake id) {
|
||||
m_websocket.Send(msg);
|
||||
}
|
||||
|
||||
void DiscordClient::JoinGuild(const std::string &code) {
|
||||
m_http.MakePOST("/invites/" + code, "{}", [](auto) {});
|
||||
}
|
||||
|
||||
void DiscordClient::LeaveGuild(Snowflake id) {
|
||||
m_http.MakeDELETE("/users/@me/guilds/" + std::to_string(id), [](auto) {});
|
||||
}
|
||||
@ -610,19 +607,6 @@ void DiscordClient::UpdateStatus(PresenceStatus status, bool is_afk, const Activ
|
||||
m_signal_presence_update.emit(GetUserData(), status);
|
||||
}
|
||||
|
||||
void DiscordClient::CreateDM(Snowflake user_id, const sigc::slot<void(DiscordError code, Snowflake channel_id)> &callback) {
|
||||
CreateDMObject obj;
|
||||
obj.Recipients.push_back(user_id);
|
||||
m_http.MakePOST("/users/@me/channels", nlohmann::json(obj).dump(), [callback](const http::response &response) {
|
||||
if (!CheckCode(response)) {
|
||||
callback(DiscordError::NONE, Snowflake::Invalid);
|
||||
return;
|
||||
}
|
||||
auto channel = nlohmann::json::parse(response.text).get<ChannelData>();
|
||||
callback(GetCodeFromResponse(response), channel.ID);
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::CloseDM(Snowflake channel_id) {
|
||||
m_http.MakeDELETE("/channels/" + std::to_string(channel_id), [](const http::response &response) {
|
||||
CheckCode(response);
|
||||
@ -1181,6 +1165,33 @@ void DiscordClient::AcceptVerificationGate(Snowflake guild_id, VerificationGateI
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::SetReferringChannel(Snowflake id) {
|
||||
if (!id.IsValid()) {
|
||||
m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me");
|
||||
} else {
|
||||
const auto channel = GetChannel(id);
|
||||
if (channel.has_value()) {
|
||||
if (channel->IsDM()) {
|
||||
m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me/" + std::to_string(id));
|
||||
} else if (channel->GuildID.has_value()) {
|
||||
m_http.SetPersistentHeader("Referer", "https://discord.com/channels/" + std::to_string(*channel->GuildID) + "/" + std::to_string(id));
|
||||
} else {
|
||||
m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me");
|
||||
}
|
||||
} else {
|
||||
m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordClient::SetBuildNumber(uint32_t build_number) {
|
||||
m_build_number = build_number;
|
||||
}
|
||||
|
||||
void DiscordClient::SetCookie(std::string_view cookie) {
|
||||
m_http.SetCookie(cookie);
|
||||
}
|
||||
|
||||
void DiscordClient::UpdateToken(const std::string &token) {
|
||||
if (!IsStarted()) {
|
||||
m_token = token;
|
||||
@ -2294,7 +2305,7 @@ void DiscordClient::HeartbeatThread() {
|
||||
void DiscordClient::SendIdentify() {
|
||||
IdentifyMessage msg;
|
||||
msg.Token = m_token;
|
||||
msg.Capabilities = 125; // no idea what this is
|
||||
msg.Capabilities = 509; // no idea what this is
|
||||
msg.Properties.OS = "Windows";
|
||||
msg.Properties.Browser = "Chrome";
|
||||
msg.Properties.Device = "";
|
||||
@ -2307,7 +2318,7 @@ void DiscordClient::SendIdentify() {
|
||||
msg.Properties.ReferrerCurrent = "";
|
||||
msg.Properties.ReferringDomainCurrent = "";
|
||||
msg.Properties.ReleaseChannel = "stable";
|
||||
msg.Properties.ClientBuildNumber = 105691;
|
||||
msg.Properties.ClientBuildNumber = m_build_number;
|
||||
msg.Properties.ClientEventSource = "";
|
||||
msg.Presence.Status = "online";
|
||||
msg.Presence.Since = 0;
|
||||
@ -2316,6 +2327,7 @@ void DiscordClient::SendIdentify() {
|
||||
msg.ClientState.HighestLastMessageID = "0";
|
||||
msg.ClientState.ReadStateVersion = 0;
|
||||
msg.ClientState.UserGuildSettingsVersion = -1;
|
||||
SetSuperPropertiesFromIdentity(msg);
|
||||
const bool b = m_websocket.GetPrintMessages();
|
||||
m_websocket.SetPrintMessages(false);
|
||||
m_websocket.Send(msg);
|
||||
@ -2330,6 +2342,36 @@ void DiscordClient::SendResume() {
|
||||
m_websocket.Send(msg);
|
||||
}
|
||||
|
||||
void DiscordClient::SetHeaders() {
|
||||
m_http.SetPersistentHeader("Sec-Fetch-Dest", "empty");
|
||||
m_http.SetPersistentHeader("Sec-Fetch-Mode", "cors");
|
||||
m_http.SetPersistentHeader("Sec-Fetch-Site", "same-origin");
|
||||
m_http.SetPersistentHeader("X-Debug-Options", "bugReporterEnabled");
|
||||
m_http.SetPersistentHeader("Accept-Language", "en-US,en;q=0.9");
|
||||
|
||||
SetReferringChannel(Snowflake::Invalid);
|
||||
}
|
||||
|
||||
void DiscordClient::SetSuperPropertiesFromIdentity(const IdentifyMessage &identity) {
|
||||
nlohmann::ordered_json j;
|
||||
j["os"] = identity.Properties.OS;
|
||||
j["browser"] = identity.Properties.Browser;
|
||||
j["device"] = identity.Properties.Device;
|
||||
j["system_locale"] = identity.Properties.SystemLocale;
|
||||
j["browser_user_agent"] = identity.Properties.BrowserUserAgent;
|
||||
j["browser_version"] = identity.Properties.BrowserVersion;
|
||||
j["os_version"] = identity.Properties.OSVersion;
|
||||
j["referrer"] = identity.Properties.Referrer;
|
||||
j["referring_domain"] = identity.Properties.ReferringDomain;
|
||||
j["referrer_current"] = identity.Properties.ReferrerCurrent;
|
||||
j["referring_domain_current"] = identity.Properties.ReferringDomainCurrent;
|
||||
j["release_channel"] = identity.Properties.ReleaseChannel;
|
||||
j["client_build_number"] = identity.Properties.ClientBuildNumber;
|
||||
j["client_event_source"] = nullptr; // probably will never be non-null ("") anyways
|
||||
m_http.SetPersistentHeader("X-Super-Properties", Glib::Base64::encode(j.dump()));
|
||||
m_http.SetPersistentHeader("X-Discord-Locale", identity.Properties.SystemLocale);
|
||||
}
|
||||
|
||||
void DiscordClient::HandleSocketOpen() {
|
||||
}
|
||||
|
||||
@ -2395,10 +2437,12 @@ void DiscordClient::StoreMessageData(Message &msg) {
|
||||
if (msg.Member.has_value())
|
||||
m_store.SetGuildMember(*msg.GuildID, msg.Author.ID, *msg.Member);
|
||||
|
||||
if (msg.Interaction.has_value() && msg.Interaction->Member.has_value()) {
|
||||
if (msg.Interaction.has_value()) {
|
||||
m_store.SetUser(msg.Interaction->User.ID, msg.Interaction->User);
|
||||
if (msg.Interaction->Member.has_value()) {
|
||||
m_store.SetGuildMember(*msg.GuildID, msg.Interaction->User.ID, *msg.Interaction->Member);
|
||||
}
|
||||
}
|
||||
|
||||
m_store.EndTransaction();
|
||||
|
||||
|
@ -113,13 +113,11 @@ public:
|
||||
void EditMessage(Snowflake channel_id, Snowflake id, std::string content);
|
||||
void SendLazyLoad(Snowflake id);
|
||||
void SendThreadLazyLoad(Snowflake id);
|
||||
void JoinGuild(const std::string &code);
|
||||
void LeaveGuild(Snowflake id);
|
||||
void KickUser(Snowflake user_id, Snowflake guild_id);
|
||||
void BanUser(Snowflake user_id, Snowflake guild_id); // todo: reason, delete messages
|
||||
void UpdateStatus(PresenceStatus status, bool is_afk);
|
||||
void UpdateStatus(PresenceStatus status, bool is_afk, const ActivityData &obj);
|
||||
void CreateDM(Snowflake user_id, const sigc::slot<void(DiscordError code, Snowflake channel_id)> &callback);
|
||||
void CloseDM(Snowflake channel_id);
|
||||
std::optional<Snowflake> FindDM(Snowflake user_id); // wont find group dms
|
||||
void AddReaction(Snowflake id, Glib::ustring param);
|
||||
@ -205,6 +203,11 @@ public:
|
||||
void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot<void(std::optional<VerificationGateInfoObject>)> &callback);
|
||||
void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot<void(DiscordError code)> &callback);
|
||||
|
||||
void SetReferringChannel(Snowflake id);
|
||||
|
||||
void SetBuildNumber(uint32_t build_number);
|
||||
void SetCookie(std::string_view cookie);
|
||||
|
||||
void UpdateToken(const std::string &token);
|
||||
void SetUserAgent(const std::string &agent);
|
||||
|
||||
@ -286,6 +289,9 @@ private:
|
||||
void SendIdentify();
|
||||
void SendResume();
|
||||
|
||||
void SetHeaders();
|
||||
void SetSuperPropertiesFromIdentity(const IdentifyMessage &identity);
|
||||
|
||||
void HandleSocketOpen();
|
||||
void HandleSocketClose(uint16_t code);
|
||||
|
||||
@ -299,6 +305,8 @@ private:
|
||||
|
||||
std::string m_token;
|
||||
|
||||
uint32_t m_build_number = 142000;
|
||||
|
||||
void AddUserToGuild(Snowflake user_id, Snowflake guild_id);
|
||||
std::map<Snowflake, std::set<Snowflake>> m_guild_to_users;
|
||||
std::map<Snowflake, std::set<Snowflake>> m_guild_to_channels;
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
//#define USE_LOCAL_PROXY
|
||||
HTTPClient::HTTPClient() {
|
||||
m_dispatcher.connect(sigc::mem_fun(*this, &HTTPClient::RunCallbacks));
|
||||
}
|
||||
@ -19,16 +18,22 @@ void HTTPClient::SetAuth(std::string auth) {
|
||||
m_authorization = std::move(auth);
|
||||
}
|
||||
|
||||
void HTTPClient::SetPersistentHeader(std::string name, std::string value) {
|
||||
m_headers.insert_or_assign(std::move(name), std::move(value));
|
||||
}
|
||||
|
||||
void HTTPClient::SetCookie(std::string_view cookie) {
|
||||
m_cookie = cookie;
|
||||
}
|
||||
|
||||
void HTTPClient::MakeDELETE(const std::string &path, const std::function<void(http::response_type r)> &cb) {
|
||||
printf("DELETE %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb] {
|
||||
http::request req(http::REQUEST_DELETE, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_header("Origin", "https://discord.com");
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@ -40,14 +45,12 @@ void HTTPClient::MakePATCH(const std::string &path, const std::string &payload,
|
||||
printf("PATCH %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] {
|
||||
http::request req(http::REQUEST_PATCH, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_header("Content-Type", "application/json");
|
||||
req.set_header("Origin", "https://discord.com");
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
req.set_body(payload);
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@ -59,14 +62,12 @@ void HTTPClient::MakePOST(const std::string &path, const std::string &payload, c
|
||||
printf("POST %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] {
|
||||
http::request req(http::REQUEST_POST, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_header("Content-Type", "application/json");
|
||||
req.set_header("Origin", "https://discord.com");
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
req.set_body(payload);
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@ -78,15 +79,13 @@ void HTTPClient::MakePUT(const std::string &path, const std::string &payload, co
|
||||
printf("PUT %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] {
|
||||
http::request req(http::REQUEST_PUT, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_header("Origin", "https://discord.com");
|
||||
if (!payload.empty())
|
||||
req.set_header("Content-Type", "application/json");
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
req.set_body(payload);
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@ -98,13 +97,9 @@ void HTTPClient::MakeGET(const std::string &path, const std::function<void(http:
|
||||
printf("GET %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb] {
|
||||
http::request req(http::REQUEST_GET, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_header("Content-Type", "application/json");
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@ -116,10 +111,6 @@ http::request HTTPClient::CreateRequest(http::EMethod method, std::string path)
|
||||
http::request req(method, m_api_base + path);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
return req;
|
||||
}
|
||||
|
||||
@ -147,6 +138,14 @@ void HTTPClient::RunCallbacks() {
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void HTTPClient::AddHeaders(http::request &r) {
|
||||
for (const auto &[name, val] : m_headers) {
|
||||
r.set_header(name, val);
|
||||
}
|
||||
curl_easy_setopt(r.get_curl(), CURLOPT_COOKIE, m_cookie.c_str());
|
||||
curl_easy_setopt(r.get_curl(), CURLOPT_ACCEPT_ENCODING, "");
|
||||
}
|
||||
|
||||
void HTTPClient::OnResponse(const http::response_type &r, const std::function<void(http::response_type r)> &cb) {
|
||||
CleanupFutures();
|
||||
try {
|
||||
|
@ -17,6 +17,9 @@ public:
|
||||
|
||||
void SetUserAgent(std::string agent);
|
||||
void SetAuth(std::string auth);
|
||||
void SetPersistentHeader(std::string name, std::string value);
|
||||
void SetCookie(std::string_view cookie);
|
||||
|
||||
void MakeDELETE(const std::string &path, const std::function<void(http::response_type r)> &cb);
|
||||
void MakeGET(const std::string &path, const std::function<void(http::response_type r)> &cb);
|
||||
void MakePATCH(const std::string &path, const std::string &payload, const std::function<void(http::response_type r)> &cb);
|
||||
@ -27,6 +30,8 @@ public:
|
||||
void Execute(http::request &&req, const std::function<void(http::response_type r)> &cb);
|
||||
|
||||
private:
|
||||
void AddHeaders(http::request &r);
|
||||
|
||||
void OnResponse(const http::response_type &r, const std::function<void(http::response_type r)> &cb);
|
||||
void CleanupFutures();
|
||||
|
||||
@ -39,4 +44,6 @@ private:
|
||||
std::string m_api_base;
|
||||
std::string m_authorization;
|
||||
std::string m_agent;
|
||||
std::unordered_map<std::string, std::string> m_headers;
|
||||
std::string m_cookie;
|
||||
};
|
||||
|
@ -262,6 +262,7 @@ void to_json(nlohmann::json &j, const ClientStateProperties &m) {
|
||||
j["highest_last_message_id"] = m.HighestLastMessageID;
|
||||
j["read_state_version"] = m.ReadStateVersion;
|
||||
j["user_guild_settings_version"] = m.UserGuildSettingsVersion;
|
||||
j["user_settings_version"] = m.UserSettingsVersion;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const IdentifyMessage &m) {
|
||||
|
@ -382,6 +382,7 @@ struct ClientStateProperties {
|
||||
std::string HighestLastMessageID = "0";
|
||||
int ReadStateVersion = 0;
|
||||
int UserGuildSettingsVersion = -1;
|
||||
int UserSettingsVersion = -1;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const ClientStateProperties &m);
|
||||
};
|
||||
|
@ -253,6 +253,14 @@ void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMem
|
||||
|
||||
s->Reset();
|
||||
|
||||
{
|
||||
auto &s = m_stmt_clr_member_roles;
|
||||
s->Bind(1, user_id);
|
||||
s->Bind(2, guild_id);
|
||||
s->Step();
|
||||
s->Reset();
|
||||
}
|
||||
|
||||
{
|
||||
auto &s = m_stmt_set_member_roles;
|
||||
|
||||
@ -1882,6 +1890,20 @@ bool Store::CreateStatements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stmt_clr_member_roles = std::make_unique<Statement>(m_db, R"(
|
||||
DELETE FROM member_roles
|
||||
WHERE user = ? AND
|
||||
EXISTS (
|
||||
SELECT 1 FROM roles
|
||||
WHERE member_roles.role = roles.id
|
||||
AND roles.guild = ?
|
||||
)
|
||||
)");
|
||||
if (!m_stmt_clr_member_roles->OK()) {
|
||||
fprintf(stderr, "failed to prepare clear member roles statement: %s\n", m_db.ErrStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stmt_set_guild_emoji = std::make_unique<Statement>(m_db, R"(
|
||||
REPLACE INTO guild_emojis VALUES (
|
||||
?, ?
|
||||
|
@ -281,6 +281,7 @@ private:
|
||||
STMT(set_interaction);
|
||||
STMT(set_member_roles);
|
||||
STMT(get_member_roles);
|
||||
STMT(clr_member_roles);
|
||||
STMT(set_guild_emoji);
|
||||
STMT(get_guild_emojis);
|
||||
STMT(clr_guild_emoji);
|
||||
|
@ -8,33 +8,5 @@ void from_json(const nlohmann::json &j, UserSettingsGuildFoldersEntry &m) {
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserSettings &m) {
|
||||
JS_D("timezone_offset", m.TimezoneOffset);
|
||||
JS_D("theme", m.Theme);
|
||||
JS_D("stream_notifications_enabled", m.AreStreamNotificationsEnabled);
|
||||
JS_D("status", m.Status);
|
||||
JS_D("show_current_game", m.ShouldShowCurrentGame);
|
||||
// JS_D("restricted_guilds", m.RestrictedGuilds);
|
||||
JS_D("render_reactions", m.ShouldRenderReactions);
|
||||
JS_D("render_embeds", m.ShouldRenderEmbeds);
|
||||
JS_D("native_phone_integration_enabled", m.IsNativePhoneIntegrationEnabled);
|
||||
JS_D("message_display_compact", m.ShouldMessageDisplayCompact);
|
||||
JS_D("locale", m.Locale);
|
||||
JS_D("inline_embed_media", m.ShouldInlineEmbedMedia);
|
||||
JS_D("inline_attachment_media", m.ShouldInlineAttachmentMedia);
|
||||
JS_D("guild_positions", m.GuildPositions);
|
||||
JS_D("guild_folders", m.GuildFolders);
|
||||
JS_D("gif_auto_play", m.ShouldGIFAutoplay);
|
||||
// JS_D("friend_source_flags", m.FriendSourceFlags);
|
||||
JS_D("explicit_content_filter", m.ExplicitContentFilter);
|
||||
JS_D("enable_tts_command", m.IsTTSCommandEnabled);
|
||||
JS_D("disable_games_tab", m.ShouldDisableGamesTab);
|
||||
JS_D("developer_mode", m.DeveloperMode);
|
||||
JS_D("detect_platform_accounts", m.ShouldDetectPlatformAccounts);
|
||||
JS_D("default_guilds_restricted", m.AreDefaultGuildsRestricted);
|
||||
// JS_N("custom_status", m.CustomStatus);
|
||||
JS_D("convert_emoticons", m.ShouldConvertEmoticons);
|
||||
JS_D("contact_sync_enabled", m.IsContactSyncEnabled);
|
||||
JS_D("animate_emoji", m.ShouldAnimateEmojis);
|
||||
JS_D("allow_accessibility_detection", m.IsAccessibilityDetectionAllowed);
|
||||
JS_D("afk_timeout", m.AFKTimeout);
|
||||
}
|
||||
|
@ -13,35 +13,36 @@ struct UserSettingsGuildFoldersEntry {
|
||||
};
|
||||
|
||||
struct UserSettings {
|
||||
int TimezoneOffset; //
|
||||
std::string Theme; //
|
||||
bool AreStreamNotificationsEnabled; //
|
||||
std::string Status; //
|
||||
bool ShouldShowCurrentGame; //
|
||||
// std::vector<Unknown> RestrictedGuilds; //
|
||||
bool ShouldRenderReactions; //
|
||||
bool ShouldRenderEmbeds; //
|
||||
bool IsNativePhoneIntegrationEnabled; //
|
||||
bool ShouldMessageDisplayCompact; //
|
||||
std::string Locale; //
|
||||
bool ShouldInlineEmbedMedia; //
|
||||
bool ShouldInlineAttachmentMedia; //
|
||||
std::vector<UserSettingsGuildFoldersEntry> GuildFolders;
|
||||
/*
|
||||
int TimezoneOffset;
|
||||
std::string Theme;
|
||||
bool AreStreamNotificationsEnabled;
|
||||
std::string Status;
|
||||
bool ShouldShowCurrentGame;
|
||||
// std::vector<Unknown> RestrictedGuilds;
|
||||
bool ShouldRenderReactions;
|
||||
bool ShouldRenderEmbeds;
|
||||
bool IsNativePhoneIntegrationEnabled;
|
||||
bool ShouldMessageDisplayCompact;
|
||||
std::string Locale;
|
||||
bool ShouldInlineEmbedMedia;
|
||||
bool ShouldInlineAttachmentMedia;
|
||||
std::vector<Snowflake> GuildPositions; // deprecated?
|
||||
std::vector<UserSettingsGuildFoldersEntry> GuildFolders; //
|
||||
bool ShouldGIFAutoplay; //
|
||||
// Unknown FriendSourceFlags; //
|
||||
int ExplicitContentFilter; //
|
||||
bool IsTTSCommandEnabled; //
|
||||
bool ShouldDisableGamesTab; //
|
||||
bool DeveloperMode; //
|
||||
bool ShouldDetectPlatformAccounts; //
|
||||
bool AreDefaultGuildsRestricted; //
|
||||
bool ShouldGIFAutoplay;
|
||||
// Unknown FriendSourceFlags;
|
||||
int ExplicitContentFilter;
|
||||
bool IsTTSCommandEnabled;
|
||||
bool ShouldDisableGamesTab;
|
||||
bool DeveloperMode;
|
||||
bool ShouldDetectPlatformAccounts;
|
||||
bool AreDefaultGuildsRestricted;
|
||||
// Unknown CustomStatus; // null
|
||||
bool ShouldConvertEmoticons; //
|
||||
bool IsContactSyncEnabled; //
|
||||
bool ShouldAnimateEmojis; //
|
||||
bool IsAccessibilityDetectionAllowed; //
|
||||
int AFKTimeout;
|
||||
bool ShouldConvertEmoticons;
|
||||
bool IsContactSyncEnabled;
|
||||
bool ShouldAnimateEmojis;
|
||||
bool IsAccessibilityDetectionAllowed;
|
||||
int AFKTimeout;*/
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserSettings &m);
|
||||
};
|
||||
|
@ -2,6 +2,22 @@
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#ifdef ABADDON_IS_BIG_ENDIAN
|
||||
/* Allows processing emojis.bin correctly on big-endian systems. */
|
||||
int emojis_int32_correct_endian(int little_endian_in) {
|
||||
/* this does the same thing as __bswap_32() but can be done without
|
||||
non-standard headers. */
|
||||
return ((little_endian_in >> 24) & 0xff) | // move byte 3 to byte 0
|
||||
((little_endian_in << 8) & 0xff0000) | // move byte 1 to byte 2
|
||||
((little_endian_in >> 8) & 0xff00) | // move byte 2 to byte 1
|
||||
((little_endian_in << 24) & 0xff000000); // byte 0 to byte 3
|
||||
}
|
||||
#else
|
||||
int emojis_int32_correct_endian(int little_endian_in) {
|
||||
return little_endian_in;
|
||||
}
|
||||
#endif
|
||||
|
||||
EmojiResource::EmojiResource(std::string filepath)
|
||||
: m_filepath(std::move(filepath)) {}
|
||||
|
||||
@ -11,18 +27,22 @@ bool EmojiResource::Load() {
|
||||
|
||||
int index_offset;
|
||||
std::fread(&index_offset, 4, 1, m_fp);
|
||||
index_offset = emojis_int32_correct_endian(index_offset);
|
||||
std::fseek(m_fp, index_offset, SEEK_SET);
|
||||
|
||||
int emojis_count;
|
||||
std::fread(&emojis_count, 4, 1, m_fp);
|
||||
emojis_count = emojis_int32_correct_endian(emojis_count);
|
||||
for (int i = 0; i < emojis_count; i++) {
|
||||
std::vector<std::string> shortcodes;
|
||||
|
||||
int shortcodes_count;
|
||||
std::fread(&shortcodes_count, 4, 1, m_fp);
|
||||
shortcodes_count = emojis_int32_correct_endian(shortcodes_count);
|
||||
for (int j = 0; j < shortcodes_count; j++) {
|
||||
int shortcode_length;
|
||||
std::fread(&shortcode_length, 4, 1, m_fp);
|
||||
shortcode_length = emojis_int32_correct_endian(shortcode_length);
|
||||
std::string shortcode(shortcode_length, '\0');
|
||||
std::fread(shortcode.data(), shortcode_length, 1, m_fp);
|
||||
shortcodes.push_back(std::move(shortcode));
|
||||
@ -30,13 +50,16 @@ bool EmojiResource::Load() {
|
||||
|
||||
int surrogates_count;
|
||||
std::fread(&surrogates_count, 4, 1, m_fp);
|
||||
surrogates_count = emojis_int32_correct_endian(surrogates_count);
|
||||
std::string surrogates(surrogates_count, '\0');
|
||||
std::fread(surrogates.data(), surrogates_count, 1, m_fp);
|
||||
m_patterns.emplace_back(surrogates);
|
||||
|
||||
int data_size, data_offset;
|
||||
std::fread(&data_size, 4, 1, m_fp);
|
||||
data_size = emojis_int32_correct_endian(data_size);
|
||||
std::fread(&data_offset, 4, 1, m_fp);
|
||||
data_offset = emojis_int32_correct_endian(data_offset);
|
||||
m_index[surrogates] = { data_offset, data_size };
|
||||
|
||||
for (const auto &shortcode : shortcodes)
|
||||
|
14
src/http.cpp
14
src/http.cpp
@ -2,6 +2,8 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
// #define USE_LOCAL_PROXY
|
||||
|
||||
namespace http {
|
||||
request::request(EMethod method, std::string url)
|
||||
: m_url(std::move(url)) {
|
||||
@ -34,7 +36,6 @@ request::request(request &&other) noexcept
|
||||
, m_url(std::exchange(other.m_url, ""))
|
||||
, m_method(std::exchange(other.m_method, nullptr))
|
||||
, m_header_list(std::exchange(other.m_header_list, nullptr))
|
||||
, m_error_buf(other.m_error_buf)
|
||||
, m_form(std::exchange(other.m_form, nullptr))
|
||||
, m_read_streams(std::move(other.m_read_streams))
|
||||
, m_progress_callback(std::move(other.m_progress_callback)) {
|
||||
@ -98,6 +99,10 @@ void request::set_user_agent(const std::string &data) {
|
||||
curl_easy_setopt(m_curl, CURLOPT_USERAGENT, data.c_str());
|
||||
}
|
||||
|
||||
CURL *request::get_curl() {
|
||||
return m_curl;
|
||||
}
|
||||
|
||||
void request::make_form() {
|
||||
m_form = curl_mime_init(m_curl);
|
||||
}
|
||||
@ -143,14 +148,16 @@ response request::execute() {
|
||||
detail::check_init();
|
||||
|
||||
std::string str;
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
set_proxy("http://127.0.0.1:8888");
|
||||
set_verify_ssl(false);
|
||||
#endif
|
||||
curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, m_method);
|
||||
curl_easy_setopt(m_curl, CURLOPT_URL, m_url.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, detail::curl_write_data_callback);
|
||||
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &str);
|
||||
curl_easy_setopt(m_curl, CURLOPT_ERRORBUFFER, m_error_buf);
|
||||
m_error_buf[0] = '\0';
|
||||
if (m_header_list != nullptr)
|
||||
curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_header_list);
|
||||
if (m_form != nullptr)
|
||||
@ -160,7 +167,6 @@ response request::execute() {
|
||||
if (result != CURLE_OK) {
|
||||
auto response = detail::make_response(m_url, EStatusCode::ClientErrorCURLPerform);
|
||||
response.error_string = curl_easy_strerror(result);
|
||||
response.error_string += " " + std::string(m_error_buf.data());
|
||||
return response;
|
||||
}
|
||||
|
||||
|
@ -122,6 +122,8 @@ struct request {
|
||||
|
||||
response execute();
|
||||
|
||||
CURL *get_curl();
|
||||
|
||||
private:
|
||||
void prepare();
|
||||
|
||||
@ -129,7 +131,6 @@ private:
|
||||
std::string m_url;
|
||||
const char *m_method;
|
||||
curl_slist *m_header_list = nullptr;
|
||||
std::array<char, CURL_ERROR_SIZE> m_error_buf = { 0 };
|
||||
curl_mime *m_form = nullptr;
|
||||
std::function<void(curl_off_t, curl_off_t)> m_progress_callback;
|
||||
|
||||
|
@ -47,6 +47,7 @@ void SettingsManager::ReadSettings() {
|
||||
SMSTR("discord", "gateway", GatewayURL);
|
||||
SMBOOL("discord", "memory_db", UseMemoryDB);
|
||||
SMBOOL("discord", "prefetch", Prefetch);
|
||||
SMBOOL("discord", "autoconnect", Autoconnect);
|
||||
SMSTR("gui", "css", MainCSS);
|
||||
SMBOOL("gui", "animated_guild_hover_only", AnimatedGuildHoverOnly);
|
||||
SMBOOL("gui", "animations", ShowAnimations);
|
||||
@ -56,6 +57,8 @@ void SettingsManager::ReadSettings() {
|
||||
SMBOOL("gui", "save_state", SaveState);
|
||||
SMBOOL("gui", "stock_emojis", ShowStockEmojis);
|
||||
SMBOOL("gui", "unreads", Unreads);
|
||||
SMBOOL("gui", "alt_menu", AltMenu);
|
||||
SMBOOL("gui", "hide_to_tray", HideToTray);
|
||||
SMINT("http", "concurrent", CacheHTTPConcurrency);
|
||||
SMSTR("http", "user_agent", UserAgent);
|
||||
SMSTR("style", "expandercolor", ChannelsExpanderColor);
|
||||
@ -125,6 +128,7 @@ void SettingsManager::Close() {
|
||||
SMSTR("discord", "gateway", GatewayURL);
|
||||
SMBOOL("discord", "memory_db", UseMemoryDB);
|
||||
SMBOOL("discord", "prefetch", Prefetch);
|
||||
SMBOOL("discord", "autoconnect", Autoconnect);
|
||||
SMSTR("gui", "css", MainCSS);
|
||||
SMBOOL("gui", "animated_guild_hover_only", AnimatedGuildHoverOnly);
|
||||
SMBOOL("gui", "animations", ShowAnimations);
|
||||
@ -134,6 +138,8 @@ void SettingsManager::Close() {
|
||||
SMBOOL("gui", "save_state", SaveState);
|
||||
SMBOOL("gui", "stock_emojis", ShowStockEmojis);
|
||||
SMBOOL("gui", "unreads", Unreads);
|
||||
SMBOOL("gui", "alt_menu", AltMenu);
|
||||
SMBOOL("gui", "hide_to_tray", HideToTray);
|
||||
SMINT("http", "concurrent", CacheHTTPConcurrency);
|
||||
SMSTR("http", "user_agent", UserAgent);
|
||||
SMSTR("style", "expandercolor", ChannelsExpanderColor);
|
||||
|
@ -12,6 +12,7 @@ public:
|
||||
std::string DiscordToken;
|
||||
bool UseMemoryDB { false };
|
||||
bool Prefetch { false };
|
||||
bool Autoconnect { false };
|
||||
|
||||
// [gui]
|
||||
std::string MainCSS { "main.css" };
|
||||
@ -27,6 +28,8 @@ public:
|
||||
bool ShowStockEmojis { true };
|
||||
#endif
|
||||
bool Unreads { true };
|
||||
bool AltMenu { false };
|
||||
bool HideToTray { false };
|
||||
|
||||
// [http]
|
||||
int CacheHTTPConcurrency { 20 };
|
||||
|
126
src/startup.cpp
Normal file
126
src/startup.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
#include "startup.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include <future>
|
||||
#include <memory>
|
||||
|
||||
DiscordStartupDialog::DiscordStartupDialog(Gtk::Window &window)
|
||||
: Gtk::MessageDialog(window, "", false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_NONE, true) {
|
||||
m_dispatcher.connect(sigc::mem_fun(*this, &DiscordStartupDialog::DispatchCallback));
|
||||
|
||||
property_text() = "Getting connection info...";
|
||||
|
||||
RunAsync();
|
||||
}
|
||||
|
||||
std::optional<std::string> DiscordStartupDialog::GetCookie() const {
|
||||
return m_cookie;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> DiscordStartupDialog::GetBuildNumber() const {
|
||||
return m_build_number;
|
||||
}
|
||||
|
||||
// good enough
|
||||
std::optional<std::pair<std::string, std::string>> ParseCookie(const Glib::ustring &str) {
|
||||
auto regex = Glib::Regex::create("\\t");
|
||||
const std::vector<Glib::ustring> split = regex->split(str);
|
||||
if (split.size() < 7) return {};
|
||||
|
||||
return { { split[5], split[6] } };
|
||||
}
|
||||
|
||||
std::optional<Glib::ustring> GetJavascriptFileFromAppPage(const Glib::ustring &contents) {
|
||||
auto regex = Glib::Regex::create(R"(app-mount.*(/assets/[\w\d]*.js).*/assets/[\w\d]*.js)");
|
||||
Glib::MatchInfo match;
|
||||
if (regex->match(contents, match)) {
|
||||
return match.fetch(1);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<uint32_t> GetBuildNumberFromJSURL(const Glib::ustring &url, const std::string &cookie) {
|
||||
http::request req(http::REQUEST_GET, "https://discord.com" + url);
|
||||
req.set_header("Accept-Language", "en-US,en;q=0.9");
|
||||
req.set_header("Sec-Fetch-Dest", "document");
|
||||
req.set_header("Sec-Fetch-Mode", "navigate");
|
||||
req.set_header("Sec-Fetch-Site", "none");
|
||||
req.set_header("Sec-Fetch-User", "?1");
|
||||
req.set_user_agent(Abaddon::Get().GetSettings().UserAgent);
|
||||
|
||||
curl_easy_setopt(req.get_curl(), CURLOPT_COOKIE, cookie.c_str());
|
||||
|
||||
auto res = req.execute();
|
||||
if (res.error) return {};
|
||||
|
||||
auto regex = Glib::Regex::create(R"("buildNumber",null!==\(t="(\d+)\"\))");
|
||||
Glib::MatchInfo match;
|
||||
if (regex->match(res.text, match)) {
|
||||
const auto str_value = match.fetch(1);
|
||||
try {
|
||||
return std::stoul(str_value);
|
||||
} catch (...) { return {}; }
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::pair<std::optional<std::string>, std::string> GetCookieTask() {
|
||||
http::request req(http::REQUEST_GET, "https://discord.com/app");
|
||||
req.set_header("Accept-Language", "en-US,en;q=0.9");
|
||||
req.set_header("Sec-Fetch-Dest", "document");
|
||||
req.set_header("Sec-Fetch-Mode", "navigate");
|
||||
req.set_header("Sec-Fetch-Site", "none");
|
||||
req.set_header("Sec-Fetch-User", "?1");
|
||||
req.set_user_agent(Abaddon::Get().GetSettings().UserAgent);
|
||||
|
||||
curl_easy_setopt(req.get_curl(), CURLOPT_COOKIEFILE, "");
|
||||
|
||||
auto res = req.execute();
|
||||
if (res.error) return {};
|
||||
|
||||
curl_slist *slist;
|
||||
if (curl_easy_getinfo(req.get_curl(), CURLINFO_COOKIELIST, &slist) != CURLE_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string dcfduid;
|
||||
std::string sdcfduid;
|
||||
|
||||
for (auto *cur = slist; cur != nullptr; cur = cur->next) {
|
||||
const auto cookie = ParseCookie(cur->data);
|
||||
if (cookie.has_value()) {
|
||||
if (cookie->first == "__dcfduid") {
|
||||
dcfduid = cookie->second;
|
||||
} else if (cookie->first == "__sdcfduid") {
|
||||
sdcfduid = cookie->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
curl_slist_free_all(slist);
|
||||
|
||||
if (!dcfduid.empty() && !sdcfduid.empty()) {
|
||||
return { "__dcfduid=" + dcfduid + "; __sdcfduid=" + sdcfduid, res.text };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void DiscordStartupDialog::RunAsync() {
|
||||
auto futptr = std::make_shared<std::future<void>>();
|
||||
*futptr = std::async(std::launch::async, [this, futptr] {
|
||||
auto [opt_cookie, app_page] = GetCookieTask();
|
||||
m_cookie = opt_cookie;
|
||||
if (opt_cookie.has_value()) {
|
||||
auto js_url = GetJavascriptFileFromAppPage(app_page);
|
||||
if (js_url.has_value()) {
|
||||
m_build_number = GetBuildNumberFromJSURL(*js_url, *opt_cookie);
|
||||
}
|
||||
}
|
||||
m_dispatcher.emit();
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordStartupDialog::DispatchCallback() {
|
||||
response(Gtk::RESPONSE_OK);
|
||||
}
|
25
src/startup.hpp
Normal file
25
src/startup.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <glibmm/dispatcher.h>
|
||||
#include <gtkmm/messagedialog.h>
|
||||
#include <gtkmm/window.h>
|
||||
#include <optional>
|
||||
|
||||
// fetch cookies, build number async
|
||||
|
||||
class DiscordStartupDialog : public Gtk::MessageDialog {
|
||||
public:
|
||||
DiscordStartupDialog(Gtk::Window &window);
|
||||
|
||||
[[nodiscard]] std::optional<std::string> GetCookie() const;
|
||||
[[nodiscard]] std::optional<uint32_t> GetBuildNumber() const;
|
||||
|
||||
private:
|
||||
void RunAsync();
|
||||
|
||||
void DispatchCallback();
|
||||
|
||||
Glib::Dispatcher m_dispatcher;
|
||||
|
||||
std::optional<std::string> m_cookie;
|
||||
std::optional<uint32_t> m_build_number;
|
||||
};
|
@ -56,9 +56,8 @@ GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id)
|
||||
guild_icon_url = guild.GetIconURL("gif", "512");
|
||||
else
|
||||
guild_icon_url = guild.GetIconURL("png", "512");
|
||||
m_guild_icon_ev.signal_button_press_event().connect([guild_icon_url](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS)
|
||||
if (event->button == GDK_BUTTON_PRIMARY)
|
||||
m_guild_icon_ev.signal_button_release_event().connect([guild_icon_url](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY)
|
||||
LaunchBrowser(guild_icon_url);
|
||||
|
||||
return false;
|
||||
|
@ -158,6 +158,10 @@ void MainWindow::UpdateMenus() {
|
||||
OnViewSubmenuPopup();
|
||||
}
|
||||
|
||||
void MainWindow::ToggleMenuVisibility() {
|
||||
m_menu_bar.set_visible(!m_menu_bar.get_visible());
|
||||
}
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
void MainWindow::GoBack() {
|
||||
m_chat.GoBack();
|
||||
@ -195,7 +199,6 @@ void MainWindow::OnDiscordSubmenuPopup() {
|
||||
std::string token = Abaddon::Get().GetDiscordToken();
|
||||
m_menu_discord_connect.set_sensitive(!token.empty() && !discord_active);
|
||||
m_menu_discord_disconnect.set_sensitive(discord_active);
|
||||
m_menu_discord_join_guild.set_sensitive(discord_active);
|
||||
m_menu_discord_set_token.set_sensitive(!discord_active);
|
||||
m_menu_discord_set_status.set_sensitive(discord_active);
|
||||
}
|
||||
@ -238,15 +241,12 @@ void MainWindow::SetupMenu() {
|
||||
m_menu_discord_disconnect.set_label("Disconnect");
|
||||
m_menu_discord_disconnect.set_sensitive(false);
|
||||
m_menu_discord_set_token.set_label("Set Token");
|
||||
m_menu_discord_join_guild.set_label("Accept Invite");
|
||||
m_menu_discord_join_guild.set_sensitive(false);
|
||||
m_menu_discord_set_status.set_label("Set Status");
|
||||
m_menu_discord_set_status.set_sensitive(false);
|
||||
m_menu_discord_add_recipient.set_label("Add user to DM");
|
||||
m_menu_discord_sub.append(m_menu_discord_connect);
|
||||
m_menu_discord_sub.append(m_menu_discord_disconnect);
|
||||
m_menu_discord_sub.append(m_menu_discord_set_token);
|
||||
m_menu_discord_sub.append(m_menu_discord_join_guild);
|
||||
m_menu_discord_sub.append(m_menu_discord_set_status);
|
||||
m_menu_discord_sub.append(m_menu_discord_add_recipient);
|
||||
m_menu_discord.set_submenu(m_menu_discord_sub);
|
||||
@ -265,6 +265,12 @@ void MainWindow::SetupMenu() {
|
||||
m_menu_view_threads.set_label("Threads");
|
||||
m_menu_view_mark_guild_as_read.set_label("Mark Server as Read");
|
||||
m_menu_view_mark_guild_as_read.add_accelerator("activate", m_accels, GDK_KEY_Escape, Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
|
||||
m_menu_view_channels.set_label("Channels");
|
||||
m_menu_view_channels.add_accelerator("activate", m_accels, GDK_KEY_L, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
|
||||
m_menu_view_channels.set_active(true);
|
||||
m_menu_view_members.set_label("Members");
|
||||
m_menu_view_members.add_accelerator("activate", m_accels, GDK_KEY_M, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
|
||||
m_menu_view_members.set_active(true);
|
||||
#ifdef WITH_LIBHANDY
|
||||
m_menu_view_go_back.set_label("Go Back");
|
||||
m_menu_view_go_forward.set_label("Go Forward");
|
||||
@ -275,6 +281,8 @@ void MainWindow::SetupMenu() {
|
||||
m_menu_view_sub.append(m_menu_view_pins);
|
||||
m_menu_view_sub.append(m_menu_view_threads);
|
||||
m_menu_view_sub.append(m_menu_view_mark_guild_as_read);
|
||||
m_menu_view_sub.append(m_menu_view_channels);
|
||||
m_menu_view_sub.append(m_menu_view_members);
|
||||
#ifdef WITH_LIBHANDY
|
||||
m_menu_view_sub.append(m_menu_view_go_back);
|
||||
m_menu_view_sub.append(m_menu_view_go_forward);
|
||||
@ -283,7 +291,25 @@ void MainWindow::SetupMenu() {
|
||||
m_menu_bar.append(m_menu_file);
|
||||
m_menu_bar.append(m_menu_discord);
|
||||
m_menu_bar.append(m_menu_view);
|
||||
|
||||
if (Abaddon::Get().GetSettings().AltMenu) {
|
||||
auto set_hide_cb = [this](Gtk::Menu &menu) {
|
||||
for (auto *child : menu.get_children()) {
|
||||
auto *item = dynamic_cast<Gtk::MenuItem *>(child);
|
||||
if (item != nullptr) {
|
||||
item->signal_activate().connect([this]() {
|
||||
m_menu_bar.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
set_hide_cb(m_menu_discord_sub);
|
||||
set_hide_cb(m_menu_file_sub);
|
||||
set_hide_cb(m_menu_view_sub);
|
||||
m_menu_bar.show_all_children();
|
||||
} else {
|
||||
m_menu_bar.show_all();
|
||||
}
|
||||
|
||||
m_menu_discord_connect.signal_activate().connect([this] {
|
||||
m_signal_action_connect.emit();
|
||||
@ -297,10 +323,6 @@ void MainWindow::SetupMenu() {
|
||||
m_signal_action_set_token.emit();
|
||||
});
|
||||
|
||||
m_menu_discord_join_guild.signal_activate().connect([this] {
|
||||
m_signal_action_join_guild.emit();
|
||||
});
|
||||
|
||||
m_menu_file_reload_css.signal_activate().connect([this] {
|
||||
m_signal_action_reload_css.emit();
|
||||
});
|
||||
@ -340,6 +362,14 @@ void MainWindow::SetupMenu() {
|
||||
}
|
||||
});
|
||||
|
||||
m_menu_view_channels.signal_activate().connect([this]() {
|
||||
m_channel_list.set_visible(m_menu_view_channels.get_active());
|
||||
});
|
||||
|
||||
m_menu_view_members.signal_activate().connect([this]() {
|
||||
m_members.GetRoot()->set_visible(m_menu_view_members.get_active());
|
||||
});
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
m_menu_view_go_back.signal_activate().connect([this] {
|
||||
GoBack();
|
||||
@ -383,10 +413,6 @@ MainWindow::type_signal_action_reload_css MainWindow::signal_action_reload_css()
|
||||
return m_signal_action_reload_css;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_join_guild MainWindow::signal_action_join_guild() {
|
||||
return m_signal_action_join_guild;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_set_status MainWindow::signal_action_set_status() {
|
||||
return m_signal_action_set_status;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ public:
|
||||
void UpdateChatReactionAdd(Snowflake id, const Glib::ustring ¶m);
|
||||
void UpdateChatReactionRemove(Snowflake id, const Glib::ustring ¶m);
|
||||
void UpdateMenus();
|
||||
void ToggleMenuVisibility();
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
void GoBack();
|
||||
@ -63,7 +64,6 @@ private:
|
||||
Gtk::MenuItem m_menu_discord_connect;
|
||||
Gtk::MenuItem m_menu_discord_disconnect;
|
||||
Gtk::MenuItem m_menu_discord_set_token;
|
||||
Gtk::MenuItem m_menu_discord_join_guild;
|
||||
Gtk::MenuItem m_menu_discord_set_status;
|
||||
Gtk::MenuItem m_menu_discord_add_recipient; // move me somewhere else some day
|
||||
void OnDiscordSubmenuPopup();
|
||||
@ -79,10 +79,13 @@ private:
|
||||
Gtk::MenuItem m_menu_view_pins;
|
||||
Gtk::MenuItem m_menu_view_threads;
|
||||
Gtk::MenuItem m_menu_view_mark_guild_as_read;
|
||||
Gtk::CheckMenuItem m_menu_view_channels;
|
||||
Gtk::CheckMenuItem m_menu_view_members;
|
||||
#ifdef WITH_LIBHANDY
|
||||
Gtk::MenuItem m_menu_view_go_back;
|
||||
Gtk::MenuItem m_menu_view_go_forward;
|
||||
#endif
|
||||
|
||||
void OnViewSubmenuPopup();
|
||||
|
||||
public:
|
||||
@ -90,7 +93,6 @@ public:
|
||||
typedef sigc::signal<void> type_signal_action_disconnect;
|
||||
typedef sigc::signal<void> type_signal_action_set_token;
|
||||
typedef sigc::signal<void> type_signal_action_reload_css;
|
||||
typedef sigc::signal<void> type_signal_action_join_guild;
|
||||
typedef sigc::signal<void> type_signal_action_set_status;
|
||||
// this should probably be removed
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_add_recipient; // channel id
|
||||
@ -101,7 +103,6 @@ public:
|
||||
type_signal_action_disconnect signal_action_disconnect();
|
||||
type_signal_action_set_token signal_action_set_token();
|
||||
type_signal_action_reload_css signal_action_reload_css();
|
||||
type_signal_action_join_guild signal_action_join_guild();
|
||||
type_signal_action_set_status signal_action_set_status();
|
||||
type_signal_action_add_recipient signal_action_add_recipient();
|
||||
type_signal_action_view_pins signal_action_view_pins();
|
||||
@ -112,7 +113,6 @@ private:
|
||||
type_signal_action_disconnect m_signal_action_disconnect;
|
||||
type_signal_action_set_token m_signal_action_set_token;
|
||||
type_signal_action_reload_css m_signal_action_reload_css;
|
||||
type_signal_action_join_guild m_signal_action_join_guild;
|
||||
type_signal_action_set_status m_signal_action_set_status;
|
||||
type_signal_action_add_recipient m_signal_action_add_recipient;
|
||||
type_signal_action_view_pins m_signal_action_view_pins;
|
||||
|
@ -41,13 +41,13 @@ ConnectionItem::ConnectionItem(const ConnectionData &conn)
|
||||
m_box.add(m_name);
|
||||
if (!url.empty()) {
|
||||
auto cb = [url](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) {
|
||||
LaunchBrowser(url);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
signal_button_press_event().connect(sigc::track_obj(cb, *this));
|
||||
signal_button_release_event().connect(sigc::track_obj(cb, *this));
|
||||
AddPointerCursor(*this);
|
||||
}
|
||||
m_overlay.add(m_box);
|
||||
|
@ -34,8 +34,8 @@ ProfileWindow::ProfileWindow(Snowflake user_id)
|
||||
|
||||
if (user.HasAvatar())
|
||||
AddPointerCursor(m_avatar_ev);
|
||||
m_avatar_ev.signal_button_press_event().connect([user](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
m_avatar_ev.signal_button_release_event().connect([user](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) {
|
||||
if (user.HasAnimatedAvatar())
|
||||
LaunchBrowser(user.GetAvatarURL("gif", "512"));
|
||||
else
|
||||
|
Loading…
Reference in New Issue
Block a user