diff options
-rw-r--r-- | abaddon.cpp | 11 | ||||
-rw-r--r-- | abaddon.hpp | 1 | ||||
-rw-r--r-- | dialogs/friendpicker.cpp | 96 | ||||
-rw-r--r-- | dialogs/friendpicker.hpp | 35 | ||||
-rw-r--r-- | discord/discord.cpp | 18 | ||||
-rw-r--r-- | discord/discord.hpp | 5 | ||||
-rw-r--r-- | discord/httpclient.cpp | 3 | ||||
-rw-r--r-- | discord/objects.cpp | 1 | ||||
-rw-r--r-- | discord/objects.hpp | 3 | ||||
-rw-r--r-- | discord/relationship.cpp | 6 | ||||
-rw-r--r-- | discord/relationship.hpp | 21 | ||||
-rw-r--r-- | windows/mainwindow.cpp | 20 | ||||
-rw-r--r-- | windows/mainwindow.hpp | 5 |
13 files changed, 223 insertions, 2 deletions
diff --git a/abaddon.cpp b/abaddon.cpp index abeb1ef..6e98ef9 100644 --- a/abaddon.cpp +++ b/abaddon.cpp @@ -8,6 +8,7 @@ #include "dialogs/joinguild.hpp" #include "dialogs/confirm.hpp" #include "dialogs/setstatus.hpp" +#include "dialogs/friendpicker.hpp" #include "abaddon.hpp" #include "windows/guildsettingswindow.hpp" #include "windows/profilewindow.hpp" @@ -124,6 +125,7 @@ int Abaddon::StartGTK() { 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_reload_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadSettings)); + m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient)); m_main_window->signal_action_show_user_menu().connect(sigc::mem_fun(*this, &Abaddon::ShowUserMenu)); @@ -547,6 +549,15 @@ void Abaddon::ActionGuildSettings(Snowflake id) { window->show(); } +void Abaddon::ActionAddRecipient(Snowflake channel_id) { + FriendPickerDialog dlg(*m_main_window); + auto response = dlg.run(); + if (response == Gtk::RESPONSE_OK) { + auto user_id = dlg.GetUserID(); + m_discord.AddGroupDMRecipient(channel_id, user_id); + } +} + void Abaddon::ActionReloadSettings() { m_settings.Reload(); } diff --git a/abaddon.hpp b/abaddon.hpp index bb7558e..884f66e 100644 --- a/abaddon.hpp +++ b/abaddon.hpp @@ -46,6 +46,7 @@ public: void ActionReactionAdd(Snowflake id, const Glib::ustring ¶m); void ActionReactionRemove(Snowflake id, const Glib::ustring ¶m); void ActionGuildSettings(Snowflake id); + void ActionAddRecipient(Snowflake channel_id); void ActionReloadSettings(); void ActionReloadCSS(); diff --git a/dialogs/friendpicker.cpp b/dialogs/friendpicker.cpp new file mode 100644 index 0000000..d39b741 --- /dev/null +++ b/dialogs/friendpicker.cpp @@ -0,0 +1,96 @@ +#include "friendpicker.hpp" +#include "../abaddon.hpp" + +FriendPickerDialog::FriendPickerDialog(Gtk::Window &parent) + : Gtk::Dialog("Pick a friend", parent, true) + , m_bbox(Gtk::ORIENTATION_HORIZONTAL) { + set_default_size(300, 300); + get_style_context()->add_class("app-window"); + get_style_context()->add_class("app-popup"); + + m_ok_button = add_button("OK", Gtk::RESPONSE_OK); + m_cancel_button = add_button("Cancel", Gtk::RESPONSE_CANCEL); + + m_ok_button->set_sensitive(false); + + auto &discord = Abaddon::Get().GetDiscordClient(); + auto relationships = discord.GetRelationships(RelationshipType::Friend); + for (auto id : relationships) { + auto *item = Gtk::manage(new FriendPickerDialogItem(id)); + item->show(); + m_list.add(*item); + } + + m_list.signal_row_activated().connect(sigc::mem_fun(*this, &FriendPickerDialog::OnRowActivated)); + m_list.signal_selected_rows_changed().connect(sigc::mem_fun(*this, &FriendPickerDialog::OnSelectionChange)); + m_list.set_activate_on_single_click(false); + m_list.set_selection_mode(Gtk::SELECTION_SINGLE); + + m_main.set_propagate_natural_height(true); + + m_main.add(m_list); + + get_content_area()->add(m_main); + + show_all_children(); +} + +Snowflake FriendPickerDialog::GetUserID() const { + return m_chosen_id; +} + +void FriendPickerDialog::OnRowActivated(Gtk::ListBoxRow *row) { + auto *x = dynamic_cast<FriendPickerDialogItem *>(row); + if (x != nullptr) { + m_chosen_id = x->ID; + response(Gtk::RESPONSE_OK); + } +} + +void FriendPickerDialog::OnSelectionChange() { + auto selection = m_list.get_selected_row(); + m_ok_button->set_sensitive(false); + if (selection != nullptr) { + auto *row = dynamic_cast<FriendPickerDialogItem *>(selection); + if (!row) return; + m_chosen_id = row->ID; + m_ok_button->set_sensitive(true); + } +} + +FriendPickerDialogItem::FriendPickerDialogItem(Snowflake user_id) + : ID(user_id) + , m_layout(Gtk::ORIENTATION_HORIZONTAL) { + auto user = *Abaddon::Get().GetDiscordClient().GetUser(user_id); + + m_name.set_markup("<b>" + + Glib::Markup::escape_text(user.Username) + "</b>#" + user.Discriminator); + m_name.set_single_line_mode(true); + + m_avatar.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(32); + if (user.HasAvatar()) { + if (user.HasAnimatedAvatar() && Abaddon::Get().GetSettings().GetShowAnimations()) { + auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) { + m_avatar.property_pixbuf_animation() = pb; + }; + Abaddon::Get().GetImageManager().LoadAnimationFromURL(user.GetAvatarURL("gif", "32"), 32, 32, sigc::track_obj(cb, *this)); + } else { + auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) { + m_avatar.property_pixbuf() = pb->scale_simple(32, 32, Gdk::INTERP_BILINEAR); + }; + Abaddon::Get().GetImageManager().LoadFromURL(user.GetAvatarURL("png", "32"), sigc::track_obj(cb, *this)); + } + } + + m_avatar.set_margin_end(5); + m_avatar.set_halign(Gtk::ALIGN_START); + m_avatar.set_valign(Gtk::ALIGN_CENTER); + m_name.set_halign(Gtk::ALIGN_START); + m_name.set_valign(Gtk::ALIGN_CENTER); + + m_layout.add(m_avatar); + m_layout.add(m_name); + add(m_layout); + + show_all_children(); +} diff --git a/dialogs/friendpicker.hpp b/dialogs/friendpicker.hpp new file mode 100644 index 0000000..8621999 --- /dev/null +++ b/dialogs/friendpicker.hpp @@ -0,0 +1,35 @@ +#pragma once +#include <gtkmm.h> +#include "../discord/snowflake.hpp" + +class FriendPickerDialog : public Gtk::Dialog { +public: + FriendPickerDialog(Gtk::Window &parent); + + Snowflake GetUserID() const; + +protected: + void OnRowActivated(Gtk::ListBoxRow *row); + void OnSelectionChange(); + + Snowflake m_chosen_id; + + Gtk::ScrolledWindow m_main; + Gtk::ListBox m_list; + Gtk::ButtonBox m_bbox; + + Gtk::Button *m_ok_button; + Gtk::Button *m_cancel_button; +}; + +class FriendPickerDialogItem : public Gtk::ListBoxRow { +public: + FriendPickerDialogItem(Snowflake user_id); + + Snowflake ID; + +private: + Gtk::Box m_layout; + Gtk::Image m_avatar; + Gtk::Label m_name; +}; diff --git a/discord/discord.cpp b/discord/discord.cpp index 0055229..5f356f2 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -522,6 +522,12 @@ void DiscordClient::DeleteInvite(const std::string &code, sigc::slot<void(bool s }); } +void DiscordClient::AddGroupDMRecipient(Snowflake channel_id, Snowflake user_id) { + m_http.MakePUT("/channels/" + std::to_string(channel_id) + "/recipients/" + std::to_string(user_id), "", [this](const http::response_type &response) { + CheckCode(response); + }); +} + void DiscordClient::RemoveGroupDMRecipient(Snowflake channel_id, Snowflake user_id) { m_http.MakeDELETE("/channels/" + std::to_string(channel_id) + "/recipients/" + std::to_string(user_id), [this](const http::response_type &response) { CheckCode(response); @@ -645,6 +651,14 @@ std::optional<PresenceStatus> DiscordClient::GetUserStatus(Snowflake id) const { return std::nullopt; } +std::unordered_set<Snowflake> DiscordClient::GetRelationships(RelationshipType type) const { + std::unordered_set<Snowflake> ret; + for (const auto &[id, rtype] : m_user_relationships) + if (rtype == type) + ret.insert(id); + return ret; +} + void DiscordClient::HandleGatewayMessageRaw(std::string str) { // handles multiple zlib compressed messages, calling HandleGatewayMessage when a full message is received std::vector<uint8_t> buf(str.begin(), str.end()); @@ -890,6 +904,10 @@ void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) { } } + if (data.Relationships.has_value()) + for (const auto &relationship : *data.Relationships) + m_user_relationships[relationship.ID] = relationship.Type; + m_store.EndTransaction(); m_session_id = data.SessionID; diff --git a/discord/discord.hpp b/discord/discord.hpp index 0fdf0e4..0e9dab7 100644 --- a/discord/discord.hpp +++ b/discord/discord.hpp @@ -120,6 +120,7 @@ public: void UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(bool success)> callback); void DeleteInvite(const std::string &code); void DeleteInvite(const std::string &code, sigc::slot<void(bool success)> callback); + void AddGroupDMRecipient(Snowflake channel_id, Snowflake user_id); void RemoveGroupDMRecipient(Snowflake channel_id, Snowflake user_id); // FetchGuildBans fetches all bans+reasons via api, this func fetches stored bans (so usually just GUILD_BAN_ADD data) @@ -143,6 +144,8 @@ public: std::optional<PresenceStatus> GetUserStatus(Snowflake id) const; + std::unordered_set<Snowflake> GetRelationships(RelationshipType type) const; + private: static const constexpr int InflateChunkSize = 0x10000; std::vector<uint8_t> m_compressed_buf; @@ -207,6 +210,8 @@ private: std::unordered_map<Snowflake, PresenceStatus> m_user_to_status; + std::unordered_map<Snowflake, RelationshipType> m_user_relationships; + UserData m_user_data; UserSettings m_user_settings; diff --git a/discord/httpclient.cpp b/discord/httpclient.cpp index dc94566..fa43810 100644 --- a/discord/httpclient.cpp +++ b/discord/httpclient.cpp @@ -75,7 +75,8 @@ void HTTPClient::MakePUT(const std::string &path, const std::string &payload, st m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] { http::request req(http::REQUEST_PUT, m_api_base + path); req.set_header("Authorization", m_authorization); - req.set_header("Content-Type", "application/json"); + if (payload != "") + req.set_header("Content-Type", "application/json"); req.set_user_agent(m_agent != "" ? m_agent : "Abaddon"); req.set_body(payload); #ifdef USE_LOCAL_PROXY diff --git a/discord/objects.cpp b/discord/objects.cpp index e4275a3..16ae320 100644 --- a/discord/objects.cpp +++ b/discord/objects.cpp @@ -118,6 +118,7 @@ void from_json(const nlohmann::json &j, ReadyEventData &m) { JS_D("private_channels", m.PrivateChannels); JS_O("users", m.Users); JS_ON("merged_members", m.MergedMembers); + JS_O("relationships", m.Relationships); } void from_json(const nlohmann::json &j, MergedPresence &m) { diff --git a/discord/objects.hpp b/discord/objects.hpp index 2f97805..6cbef69 100644 --- a/discord/objects.hpp +++ b/discord/objects.hpp @@ -18,6 +18,7 @@ #include "sticker.hpp" #include "ban.hpp" #include "auditlog.hpp" +#include "relationship.hpp" // most stuff below should just be objects that get processed and thrown away immediately @@ -200,6 +201,7 @@ struct ReadyEventData { std::optional<int> FriendSuggestionCount; UserSettings Settings; std::optional<std::vector<std::vector<GuildMember>>> MergedMembers; + std::optional<std::vector<RelationshipData>> Relationships; // std::vector<Unknown> ConnectedAccounts; // opt // std::map<std::string, Unknown> Consents; // opt // std::vector<Unknown> Experiments; // opt @@ -207,7 +209,6 @@ struct ReadyEventData { // std::map<Unknown, Unknown> Notes; // opt // std::vector<PresenceData> Presences; // opt // std::vector<ReadStateData> ReadStates; // opt - // std::vector<RelationshipData> Relationships; // opt // Unknown Tutorial; // opt, null // std::vector<GuildSettingData> UserGuildSettings; // opt diff --git a/discord/relationship.cpp b/discord/relationship.cpp new file mode 100644 index 0000000..d65d2c1 --- /dev/null +++ b/discord/relationship.cpp @@ -0,0 +1,6 @@ +#include "relationship.hpp" + +void from_json(const nlohmann::json &j, RelationshipData &m) { + JS_D("type", m.Type); + JS_D("id", m.ID); +} diff --git a/discord/relationship.hpp b/discord/relationship.hpp new file mode 100644 index 0000000..d492bd3 --- /dev/null +++ b/discord/relationship.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "json.hpp" +#include "user.hpp" + +enum class RelationshipType { + None = 0, + Friend = 1, + Blocked = 2, + PendingIncoming = 3, + PendingOutgoing = 4, + Implicit = 5, +}; + +struct RelationshipData { + // Snowflake UserID; this is the same as ID apparently but it looks new so i wont touch it + RelationshipType Type; + Snowflake ID; + // Unknown Nickname; // null + + friend void from_json(const nlohmann::json &j, RelationshipData &m); +}; diff --git a/windows/mainwindow.cpp b/windows/mainwindow.cpp index a09309f..db029bc 100644 --- a/windows/mainwindow.cpp +++ b/windows/mainwindow.cpp @@ -20,11 +20,14 @@ MainWindow::MainWindow() 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_sub.signal_popped_up().connect(sigc::mem_fun(*this, &MainWindow::OnDiscordSubmenuPopup)); // this gets called twice for some reason m_menu_discord.set_submenu(m_menu_discord_sub); m_menu_file.set_label("File"); @@ -72,6 +75,10 @@ MainWindow::MainWindow() Abaddon::Get().GetImageManager().ClearCache(); }); + m_menu_discord_add_recipient.signal_activate().connect([this] { + m_signal_action_add_recipient.emit(GetChatActiveChannel()); + }); + m_content_box.set_hexpand(true); m_content_box.set_vexpand(true); m_content_box.show(); @@ -237,6 +244,15 @@ void MainWindow::UpdateChatReactionRemove(Snowflake id, const Glib::ustring &par m_chat.UpdateReactions(id); } +void MainWindow::OnDiscordSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { + auto &discord = Abaddon::Get().GetDiscordClient(); + auto channel_id = GetChatActiveChannel(); + auto channel = discord.GetChannel(channel_id); + m_menu_discord_add_recipient.set_visible(false); + if (channel.has_value() && channel->GetDMRecipients().size() + 1 < 10) + m_menu_discord_add_recipient.set_visible(channel->Type == ChannelType::GROUP_DM); +} + ChannelList *MainWindow::GetChannelList() { return &m_channel_list; } @@ -280,3 +296,7 @@ MainWindow::type_signal_action_show_user_menu MainWindow::signal_action_show_use MainWindow::type_signal_action_reload_settings MainWindow::signal_action_reload_settings() { return m_signal_action_reload_settings; } + +MainWindow::type_signal_action_add_recipient MainWindow::signal_action_add_recipient() { + return m_signal_action_add_recipient; +} diff --git a/windows/mainwindow.hpp b/windows/mainwindow.hpp index 747cc73..b1803ad 100644 --- a/windows/mainwindow.hpp +++ b/windows/mainwindow.hpp @@ -42,6 +42,7 @@ public: typedef sigc::signal<void> type_signal_action_set_status; typedef sigc::signal<void, const GdkEvent *, Snowflake, Snowflake> type_signal_action_show_user_menu; typedef sigc::signal<void> type_signal_action_reload_settings; + typedef sigc::signal<void, Snowflake> type_signal_action_add_recipient; // channel id type_signal_action_connect signal_action_connect(); type_signal_action_disconnect signal_action_disconnect(); @@ -51,6 +52,7 @@ public: type_signal_action_set_status signal_action_set_status(); type_signal_action_show_user_menu signal_action_show_user_menu(); type_signal_action_reload_settings signal_action_reload_settings(); + type_signal_action_add_recipient signal_action_add_recipient(); protected: type_signal_action_connect m_signal_action_connect; @@ -61,6 +63,7 @@ protected: type_signal_action_set_status m_signal_action_set_status; type_signal_action_show_user_menu m_signal_action_show_user_menu; type_signal_action_reload_settings m_signal_action_reload_settings; + type_signal_action_add_recipient m_signal_action_add_recipient; protected: Gtk::Box m_main_box; @@ -80,6 +83,8 @@ protected: 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(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); Gtk::MenuItem m_menu_file; Gtk::Menu m_menu_file_sub; |