diff options
-rw-r--r-- | README.md | 22 | ||||
-rw-r--r-- | src/abaddon.cpp | 28 | ||||
-rw-r--r-- | src/components/chatmessage.cpp | 65 | ||||
-rw-r--r-- | src/components/chatmessage.hpp | 5 | ||||
-rw-r--r-- | src/discord/discord.cpp | 68 | ||||
-rw-r--r-- | src/discord/discord.hpp | 6 | ||||
-rw-r--r-- | src/discord/httpclient.cpp | 21 | ||||
-rw-r--r-- | src/discord/httpclient.hpp | 5 | ||||
-rw-r--r-- | src/discord/objects.cpp | 1 | ||||
-rw-r--r-- | src/discord/objects.hpp | 1 | ||||
-rw-r--r-- | src/discord/store.cpp | 22 | ||||
-rw-r--r-- | src/discord/store.hpp | 1 | ||||
-rw-r--r-- | src/http.cpp | 4 | ||||
-rw-r--r-- | src/http.hpp | 2 |
14 files changed, 185 insertions, 66 deletions
@@ -25,13 +25,13 @@ Current features: * Thread support<sup>3</sup> * Animated avatars, server icons, emojis (can be turned off) -1 - Abaddon tries its best to make Discord think it's a legitimate web client. Some of the things done to do this +1 - Abaddon tries its best (though is not perfect) to make Discord think it's a legitimate web client. Some of the things done to do this include: using a browser user agent, sending the same IDENTIFY message that the official web client does, using API v9 endpoints in all cases, and not using endpoints the web client does not normally use. There are still a few smaller inconsistencies, however. For example the web client sends lots of telemetry via the `/science` endpoint (uBlock origin -stops this) as well as in the headers of all requests. **In any case,** you should use an official client for joining -servers, sending new DMs, or managing your friends list if you are afraid of being caught in Discord's spam filters -(unlikely). +stops this) as well as in the headers of all requests.<br> + +**See [here](#the-spam-filter)** for things you might want to avoid if you are worried about being caught in the spam filter. 2 - Unicode emojis are substituted manually as opposed to rendered by GTK on non-Windows platforms. This can be changed with the `stock_emojis` setting as shown at the bottom of this README. A CBDT-based font using Twemoji is provided to @@ -52,6 +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: diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 563a42c..e296aa4 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -390,6 +390,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_ for (const auto child : m_user_menu_roles_submenu->get_children()) delete child; + if (guild.has_value() && user.has_value()) { const auto roles = user->GetSortedRoles(); m_user_menu_roles->set_visible(!roles.empty()); @@ -412,7 +413,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_ if (me == id) { m_user_menu_ban->set_visible(false); m_user_menu_kick->set_visible(false); - m_user_menu_open_dm->set_visible(false); + m_user_menu_open_dm->set_sensitive(false); } else { const bool has_kick = m_discord.HasGuildPermission(me, guild_id, Permission::KICK_MEMBERS); const bool has_ban = m_discord.HasGuildPermission(me, guild_id, Permission::BAN_MEMBERS); @@ -420,7 +421,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(); @@ -468,7 +469,7 @@ void Abaddon::SetupUserMenu() { m_user_menu_ban = Gtk::manage(new Gtk::MenuItem("Ban")); m_user_menu_kick = Gtk::manage(new Gtk::MenuItem("Kick")); m_user_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID")); - m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Open DM")); + m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Go to DM")); m_user_menu_roles = Gtk::manage(new Gtk::MenuItem("Roles")); m_user_menu_info = Gtk::manage(new Gtk::MenuItem("View Profile")); m_user_menu_remove_recipient = Gtk::manage(new Gtk::MenuItem("Remove From Group")); @@ -579,18 +580,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() { @@ -656,12 +648,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; @@ -710,6 +707,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/components/chatmessage.cpp b/src/components/chatmessage.cpp index 863349f..1aca81d 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, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span><b>" + Glib::Markup::escape_text(data->Content) + "</b></i>"); } @@ -297,12 +295,10 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedsComponent(const std::vector<E if (IsEmbedImageOnly(embed)) { auto *widget = CreateImageComponent(*embed.Thumbnail->ProxyURL, *embed.Thumbnail->URL, *embed.Thumbnail->Width, *embed.Thumbnail->Height); widget->show(); - AttachEventHandlers(*widget); box->add(*widget); } else { auto *widget = CreateEmbedComponent(embed); widget->show(); - AttachEventHandlers(*widget); box->add(*widget); } } @@ -493,12 +489,22 @@ Gtk::Widget *ChatMessageItemContainer::CreateImageComponent(const std::string &p Gtk::EventBox *ev = Gtk::manage(new Gtk::EventBox); Gtk::Image *widget = Gtk::manage(new LazyImage(proxy_url, w, h, false)); ev->add(*widget); + ev->set_halign(Gtk::ALIGN_START); widget->set_halign(Gtk::ALIGN_START); widget->set_size_request(w, h); - AttachEventHandlers(*ev); AddClickHandler(ev, url); + const auto on_button_press_event = [this, url](GdkEventButton *e) -> bool { + if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) { + m_selected_link = url; + m_link_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(e)); + return true; + } + return false; + }; + ev->signal_button_press_event().connect(on_button_press_event, false); + return ev; } @@ -510,9 +516,18 @@ Gtk::Widget *ChatMessageItemContainer::CreateAttachmentComponent(const Attachmen ev->get_style_context()->add_class("message-attachment-box"); ev->add(*btn); - AttachEventHandlers(*ev); AddClickHandler(ev, data.URL); + const auto on_button_press_event = [this, url = data.URL](GdkEventButton *e) -> bool { + if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) { + m_selected_link = url; + m_link_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(e)); + return true; + } + return false; + }; + ev->signal_button_press_event().connect(on_button_press_event, false); + return ev; } @@ -534,7 +549,6 @@ Gtk::Widget *ChatMessageItemContainer::CreateStickersComponent(const std::vector box->show(); - AttachEventHandlers(*box); return box; } @@ -956,7 +970,6 @@ void ChatMessageItemContainer::HandleChannelMentions(const Glib::RefPtr<Gtk::Tex } void ChatMessageItemContainer::HandleChannelMentions(Gtk::TextView *tv) { - tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnClickChannel), false); HandleChannelMentions(tv->get_buffer()); } @@ -990,6 +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<GdkEvent *>(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<GdkEvent *>(e)); // illegal ooooooh - return true; - } - - return false; - }; - widget.signal_button_press_event().connect(on_button_press_event, false); -} - ChatMessageHeader::ChatMessageHeader(const Message &data) : m_main_box(Gtk::ORIENTATION_HORIZONTAL) , m_content_box(Gtk::ORIENTATION_VERTICAL) 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 <gtkmm.h> #include "discord/discord.hpp" -class ChatMessageItemContainer : public Gtk::Box { +class ChatMessageItemContainer : public Gtk::EventBox { public: Snowflake ID; Snowflake ChannelID; @@ -44,6 +44,7 @@ protected: void HandleChannelMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf); void HandleChannelMentions(Gtk::TextView *tv); bool OnClickChannel(GdkEventButton *ev); + bool OnTextViewButtonPress(GdkEventButton *ev); // reused for images and links Gtk::Menu m_link_menu; @@ -57,8 +58,6 @@ protected: std::map<Glib::RefPtr<Gtk::TextTag>, std::string> m_link_tagmap; std::map<Glib::RefPtr<Gtk::TextTag>, Snowflake> m_channel_tagmap; - void AttachEventHandlers(Gtk::Widget &widget); - Gtk::EventBox *_ev; Gtk::Box m_main; Gtk::Label *m_attrib_label = nullptr; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index d2c8edd..48f08d6 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); @@ -610,19 +611,6 @@ void DiscordClient::UpdateStatus(PresenceStatus status, bool is_afk, const Activ m_signal_presence_update.emit(GetUserData(), status); } -void DiscordClient::CreateDM(Snowflake user_id, const sigc::slot<void(DiscordError code, Snowflake channel_id)> &callback) { - CreateDMObject obj; - obj.Recipients.push_back(user_id); - m_http.MakePOST("/users/@me/channels", nlohmann::json(obj).dump(), [callback](const http::response &response) { - if (!CheckCode(response)) { - callback(DiscordError::NONE, Snowflake::Invalid); - return; - } - auto channel = nlohmann::json::parse(response.text).get<ChannelData>(); - callback(GetCodeFromResponse(response), channel.ID); - }); -} - void DiscordClient::CloseDM(Snowflake channel_id) { m_http.MakeDELETE("/channels/" + std::to_string(channel_id), [](const http::response &response) { CheckCode(response); @@ -1181,6 +1169,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; @@ -2294,7 +2301,7 @@ void DiscordClient::HeartbeatThread() { void DiscordClient::SendIdentify() { IdentifyMessage msg; msg.Token = m_token; - msg.Capabilities = 125; // no idea what this is + msg.Capabilities = 509; // no idea what this is msg.Properties.OS = "Windows"; msg.Properties.Browser = "Chrome"; msg.Properties.Device = ""; @@ -2307,7 +2314,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; @@ -2316,6 +2323,7 @@ void DiscordClient::SendIdentify() { msg.ClientState.HighestLastMessageID = "0"; msg.ClientState.ReadStateVersion = 0; msg.ClientState.UserGuildSettingsVersion = -1; + SetSuperPropertiesFromIdentity(msg); const bool b = m_websocket.GetPrintMessages(); m_websocket.SetPrintMessages(false); m_websocket.Send(msg); @@ -2330,6 +2338,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 718cb83..b1b623b 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -119,7 +119,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<void(DiscordError code, Snowflake channel_id)> &callback); void CloseDM(Snowflake channel_id); std::optional<Snowflake> FindDM(Snowflake user_id); // wont find group dms void AddReaction(Snowflake id, Glib::ustring param); @@ -205,6 +204,8 @@ public: void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot<void(std::optional<VerificationGateInfoObject>)> &callback); void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot<void(DiscordError code)> &callback); + void SetReferringChannel(Snowflake id); + void UpdateToken(const std::string &token); void SetUserAgent(const std::string &agent); @@ -286,6 +287,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 6646bf3..d13246d 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<void(http::response_type r)> &cb) { printf("DELETE %s\n", path.c_str()); m_futures.push_back(std::async(std::launch::async, [this, path, cb] { http::request req(http::REQUEST_DELETE, m_api_base + path); + AddHeaders(req); req.set_header("Authorization", m_authorization); + req.set_header("Origin", "https://discord.com"); req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon"); #ifdef USE_LOCAL_PROXY req.set_proxy("http://127.0.0.1:8888"); @@ -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<void(http: printf("GET %s\n", path.c_str()); m_futures.push_back(std::async(std::launch::async, [this, path, cb] { http::request req(http::REQUEST_GET, m_api_base + path); + AddHeaders(req); req.set_header("Authorization", m_authorization); - req.set_header("Content-Type", "application/json"); req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon"); #ifdef USE_LOCAL_PROXY req.set_proxy("http://127.0.0.1:8888"); @@ -147,6 +159,13 @@ void HTTPClient::RunCallbacks() { m_mutex.unlock(); } +void HTTPClient::AddHeaders(http::request &r) { + for (const auto &[name, val] : m_headers) { + r.set_header(name, val); + } + curl_easy_setopt(r.get_curl(), CURLOPT_ACCEPT_ENCODING, "gzip, deflate, br"); +} + void HTTPClient::OnResponse(const http::response_type &r, const std::function<void(http::response_type r)> &cb) { CleanupFutures(); try { diff --git a/src/discord/httpclient.hpp b/src/discord/httpclient.hpp index 366d4eb..83b1f5a 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<void(http::response_type r)> &cb); void MakeGET(const std::string &path, const std::function<void(http::response_type r)> &cb); void MakePATCH(const std::string &path, const std::string &payload, const std::function<void(http::response_type r)> &cb); @@ -27,6 +29,8 @@ public: void Execute(http::request &&req, const std::function<void(http::response_type r)> &cb); private: + void AddHeaders(http::request &r); + void OnResponse(const http::response_type &r, const std::function<void(http::response_type r)> &cb); void CleanupFutures(); @@ -39,4 +43,5 @@ private: std::string m_api_base; std::string m_authorization; std::string m_agent; + std::unordered_map<std::string, std::string> 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/discord/store.cpp b/src/discord/store.cpp index 663d113..892f4aa 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -254,6 +254,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; BeginTransaction(); @@ -1882,6 +1890,20 @@ bool Store::CreateStatements() { return false; } + m_stmt_clr_member_roles = std::make_unique<Statement>(m_db, R"( + DELETE FROM member_roles + WHERE user = ? AND + EXISTS ( + SELECT 1 FROM roles + WHERE member_roles.role = roles.id + AND roles.guild = ? + ) + )"); + if (!m_stmt_clr_member_roles->OK()) { + fprintf(stderr, "failed to prepare clear member roles statement: %s\n", m_db.ErrStr()); + return false; + } + m_stmt_set_guild_emoji = std::make_unique<Statement>(m_db, R"( REPLACE INTO guild_emojis VALUES ( ?, ? 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); diff --git a/src/http.cpp b/src/http.cpp index ba3ce3c..0fae39f 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -98,6 +98,10 @@ void request::set_user_agent(const std::string &data) { curl_easy_setopt(m_curl, CURLOPT_USERAGENT, data.c_str()); } +CURL *request::get_curl() { + return m_curl; +} + void request::make_form() { m_form = curl_mime_init(m_curl); } diff --git a/src/http.hpp b/src/http.hpp index 90a514a..5bf3c69 100644 --- a/src/http.hpp +++ b/src/http.hpp @@ -122,6 +122,8 @@ struct request { response execute(); + CURL *get_curl(); + private: void prepare(); |