From 860049fad5a2a4806e946bd6413d18b9bc0cc71a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 7 Aug 2022 20:15:47 +0000 Subject: [PATCH 01/28] Update README.md --- README.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 86df18d..5784a11 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,13 @@ Current features: * Thread support3 * 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.
+ +**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 +52,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,7 +61,7 @@ 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` @@ -93,6 +94,16 @@ On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/ `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 +112,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: From f60cea2216dd9677cb9105364cdaa778a0c187db Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 10 Aug 2022 00:03:04 -0400 Subject: [PATCH 02/28] control icon pos in css --- res/css/main.css | 2 ++ src/components/chatinput.cpp | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/res/css/main.css b/res/css/main.css index 9398d48..ace3b0b 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -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 */ diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 87892e0..2466965 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -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; From 96ec5bb6652b1a2eb3b6a11ed3468da190dad359 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 10 Aug 2022 23:20:27 -0400 Subject: [PATCH 03/28] fix removing roles from members (maybe) --- src/discord/store.cpp | 22 ++++++++++++++++++++++ src/discord/store.hpp | 1 + 2 files changed, 23 insertions(+) diff --git a/src/discord/store.cpp b/src/discord/store.cpp index 998bc41..f08f0c8 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -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; @@ -1881,6 +1889,20 @@ bool Store::CreateStatements() { return false; } + m_stmt_clr_member_roles = std::make_unique(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(m_db, R"( REPLACE INTO guild_emojis VALUES ( ?, ? diff --git a/src/discord/store.hpp b/src/discord/store.hpp index d863fa6..da97dd5 100644 --- a/src/discord/store.hpp +++ b/src/discord/store.hpp @@ -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); From a4c8a2290d5ba00950dc5883157869d9444dcae1 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 10 Aug 2022 23:29:00 -0400 Subject: [PATCH 04/28] remove ability to create dms --- src/abaddon.cpp | 20 ++++++-------------- src/discord/discord.cpp | 13 ------------- src/discord/discord.hpp | 1 - 3 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 14e43af..787265b 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -389,6 +389,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()); @@ -411,7 +412,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); @@ -419,7 +420,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(); @@ -467,7 +468,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")); @@ -578,18 +579,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() { diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index d94f3df..bb29d77 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -552,19 +552,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 &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(); - 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); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 6310296..6bcd2d5 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -116,7 +116,6 @@ public: 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 &callback); void CloseDM(Snowflake channel_id); std::optional FindDM(Snowflake user_id); // wont find group dms void AddReaction(Snowflake id, Glib::ustring param); From 31ca6d9fd24943279a1bb373b9e06e9bd1357fb4 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 10 Aug 2022 23:38:12 -0400 Subject: [PATCH 05/28] update capabilities and client build number --- src/discord/discord.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index bb29d77..c95c065 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2223,7 +2223,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 = ""; @@ -2236,7 +2236,7 @@ void DiscordClient::SendIdentify() { msg.Properties.ReferrerCurrent = ""; msg.Properties.ReferringDomainCurrent = ""; msg.Properties.ReleaseChannel = "stable"; - msg.Properties.ClientBuildNumber = 105691; + msg.Properties.ClientBuildNumber = 141021; msg.Properties.ClientEventSource = ""; msg.Presence.Status = "online"; msg.Presence.Since = 0; From baf96da80c1e4167fb638aca8a058cc9d9cfc4fd Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 11 Aug 2022 20:53:02 -0400 Subject: [PATCH 06/28] add copy url menu item to attachments (closes #96) also refactor menu popup to fit over entire message width --- src/components/chatmessage.cpp | 65 ++++++++++++++++++++-------------- src/components/chatmessage.hpp | 5 ++- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 4d6eec5..fd71fce 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -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(); } @@ -155,9 +153,9 @@ void ChatMessageItemContainer::AddClickHandler(Gtk::Widget *widget, const std::s widget->signal_button_press_event().connect([url](GdkEventButton *event) -> bool { if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) { LaunchBrowser(url); - return false; + return true; } - 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, "" + author->GetEscapedBoldName() + " started a thread: " + Glib::Markup::escape_text(data->Content) + ""); } @@ -297,12 +295,10 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedsComponent(const std::vectorProxyURL, *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); } } @@ -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(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(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; } @@ -956,7 +970,6 @@ void ChatMessageItemContainer::HandleChannelMentions(const Glib::RefPtrsignal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnClickChannel), false); HandleChannelMentions(tv->get_buffer()); } @@ -990,6 +1003,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(ev)); + return true; + } + + return false; +} + void ChatMessageItemContainer::on_link_menu_copy() { Gtk::Clipboard::get()->set_text(m_selected_link); } @@ -997,8 +1024,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 +1095,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(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) diff --git a/src/components/chatmessage.hpp b/src/components/chatmessage.hpp index 86c3fea..7851351 100644 --- a/src/components/chatmessage.hpp +++ b/src/components/chatmessage.hpp @@ -2,7 +2,7 @@ #include #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 &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, std::string> m_link_tagmap; std::map, Snowflake> m_channel_tagmap; - void AttachEventHandlers(Gtk::Widget &widget); - Gtk::EventBox *_ev; Gtk::Box m_main; Gtk::Label *m_attrib_label = nullptr; From dc28eae95a46c3079fcc76b3425ffa37844dd37d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 11 Aug 2022 23:37:39 -0400 Subject: [PATCH 07/28] spoof a bunch of headers like the web client --- src/abaddon.cpp | 8 +++++- src/discord/discord.cpp | 51 ++++++++++++++++++++++++++++++++++++++ src/discord/discord.hpp | 5 ++++ src/discord/httpclient.cpp | 21 +++++++++++++++- src/discord/httpclient.hpp | 5 ++++ src/discord/objects.cpp | 1 + src/discord/objects.hpp | 1 + src/http.cpp | 4 +++ src/http.hpp | 2 ++ 9 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 787265b..08e6857 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -647,12 +647,17 @@ void Abaddon::ActionJoinGuildDialog() { } 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; @@ -701,6 +706,7 @@ void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) { } m_main_window->UpdateMenus(); + m_discord.SetReferringChannel(id); } void Abaddon::ActionChatLoadHistory(Snowflake id) { diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index c95c065..4b6f6dd 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -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); @@ -1110,6 +1111,25 @@ 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::UpdateToken(const std::string &token) { if (!IsStarted()) { m_token = token; @@ -2245,6 +2265,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); @@ -2259,6 +2280,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() { } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 6bcd2d5..75b681a 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -201,6 +201,8 @@ public: void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot)> &callback); void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot &callback); + void SetReferringChannel(Snowflake id); + void UpdateToken(const std::string &token); void SetUserAgent(const std::string &agent); @@ -282,6 +284,9 @@ private: void SendIdentify(); void SendResume(); + void SetHeaders(); + void SetSuperPropertiesFromIdentity(const IdentifyMessage &identity); + void HandleSocketOpen(); void HandleSocketClose(uint16_t code); diff --git a/src/discord/httpclient.cpp b/src/discord/httpclient.cpp index f5b640c..cd699f0 100644 --- a/src/discord/httpclient.cpp +++ b/src/discord/httpclient.cpp @@ -19,11 +19,17 @@ 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::MakeDELETE(const std::string &path, const std::function &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"); @@ -40,8 +46,10 @@ 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 @@ -59,8 +67,10 @@ 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 @@ -78,7 +88,9 @@ 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"); @@ -98,8 +110,8 @@ void HTTPClient::MakeGET(const std::string &path, const std::function &cb) { CleanupFutures(); try { diff --git a/src/discord/httpclient.hpp b/src/discord/httpclient.hpp index 47b8a4d..841ce11 100644 --- a/src/discord/httpclient.hpp +++ b/src/discord/httpclient.hpp @@ -17,6 +17,8 @@ public: void SetUserAgent(std::string agent); void SetAuth(std::string auth); + void SetPersistentHeader(std::string name, std::string value); + void MakeDELETE(const std::string &path, const std::function &cb); void MakeGET(const std::string &path, const std::function &cb); void MakePATCH(const std::string &path, const std::string &payload, const std::function &cb); @@ -24,6 +26,8 @@ public: void MakePUT(const std::string &path, const std::string &payload, const std::function &cb); private: + void AddHeaders(http::request &r); + void OnResponse(const http::response_type &r, const std::function &cb); void CleanupFutures(); @@ -36,4 +40,5 @@ private: std::string m_api_base; std::string m_authorization; std::string m_agent; + std::unordered_map m_headers; }; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index f796000..e43e05a 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -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) { diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index db5fd76..9db9369 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -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); }; diff --git a/src/http.cpp b/src/http.cpp index 7338d39..80fb829 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -57,6 +57,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; +} + response request::execute() { if (m_curl == nullptr) { auto response = detail::make_response(m_url, EStatusCode::ClientErrorCURLInit); diff --git a/src/http.hpp b/src/http.hpp index 4220a6e..c44bce3 100644 --- a/src/http.hpp +++ b/src/http.hpp @@ -108,6 +108,8 @@ struct request { response execute(); + CURL *get_curl(); + private: void prepare(); From fac9f1ba58fae1bc12b57708e3908c74dcce9997 Mon Sep 17 00:00:00 2001 From: dragontamer8740 Date: Sat, 13 Aug 2022 22:59:08 -0400 Subject: [PATCH 08/28] Big-endian 'emojis.bin' decoding fix (#100) * Big-endian patch: fixes emojis.bin reading on big-endian systems (tested only on 32-bit PowerPC linux) --- CMakeLists.txt | 6 ++++++ src/emojis.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 44ca621..1e28791 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,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 diff --git a/src/emojis.cpp b/src/emojis.cpp index 25516d3..dc28310 100644 --- a/src/emojis.cpp +++ b/src/emojis.cpp @@ -1,6 +1,23 @@ #include "emojis.hpp" #include #include +#include + +#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 +28,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 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 +51,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) From 243e48e60955e60b85f901a5aac3010984b7a32f Mon Sep 17 00:00:00 2001 From: dragontamer8740 Date: Sat, 13 Aug 2022 23:36:56 -0400 Subject: [PATCH 09/28] remove unnecessary cstdio include (#101) --- src/emojis.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/emojis.cpp b/src/emojis.cpp index dc28310..1952a7f 100644 --- a/src/emojis.cpp +++ b/src/emojis.cpp @@ -1,7 +1,6 @@ #include "emojis.hpp" #include #include -#include #ifdef ABADDON_IS_BIG_ENDIAN /* Allows processing emojis.bin correctly on big-endian systems. */ From d99f16f82d0e83a4e35024ec2596e3c42f039ec1 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 14 Aug 2022 00:02:19 -0400 Subject: [PATCH 10/28] fix ci run on mac --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bea5a2e..129a426 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,6 +117,7 @@ jobs: run: | brew install gtkmm3 brew install nlohmann-json + brew install jpeg - name: Build uses: lukka/run-cmake@v3 From 039cc7458d22e6222f1056990b8f15b63bcf819d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 14 Aug 2022 22:32:33 -0400 Subject: [PATCH 11/28] copy over new dlls for win build --- .github/workflows/ci.yml | 1 + ci/msys-deps.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 129a426..693b9b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/ci/msys-deps.txt b/ci/msys-deps.txt index f628d09..66a7896 100644 --- a/ci/msys-deps.txt +++ b/ci/msys-deps.txt @@ -56,3 +56,4 @@ /bin/libwinpthread-1.dll /bin/libzstd.dll /bin/zlib1.dll +../usr/bin/msys-2.0.dll From c683ef9ad94480c9f97b5e05dddc3f2ef370c0f6 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 15 Aug 2022 23:24:40 -0400 Subject: [PATCH 12/28] update msys-deps again --- ci/msys-deps.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ci/msys-deps.txt b/ci/msys-deps.txt index 66a7896..864a5e5 100644 --- a/ci/msys-deps.txt +++ b/ci/msys-deps.txt @@ -14,7 +14,6 @@ /bin/libdeflate.dll /bin/libepoxy-0.dll /bin/libexpat-1.dll -/bin/libffi-7.dll /bin/libfontconfig-1.dll /bin/libfreetype-6.dll /bin/libfribidi-0.dll @@ -56,4 +55,4 @@ /bin/libwinpthread-1.dll /bin/libzstd.dll /bin/zlib1.dll -../usr/bin/msys-2.0.dll +/../usr/bin/msys-2.0.dll From 14602a73842f0c57eda857503a3539a9d9aa4089 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 16 Aug 2022 17:34:55 -0400 Subject: [PATCH 13/28] make cmake unity build work --- src/abaddon.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/abaddon.hpp b/src/abaddon.hpp index c267269..3cd04e9 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -1,3 +1,4 @@ +#pragma once #include #include #include From 32fc7def7c5936e9a4c8c4d9b36b9a7570ca8664 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 17 Aug 2022 01:42:30 -0400 Subject: [PATCH 14/28] fetch cookies and build number on startup --- src/abaddon.cpp | 41 ++++++++++++ src/abaddon.hpp | 2 + src/dialogs/confirm.cpp | 4 ++ src/dialogs/confirm.hpp | 1 + src/discord/discord.cpp | 10 ++- src/discord/discord.hpp | 5 ++ src/discord/httpclient.cpp | 30 ++------- src/discord/httpclient.hpp | 2 + src/http.cpp | 6 ++ src/startup.cpp | 126 +++++++++++++++++++++++++++++++++++++ src/startup.hpp | 25 ++++++++ 11 files changed, 226 insertions(+), 26 deletions(-) create mode 100644 src/startup.cpp create mode 100644 src/startup.hpp diff --git a/src/abaddon.cpp b/src/abaddon.cpp index e296aa4..343dff7 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -17,6 +17,7 @@ #include "windows/profilewindow.hpp" #include "windows/pinnedwindow.hpp" #include "windows/threadswindow.hpp" +#include "startup.hpp" #ifdef WITH_LIBHANDY #include @@ -253,6 +254,9 @@ int Abaddon::StartGTK() { m_main_window->UpdateMenus(); m_main_window->show(); + + RunFirstTimeDiscordStartup(); + return m_gtk_app->run(*m_main_window); } @@ -435,6 +439,43 @@ 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 cookie; + std::optional 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(); + } + }); +} + void Abaddon::ShowGuildVerificationGateDialog(Snowflake guild_id) { VerificationGateDialog dlg(*m_main_window, guild_id); if (dlg.run() == Gtk::RESPONSE_OK) { diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 3cd04e9..ab80c46 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -94,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 &msgs); diff --git a/src/dialogs/confirm.cpp b/src/dialogs/confirm.cpp index 39d8971..8e606df 100644 --- a/src/dialogs/confirm.cpp +++ b/src/dialogs/confirm.cpp @@ -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); +} diff --git a/src/dialogs/confirm.hpp b/src/dialogs/confirm.hpp index df1e185..84a089c 100644 --- a/src/dialogs/confirm.hpp +++ b/src/dialogs/confirm.hpp @@ -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; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 48f08d6..561b25b 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1188,6 +1188,14 @@ void DiscordClient::SetReferringChannel(Snowflake id) { } } +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; @@ -2314,7 +2322,7 @@ void DiscordClient::SendIdentify() { msg.Properties.ReferrerCurrent = ""; msg.Properties.ReferringDomainCurrent = ""; msg.Properties.ReleaseChannel = "stable"; - msg.Properties.ClientBuildNumber = 141021; + msg.Properties.ClientBuildNumber = m_build_number; msg.Properties.ClientEventSource = ""; msg.Presence.Status = "online"; msg.Presence.Since = 0; diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index b1b623b..70c2d82 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -206,6 +206,9 @@ public: 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); @@ -303,6 +306,8 @@ private: std::string m_token; + uint32_t m_build_number = 142000; + void AddUserToGuild(Snowflake user_id, Snowflake guild_id); std::map> m_guild_to_users; std::map> m_guild_to_channels; diff --git a/src/discord/httpclient.cpp b/src/discord/httpclient.cpp index d13246d..cc0bb93 100644 --- a/src/discord/httpclient.cpp +++ b/src/discord/httpclient.cpp @@ -2,7 +2,6 @@ #include -//#define USE_LOCAL_PROXY HTTPClient::HTTPClient() { m_dispatcher.connect(sigc::mem_fun(*this, &HTTPClient::RunCallbacks)); } @@ -23,6 +22,10 @@ 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 &cb) { printf("DELETE %s\n", path.c_str()); m_futures.push_back(std::async(std::launch::async, [this, path, cb] { @@ -31,10 +34,6 @@ void HTTPClient::MakeDELETE(const std::string &path, const std::function &cb); void MakeGET(const std::string &path, const std::function &cb); @@ -44,4 +45,5 @@ private: std::string m_authorization; std::string m_agent; std::unordered_map m_headers; + std::string m_cookie; }; diff --git a/src/http.cpp b/src/http.cpp index 0fae39f..f0471a1 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -2,6 +2,8 @@ #include +// #define USE_LOCAL_PROXY + namespace http { request::request(EMethod method, std::string url) : m_url(std::move(url)) { @@ -147,6 +149,10 @@ 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()); diff --git a/src/startup.cpp b/src/startup.cpp new file mode 100644 index 0000000..baedf76 --- /dev/null +++ b/src/startup.cpp @@ -0,0 +1,126 @@ +#include "startup.hpp" +#include "abaddon.hpp" +#include +#include + +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 DiscordStartupDialog::GetCookie() const { + return m_cookie; +} + +std::optional DiscordStartupDialog::GetBuildNumber() const { + return m_build_number; +} + +// good enough +std::optional> ParseCookie(const Glib::ustring &str) { + auto regex = Glib::Regex::create("\\t"); + const std::vector split = regex->split(str); + if (split.size() < 7) return {}; + + return { { split[5], split[6] } }; +} + +std::optional 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 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::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>(); + *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); +} diff --git a/src/startup.hpp b/src/startup.hpp new file mode 100644 index 0000000..ba1459d --- /dev/null +++ b/src/startup.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include +#include + +// fetch cookies, build number async + +class DiscordStartupDialog : public Gtk::MessageDialog { +public: + DiscordStartupDialog(Gtk::Window &window); + + [[nodiscard]] std::optional GetCookie() const; + [[nodiscard]] std::optional GetBuildNumber() const; + +private: + void RunAsync(); + + void DispatchCallback(); + + Glib::Dispatcher m_dispatcher; + + std::optional m_cookie; + std::optional m_build_number; +}; From 348c1cb965cc55bee2b7b008e8a56da78b91a98f Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 21 Aug 2022 14:10:28 -0400 Subject: [PATCH 15/28] remove curl error buffer it was useless anyways --- src/http.cpp | 4 ---- src/http.hpp | 1 - 2 files changed, 5 deletions(-) diff --git a/src/http.cpp b/src/http.cpp index f0471a1..f0bb85b 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -36,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)) { @@ -159,8 +158,6 @@ response request::execute() { 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) @@ -170,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; } diff --git a/src/http.hpp b/src/http.hpp index 5bf3c69..63b6e38 100644 --- a/src/http.hpp +++ b/src/http.hpp @@ -131,7 +131,6 @@ private: std::string m_url; const char *m_method; curl_slist *m_header_list = nullptr; - std::array m_error_buf = { 0 }; curl_mime *m_form = nullptr; std::function m_progress_callback; From a78fdd386f93db366d7327aa736624ad8bc1aa6f Mon Sep 17 00:00:00 2001 From: KnightMurloc <44520059+KnightMurloc@users.noreply.github.com> Date: Fri, 9 Sep 2022 12:03:55 +0700 Subject: [PATCH 16/28] add opt-in hide to system tray icon (#99) --- src/abaddon.cpp | 29 +++++++++++++++++++++++++++++ src/abaddon.hpp | 9 ++++++++- src/settings.cpp | 2 ++ src/settings.hpp | 2 ++ src/windows/mainwindow.cpp | 2 +- src/windows/mainwindow.hpp | 1 + 6 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 343dff7..02dcd08 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -248,11 +248,25 @@ 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(); + m_tray_exit = Gtk::make_managed("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(); @@ -937,6 +951,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(); diff --git a/src/abaddon.hpp b/src/abaddon.hpp index ab80c46..b067324 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -117,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(); @@ -124,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; @@ -142,5 +148,6 @@ private: Glib::RefPtr m_gtk_app; Glib::RefPtr m_css_provider; Glib::RefPtr m_css_low_provider; // registered with a lower priority to allow better customization - std::unique_ptr m_main_window; // wah wah cant create a gtkstylecontext fuck you + Glib::RefPtr m_tray; + std::unique_ptr m_main_window; // wah wah cant create a gtkstylecontext fuck you }; diff --git a/src/settings.cpp b/src/settings.cpp index 242bd7c..dd1fe83 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -48,6 +48,7 @@ void SettingsManager::ReadSettings() { SMBOOL("gui", "save_state", SaveState); SMBOOL("gui", "stock_emojis", ShowStockEmojis); SMBOOL("gui", "unreads", Unreads); + SMBOOL("gui", "hide_to_tray", HideToTray); SMINT("http", "concurrent", CacheHTTPConcurrency); SMSTR("http", "user_agent", UserAgent); SMSTR("style", "expandercolor", ChannelsExpanderColor); @@ -101,6 +102,7 @@ void SettingsManager::Close() { SMBOOL("gui", "save_state", SaveState); SMBOOL("gui", "stock_emojis", ShowStockEmojis); SMBOOL("gui", "unreads", Unreads); + SMBOOL("gui", "hide_to_tray", HideToTray); SMINT("http", "concurrent", CacheHTTPConcurrency); SMSTR("http", "user_agent", UserAgent); SMSTR("style", "expandercolor", ChannelsExpanderColor); diff --git a/src/settings.hpp b/src/settings.hpp index 3c9aebb..7f5e015 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -28,6 +28,8 @@ public: #endif bool Unreads { true }; + bool HideToTray { false }; + // [http] int CacheHTTPConcurrency { 20 }; std::string UserAgent { "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" }; diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 17edfa3..7f4395c 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -401,4 +401,4 @@ MainWindow::type_signal_action_view_pins MainWindow::signal_action_view_pins() { MainWindow::type_signal_action_view_threads MainWindow::signal_action_view_threads() { return m_signal_action_view_threads; -} +} \ No newline at end of file diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index b5b6fc1..534df1b 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -83,6 +83,7 @@ private: Gtk::MenuItem m_menu_view_go_back; Gtk::MenuItem m_menu_view_go_forward; #endif + void OnViewSubmenuPopup(); public: From f3e5dcbe6521413344091004641feaa2906efb0f Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 9 Sep 2022 02:40:33 -0400 Subject: [PATCH 17/28] fix some potential crashes because of optionals --- src/components/chatmessage.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 1aca81d..89c924e 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -655,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); - return "Color) + "\">" + author->GetEscapedString() + ""; + if (author.has_value()) { + return "Color) + "\">" + author->GetEscapedString() + ""; + } } } } const auto author = discord.GetUser(author_id); - return author->GetEscapedBoldString(); + if (author.has_value()) { + return author->GetEscapedBoldString(); + } + + return "Unknown User"; }; // if the message wasnt fetched from store it might have an un-fetched reference @@ -673,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 /" + Glib::Markup::escape_text(data.Interaction->Name) + ""); + } else if (const auto user = discord.GetUser(data.Interaction->User.ID); user.has_value()) { + lbl->set_markup(user->GetEscapedBoldString()); } else { - lbl->set_markup(user.GetEscapedBoldString()); + lbl->set_markup("Unknown User"); } } else if (referenced_message.has_value()) { if (referenced_message.value() == nullptr) { From 84eb56d6b1f6d0fee4b909e3a96c66b6ad1311f7 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 9 Sep 2022 02:51:59 -0400 Subject: [PATCH 18/28] store user from interaction even if member is not present --- src/discord/discord.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 561b25b..e1b7a48 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2441,9 +2441,11 @@ 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); - m_store.SetGuildMember(*msg.GuildID, msg.Interaction->User.ID, *msg.Interaction->Member); + if (msg.Interaction->Member.has_value()) { + m_store.SetGuildMember(*msg.GuildID, msg.Interaction->User.ID, *msg.Interaction->Member); + } } m_store.EndTransaction(); From 3027e00905b19282a4f501a26f7a4f71bc6940ea Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 25 Sep 2022 01:44:09 -0400 Subject: [PATCH 19/28] open browser on mouse release (fixes #108) --- src/components/chatmessage.cpp | 8 ++++---- src/windows/guildsettings/infopane.cpp | 7 +++---- src/windows/profile/userinfopane.cpp | 4 ++-- src/windows/profilewindow.cpp | 4 ++-- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 89c924e..3afdf9f 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -150,8 +150,8 @@ 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 true; } @@ -357,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; } diff --git a/src/windows/guildsettings/infopane.cpp b/src/windows/guildsettings/infopane.cpp index 578aaac..a27c1a8 100644 --- a/src/windows/guildsettings/infopane.cpp +++ b/src/windows/guildsettings/infopane.cpp @@ -56,10 +56,9 @@ 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) - LaunchBrowser(guild_icon_url); + 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; }); diff --git a/src/windows/profile/userinfopane.cpp b/src/windows/profile/userinfopane.cpp index a17dbff..b62da93 100644 --- a/src/windows/profile/userinfopane.cpp +++ b/src/windows/profile/userinfopane.cpp @@ -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); diff --git a/src/windows/profilewindow.cpp b/src/windows/profilewindow.cpp index aff98c5..d73731d 100644 --- a/src/windows/profilewindow.cpp +++ b/src/windows/profilewindow.cpp @@ -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 From 7e851685768d26143e7464fc4acb2d2d5a621eac Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 6 Oct 2022 02:34:45 -0400 Subject: [PATCH 20/28] remove a bunch of unnecessary fields from user settings --- src/discord/usersettings.cpp | 28 ------------------ src/discord/usersettings.hpp | 57 ++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 56 deletions(-) diff --git a/src/discord/usersettings.cpp b/src/discord/usersettings.cpp index e4ab41a..8f57ee3 100644 --- a/src/discord/usersettings.cpp +++ b/src/discord/usersettings.cpp @@ -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); } diff --git a/src/discord/usersettings.hpp b/src/discord/usersettings.hpp index 2baf61e..2631c45 100644 --- a/src/discord/usersettings.hpp +++ b/src/discord/usersettings.hpp @@ -13,35 +13,36 @@ struct UserSettingsGuildFoldersEntry { }; struct UserSettings { - int TimezoneOffset; // - std::string Theme; // - bool AreStreamNotificationsEnabled; // - std::string Status; // - bool ShouldShowCurrentGame; // - // std::vector RestrictedGuilds; // - bool ShouldRenderReactions; // - bool ShouldRenderEmbeds; // - bool IsNativePhoneIntegrationEnabled; // - bool ShouldMessageDisplayCompact; // - std::string Locale; // - bool ShouldInlineEmbedMedia; // - bool ShouldInlineAttachmentMedia; // - std::vector GuildPositions; // deprecated? - std::vector GuildFolders; // - bool ShouldGIFAutoplay; // - // Unknown FriendSourceFlags; // - int ExplicitContentFilter; // - bool IsTTSCommandEnabled; // - bool ShouldDisableGamesTab; // - bool DeveloperMode; // - bool ShouldDetectPlatformAccounts; // - bool AreDefaultGuildsRestricted; // + std::vector GuildFolders; + /* + int TimezoneOffset; + std::string Theme; + bool AreStreamNotificationsEnabled; + std::string Status; + bool ShouldShowCurrentGame; + // std::vector RestrictedGuilds; + bool ShouldRenderReactions; + bool ShouldRenderEmbeds; + bool IsNativePhoneIntegrationEnabled; + bool ShouldMessageDisplayCompact; + std::string Locale; + bool ShouldInlineEmbedMedia; + bool ShouldInlineAttachmentMedia; + std::vector GuildPositions; // deprecated? + 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); }; From 0a34c04b44d3f500317a319d497ce2064fdd852f Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 6 Oct 2022 03:08:54 -0400 Subject: [PATCH 21/28] remove ability to join guilds because 1. joining a guild seems to often require captchas now which are never going to be supported and 2. joining guilds is one of the things that upsets discords spam filter the most, so it kinda makes sense to remove anyways just like open dm --- src/abaddon.cpp | 11 ----- src/dialogs/joinguild.cpp | 97 -------------------------------------- src/dialogs/joinguild.hpp | 31 ------------ src/discord/discord.cpp | 4 -- src/discord/discord.hpp | 1 - src/windows/mainwindow.cpp | 12 ----- src/windows/mainwindow.hpp | 4 -- 7 files changed, 160 deletions(-) delete mode 100644 src/dialogs/joinguild.cpp delete mode 100644 src/dialogs/joinguild.hpp diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 02dcd08..ec567e7 100644 --- a/src/abaddon.cpp +++ b/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" @@ -229,7 +228,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)); @@ -693,15 +691,6 @@ 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()) { m_discord.SetReferringChannel(Snowflake::Invalid); diff --git a/src/dialogs/joinguild.cpp b/src/dialogs/joinguild.cpp deleted file mode 100644 index 14fab53..0000000 --- a/src/dialogs/joinguild.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "joinguild.hpp" -#include "abaddon.hpp" -#include -#include - -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 &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; -} diff --git a/src/dialogs/joinguild.hpp b/src/dialogs/joinguild.hpp deleted file mode 100644 index ba061f3..0000000 --- a/src/dialogs/joinguild.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include -#include -#include - -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 m_last_req_time; - bool on_idle_slot(); - -private: - std::string m_code; -}; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index e1b7a48..2808e17 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -573,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) {}); } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 70c2d82..c2bea7d 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -113,7 +113,6 @@ 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 diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 7f4395c..07a7f17 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -195,7 +195,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 +237,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); @@ -297,10 +293,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(); }); @@ -383,10 +375,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; } diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index 534df1b..b013e32 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -63,7 +63,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(); @@ -91,7 +90,6 @@ public: typedef sigc::signal type_signal_action_disconnect; typedef sigc::signal type_signal_action_set_token; typedef sigc::signal type_signal_action_reload_css; - typedef sigc::signal type_signal_action_join_guild; typedef sigc::signal type_signal_action_set_status; // this should probably be removed typedef sigc::signal type_signal_action_add_recipient; // channel id @@ -102,7 +100,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(); @@ -113,7 +110,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; From 1767575728b21d7825423f0a1e3854932ed80864 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 9 Oct 2022 17:31:15 -0400 Subject: [PATCH 22/28] make CURLOPT_ACCEPT_ENCODING automatic --- src/discord/httpclient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/discord/httpclient.cpp b/src/discord/httpclient.cpp index cc0bb93..37436ee 100644 --- a/src/discord/httpclient.cpp +++ b/src/discord/httpclient.cpp @@ -143,7 +143,7 @@ void HTTPClient::AddHeaders(http::request &r) { 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, "gzip, deflate, br"); + curl_easy_setopt(r.get_curl(), CURLOPT_ACCEPT_ENCODING, ""); } void HTTPClient::OnResponse(const http::response_type &r, const std::function &cb) { From cd900cdfee2014650d5b36981d0610fbe1dc06dd Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 13 Oct 2022 17:03:20 -0400 Subject: [PATCH 23/28] update msys dependencies --- ci/msys-deps.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/msys-deps.txt b/ci/msys-deps.txt index 864a5e5..c2e626e 100644 --- a/ci/msys-deps.txt +++ b/ci/msys-deps.txt @@ -14,6 +14,7 @@ /bin/libdeflate.dll /bin/libepoxy-0.dll /bin/libexpat-1.dll +/bin/libffi-8.dll /bin/libfontconfig-1.dll /bin/libfreetype-6.dll /bin/libfribidi-0.dll @@ -41,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 From ccb82c1676326107133e63b7822dfd0b87afd0f2 Mon Sep 17 00:00:00 2001 From: Addison Snelling Date: Tue, 18 Oct 2022 00:48:45 -0500 Subject: [PATCH 24/28] readme: list exact depedency package names for Ubuntu (#109) Co-authored-by: Addison Snelling --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5784a11..a059990 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,11 @@ the result of fundamental issues with Discord's thread implementation. #### 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 ..` From 772598996c24d570cb74686c4d888d6f1aa070ad Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 23 Oct 2022 02:56:07 +0000 Subject: [PATCH 25/28] Add option to hide the menu bar behind alt key (#115) --- README.md | 16 +++++++++++++--- src/abaddon.cpp | 20 +++++++++++++++----- src/settings.cpp | 2 ++ src/settings.hpp | 2 +- src/windows/mainwindow.cpp | 24 +++++++++++++++++++++++- src/windows/mainwindow.hpp | 1 + 6 files changed, 55 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a059990..555b129 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,15 @@ Current features: * Thread support3 * Animated avatars, server icons, emojis (can be turned off) -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 +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.
-**See [here](#the-spam-filter)** for things you might want to avoid if you are worried about being caught in the spam filter. +**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 @@ -99,7 +101,12 @@ 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: +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 @@ -252,6 +259,9 @@ For example, memory_db would be set by adding `memory_db = true` under the line 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 +* 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 diff --git a/src/abaddon.cpp b/src/abaddon.cpp index ec567e7..630e584 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -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(window) != static_cast(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(main_window))) return; gtk_main_do_event(event); } -#endif int Abaddon::StartGTK() { m_gtk_app = Gtk::Application::create("com.github.uowuo.abaddon"); diff --git a/src/settings.cpp b/src/settings.cpp index dd1fe83..34b3dc0 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -48,6 +48,7 @@ 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); @@ -102,6 +103,7 @@ 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); diff --git a/src/settings.hpp b/src/settings.hpp index 7f5e015..4ab512e 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -27,7 +27,7 @@ public: bool ShowStockEmojis { true }; #endif bool Unreads { true }; - + bool AltMenu { false }; bool HideToTray { false }; // [http] diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 07a7f17..20da46b 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -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(); @@ -279,7 +283,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); - m_menu_bar.show_all(); + + if (Abaddon::Get().GetSettings().AltMenu) { + auto set_hide_cb = [this](Gtk::Menu &menu) { + for (auto *child : menu.get_children()) { + auto *item = dynamic_cast(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(); diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index b013e32..6e95b72 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -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(); From 64245bf7457a81eed063901ad8e5ce55fdc2ca0c Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 23 Oct 2022 18:23:11 -0400 Subject: [PATCH 26/28] add option to autoconnect (closes #114) --- src/abaddon.cpp | 5 +++++ src/settings.cpp | 2 ++ src/settings.hpp | 1 + 3 files changed, 8 insertions(+) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 630e584..d759426 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -495,6 +495,11 @@ void Abaddon::RunFirstTimeDiscordStartup() { confirm.SetAcceptOnly(true); confirm.run(); } + + // autoconnect + if (cookie.has_value() && build_number.has_value() && GetSettings().Autoconnect && !GetDiscordToken().empty()) { + ActionConnect(); + } }); } diff --git a/src/settings.cpp b/src/settings.cpp index 34b3dc0..0019a00 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -39,6 +39,7 @@ void SettingsManager::ReadSettings() { SMSTR("discord", "token", DiscordToken); 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); @@ -94,6 +95,7 @@ void SettingsManager::Close() { SMSTR("discord", "token", DiscordToken); 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); diff --git a/src/settings.hpp b/src/settings.hpp index 4ab512e..9d32d2e 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -12,6 +12,7 @@ public: std::string DiscordToken; bool UseMemoryDB { false }; bool Prefetch { false }; + bool Autoconnect { false }; // [gui] std::string MainCSS { "main.css" }; From 2a9f49a1485a145668923a550347af8890e88bf0 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 3 Nov 2022 00:45:31 -0400 Subject: [PATCH 27/28] add menu item + shortcuts to hide channel and member lists (closes #118) --- src/windows/mainwindow.cpp | 16 ++++++++++++++++ src/windows/mainwindow.hpp | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 20da46b..8a85d49 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -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); @@ -354,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(); diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index 6e95b72..78e0115 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -79,6 +79,8 @@ 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; From c5807a3463aaefc89e2432730b997437305af59a Mon Sep 17 00:00:00 2001 From: abdalrzag eisa <53495583+Senpai-10@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:47:16 +0300 Subject: [PATCH 28/28] Make README.md more readable (#120) * More noticeable warnings * Make CSS selectors stand out more from their description * Make Settings options stand out more from their description, and make the default value easy to see --- README.md | 198 +++++++++++++++++++++++++++--------------------------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 555b129..4f56568 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ 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` @@ -135,90 +135,90 @@ spam filter's wrath: #### 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 @@ -234,46 +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 -* 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 +* **`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