From 8941d5f204fdfc60c33f26f50aedc603bf091e6f Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 5 Sep 2020 00:55:06 -0400 Subject: lazy loading, member list, some other shit --- abaddon.cpp | 16 ++++++++- abaddon.hpp | 1 + components/memberlist.cpp | 59 ++++++++++++++++++++++++++++++- components/memberlist.hpp | 20 ++++++++++- discord/discord.cpp | 90 ++++++++++++++++++++++++++++++++++++++++++++--- discord/discord.hpp | 11 ++++++ discord/objects.cpp | 54 ++++++++++++++++++++++++++++ discord/objects.hpp | 57 ++++++++++++++++++++++++++++++ windows/mainwindow.cpp | 11 ++++++ windows/mainwindow.hpp | 1 + 10 files changed, 313 insertions(+), 7 deletions(-) diff --git a/abaddon.cpp b/abaddon.cpp index f5a65b1..76600fa 100644 --- a/abaddon.cpp +++ b/abaddon.cpp @@ -101,6 +101,10 @@ void Abaddon::DiscordNotifyMessageUpdateContent(Snowflake id, Snowflake channel_ m_main_window->UpdateChatMessageEditContent(id, channel_id); } +void Abaddon::DiscordNotifyGuildMemberListUpdate(Snowflake guild_id) { + m_main_window->UpdateMembers(); +} + void Abaddon::ActionConnect() { if (!m_discord.IsStarted()) StartDiscord(); @@ -173,7 +177,17 @@ void Abaddon::ActionCopyGuildID(Snowflake id) { void Abaddon::ActionListChannelItemClick(Snowflake id) { auto *channel = m_discord.GetChannel(id); - m_main_window->set_title(std::string(APP_TITLE) + " - #" + channel->Name); + m_discord.SendLazyLoad(id); + if (channel->Type == ChannelType::GUILD_TEXT) + m_main_window->set_title(std::string(APP_TITLE) + " - #" + channel->Name); + else { + std::string display; + if (channel->Recipients.size() > 1) + display = std::to_string(channel->Recipients.size()) + " users"; + else + display = channel->Recipients[0].Username; + m_main_window->set_title(std::string(APP_TITLE) + " - " + display); + } m_main_window->UpdateChatActiveChannel(id); if (m_channels_requested.find(id) == m_channels_requested.end()) { m_discord.FetchMessagesInChannel(id, [this, id](const std::vector &msgs) { diff --git a/abaddon.hpp b/abaddon.hpp index 6e568a9..76ee37a 100644 --- a/abaddon.hpp +++ b/abaddon.hpp @@ -43,6 +43,7 @@ public: void DiscordNotifyMessageCreate(Snowflake id); void DiscordNotifyMessageDelete(Snowflake id, Snowflake channel_id); void DiscordNotifyMessageUpdateContent(Snowflake id, Snowflake channel_id); + void DiscordNotifyGuildMemberListUpdate(Snowflake guild_id); private: DiscordClient m_discord; diff --git a/components/memberlist.cpp b/components/memberlist.cpp index f1553b8..edbc752 100644 --- a/components/memberlist.cpp +++ b/components/memberlist.cpp @@ -1,9 +1,66 @@ #include "memberlist.hpp" +#include "../abaddon.hpp" MemberList::MemberList() { - m_main = Gtk::manage(new Gtk::Box); + m_update_member_list_dispatcher.connect(sigc::mem_fun(*this, &MemberList::UpdateMemberListInternal)); + + m_main = Gtk::manage(new Gtk::ScrolledWindow); + m_listbox = Gtk::manage(new Gtk::ListBox); + + m_main->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + m_main->add(*m_listbox); + m_main->show_all(); } Gtk::Widget *MemberList::GetRoot() const { return m_main; } + +void MemberList::SetActiveChannel(Snowflake id) { + std::scoped_lock guard(m_mutex); + m_chan_id = id; + m_guild_id = m_abaddon->GetDiscordClient().GetChannel(id)->GuildID; +} + +void MemberList::UpdateMemberList() { + std::scoped_lock guard(m_mutex); + m_update_member_list_dispatcher.emit(); +} + +void MemberList::UpdateMemberListInternal() { + auto children = m_listbox->get_children(); + auto it = children.begin(); + while (it != children.end()) { + delete *it; + it++; + } + + if (!m_chan_id.IsValid()) return; + + auto &discord = m_abaddon->GetDiscordClient(); + auto *chan = discord.GetChannel(m_chan_id); + std::unordered_set ids; + if (chan->Type == ChannelType::DM) { + for (const auto &user : chan->Recipients) + ids.insert(user.ID); + } else { + ids = discord.GetUsersInGuild(m_guild_id); + } + + for (const auto &id : ids) { + auto *user = discord.GetUser(id); + auto *row = Gtk::manage(new Gtk::ListBoxRow); + auto *label = Gtk::manage(new Gtk::Label); + label->set_single_line_mode(true); + label->set_ellipsize(Pango::ELLIPSIZE_END); + label->set_text(user->Username + "#" + user->Discriminator); + label->set_halign(Gtk::ALIGN_START); + row->add(*label); + row->show_all(); + m_listbox->add(*row); + } +} + +void MemberList::SetAbaddon(Abaddon *ptr) { + m_abaddon = ptr; +} diff --git a/components/memberlist.hpp b/components/memberlist.hpp index 5434324..cce7bcd 100644 --- a/components/memberlist.hpp +++ b/components/memberlist.hpp @@ -1,11 +1,29 @@ #pragma once #include +#include +#include "../discord/discord.hpp" +class Abaddon; class MemberList { public: MemberList(); Gtk::Widget *GetRoot() const; + void UpdateMemberList(); + void SetActiveChannel(Snowflake id); + + void SetAbaddon(Abaddon *ptr); + private: - Gtk::Box *m_main; + void UpdateMemberListInternal(); + + std::mutex m_mutex; + Glib::Dispatcher m_update_member_list_dispatcher; + + Gtk::ScrolledWindow *m_main; + Gtk::ListBox *m_listbox; + + Snowflake m_guild_id; + Snowflake m_chan_id; + Abaddon *m_abaddon = nullptr; }; diff --git a/discord/discord.cpp b/discord/discord.cpp index ed919df..4417cf5 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -141,6 +141,8 @@ void DiscordClient::FetchMessagesInChannel(Snowflake id, std::functionsecond; + + return nullptr; +} + +std::unordered_set DiscordClient::GetUsersInGuild(Snowflake id) const { + auto it = m_guild_to_users.find(id); + if (it != m_guild_to_users.end()) + return it->second; + + return std::unordered_set(); } void DiscordClient::SendChatMessage(std::string content, Snowflake channel) { @@ -193,6 +219,21 @@ void DiscordClient::EditMessage(Snowflake channel_id, Snowflake id, std::string m_http.MakePATCH(path, j.dump(), [](auto) {}); } +void DiscordClient::SendLazyLoad(Snowflake id) { + LazyLoadRequestMessage msg; + std::unordered_map>> c; + c[id] = { + std::make_pair(0, 99) + }; + msg.Channels = c; + msg.GuildID = GetChannel(id)->GuildID; + msg.ShouldGetActivities = false; + msg.ShouldGetTyping = false; + + nlohmann::json j = msg; + m_websocket.Send(j); +} + void DiscordClient::UpdateToken(std::string token) { m_token = token; m_http.SetAuth(token); @@ -274,6 +315,9 @@ void DiscordClient::HandleGatewayMessage(std::string str) { case GatewayEvent::MESSAGE_UPDATE: { HandleGatewayMessageUpdate(m); } break; + case GatewayEvent::GUILD_MEMBER_LIST_UPDATE: { + HandleGatewayGuildMemberListUpdate(m); + } break; } } break; default: @@ -285,13 +329,15 @@ void DiscordClient::HandleGatewayMessage(std::string str) { void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) { m_ready_received = true; ReadyEventData data = msg.Data; - for (const auto &g : data.Guilds) { + for (auto &g : data.Guilds) { if (g.IsUnavailable) printf("guild (%lld) unavailable\n", g.ID); else { StoreGuild(g.ID, g); - for (const auto &c : g.Channels) + for (auto &c : g.Channels) { + c.GuildID = g.ID; StoreChannel(c.ID, c); + } } } @@ -307,6 +353,8 @@ void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) { void DiscordClient::HandleGatewayMessageCreate(const GatewayMessage &msg) { MessageData data = msg.Data; StoreMessage(data.ID, data); + StoreUser(data.Author); + AddUserToGuild(data.Author.ID, data.GuildID); m_abaddon->DiscordNotifyMessageCreate(data.ID); } @@ -330,6 +378,28 @@ void DiscordClient::HandleGatewayMessageUpdate(const GatewayMessage &msg) { } } +void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg) { + GuildMemberListUpdateMessage data = msg.Data; + // man + for (const auto &op : data.Ops) { + if (op.Op == "SYNC") { + for (const auto &item : op.Items) { + if (item->Type == "member") { + auto member = static_cast(item.get()); + auto known = GetUser(member->User.ID); + if (known == nullptr) { + StoreUser(member->User); + AddUserToGuild(member->User.ID, data.GuildID); + known = GetUser(member->User.ID); + } + } + } + } + } + + m_abaddon->DiscordNotifyGuildMemberListUpdate(data.GuildID); +} + void DiscordClient::StoreGuild(Snowflake id, const GuildData &g) { assert(id.IsValid() && id == g.ID); m_guilds[id] = g; @@ -348,6 +418,17 @@ void DiscordClient::StoreChannel(Snowflake id, const ChannelData &c) { m_channels[id] = c; } +void DiscordClient::AddUserToGuild(Snowflake user_id, Snowflake guild_id) { + if (m_guild_to_users.find(guild_id) == m_guild_to_users.end()) + m_guild_to_users[guild_id] = std::unordered_set(); + + m_guild_to_users[guild_id].insert(user_id); +} + +void DiscordClient::StoreUser(const UserData &u) { + m_users[u.ID] = u; +} + std::set DiscordClient::GetPrivateChannels() const { auto ret = std::set(); @@ -392,4 +473,5 @@ void DiscordClient::LoadEventMap() { m_event_map["MESSAGE_CREATE"] = GatewayEvent::MESSAGE_CREATE; m_event_map["MESSAGE_DELETE"] = GatewayEvent::MESSAGE_DELETE; m_event_map["MESSAGE_UPDATE"] = GatewayEvent::MESSAGE_UPDATE; + m_event_map["GUILD_MEMBER_LIST_UPDATE"] = GatewayEvent::GUILD_MEMBER_LIST_UPDATE; } diff --git a/discord/discord.hpp b/discord/discord.hpp index 1264d1c..d6577c7 100644 --- a/discord/discord.hpp +++ b/discord/discord.hpp @@ -23,6 +23,7 @@ public: std::unique_lock lock(m); return !cv.wait_for(lock, time, [&] { return terminate; }); } + void kill() { std::unique_lock lock(m); terminate = true; @@ -54,6 +55,7 @@ public: using Channels_t = std::unordered_map; using Guilds_t = std::unordered_map; using Messages_t = std::unordered_map; + using Users_t = std::unordered_map; const Guilds_t &GetGuilds() const; const UserData &GetUserData() const; @@ -67,10 +69,13 @@ public: void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function &)> cb); const MessageData *GetMessage(Snowflake id) const; const ChannelData *GetChannel(Snowflake id) const; + const UserData *GetUser(Snowflake id) const; + std::unordered_set GetUsersInGuild(Snowflake id) const; void SendChatMessage(std::string content, Snowflake channel); void DeleteMessage(Snowflake channel_id, Snowflake id); void EditMessage(Snowflake channel_id, Snowflake id, std::string content); + void SendLazyLoad(Snowflake id); void UpdateToken(std::string token); @@ -85,6 +90,7 @@ private: void HandleGatewayMessageCreate(const GatewayMessage &msg); void HandleGatewayMessageDelete(const GatewayMessage &msg); void HandleGatewayMessageUpdate(const GatewayMessage &msg); + void HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg); void HeartbeatThread(); void SendIdentify(); @@ -105,6 +111,11 @@ private: void StoreChannel(Snowflake id, const ChannelData &c); Channels_t m_channels; + void AddUserToGuild(Snowflake user_id, Snowflake guild_id); + void StoreUser(const UserData &u); + Users_t m_users; + std::unordered_map> m_guild_to_users; + UserData m_user_data; UserSettingsData m_user_settings; diff --git a/discord/objects.cpp b/discord/objects.cpp index 44c3c4a..c3094a9 100644 --- a/discord/objects.cpp +++ b/discord/objects.cpp @@ -180,6 +180,60 @@ void from_json(const nlohmann::json &j, MessageDeleteData &m) { JS_O("guild_id", m.GuildID); } +void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::GroupItem &m) { + m.Type = "group"; + JS_D("id", m.ID); + JS_D("count", m.Count); +} + +void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::MemberItem &m) { + m.Type = "member"; + JS_D("user", m.User); + JS_D("roles", m.Roles); + JS_D("mute", m.IsMuted); + JS_D("joined_at", m.JoinedAt); + JS_D("deaf", m.IsDefeaned); + JS_N("hoisted_role", m.HoistedRole); + JS_ON("premium_since", m.PremiumSince); + JS_ON("nick", m.Nickname); +} + +void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::OpObject &m) { + JS_D("op", m.Op); + if (m.Op == "SYNC") { + JS_D("range", m.Range); + for (const auto &ij : j.at("items")) { + if (ij.contains("group")) + m.Items.push_back(std::make_unique(ij.at("group"))); + else if (ij.contains("member")) + m.Items.push_back(std::make_unique(ij.at("member"))); + } + } +} + +void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage &m) { + JS_D("online_count", m.OnlineCount); + JS_D("member_count", m.MemberCount); + JS_D("id", m.ListIDHash); + JS_D("guild_id", m.GuildID); + JS_D("groups", m.Groups); + JS_D("ops", m.Ops); +} + +void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m) { + j["op"] = GatewayOp::LazyLoadRequest; + j["d"] = nlohmann::json::object(); + j["d"]["guild_id"] = m.GuildID; + j["d"]["channels"] = nlohmann::json::object(); + for (const auto &[key, chans] : m.Channels) { // apparently a map gets written as a list + j["d"]["channels"][std::to_string(key)] = chans; + } + j["d"]["typing"] = m.ShouldGetTyping; + j["d"]["activities"] = m.ShouldGetActivities; + if (m.Members.size() > 0) + j["d"]["members"] = m.Members; +} + void from_json(const nlohmann::json &j, ReadyEventData &m) { JS_D("v", m.GatewayVersion); JS_D("user", m.User); diff --git a/discord/objects.hpp b/discord/objects.hpp index 2a97c41..bbca342 100644 --- a/discord/objects.hpp +++ b/discord/objects.hpp @@ -57,6 +57,7 @@ enum class GatewayOp : int { Identify = 2, Hello = 10, HeartbeatAck = 11, + LazyLoadRequest = 14, }; enum class GatewayEvent : int { @@ -64,6 +65,7 @@ enum class GatewayEvent : int { MESSAGE_CREATE, MESSAGE_DELETE, MESSAGE_UPDATE, + GUILD_MEMBER_LIST_UPDATE, }; struct GatewayMessage { @@ -371,6 +373,61 @@ struct MessageDeleteData { friend void from_json(const nlohmann::json &j, MessageDeleteData &m); }; +struct GuildMemberListUpdateMessage { + struct Item { + std::string Type; + }; + + struct GroupItem : Item { + std::string ID; + int Count; + + friend void from_json(const nlohmann::json &j, GroupItem &m); + }; + + struct MemberItem : Item { + UserData User; // + std::vector Roles; // + // PresenceData Presence; // + std::string PremiumSince; // opt + std::string Nickname; // opt + bool IsMuted; // + std::string JoinedAt; // + std::string HoistedRole; // null + bool IsDefeaned; // + + friend void from_json(const nlohmann::json &j, MemberItem &m); + }; + + struct OpObject { + std::string Op; + int Index; + std::vector> Items; // SYNC + std::pair Range; // SYNC + + friend void from_json(const nlohmann::json &j, OpObject &m); + }; + + int OnlineCount; + int MemberCount; + std::string ListIDHash; + std::string GuildID; + std::vector Groups; + std::vector Ops; + + friend void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage &m); +}; + +struct LazyLoadRequestMessage { + Snowflake GuildID; + bool ShouldGetTyping = false; + bool ShouldGetActivities = false; + std::vector Members; // snowflake? + std::unordered_map>> Channels; // channel ID -> range of sidebar + + friend void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m); +}; + struct ReadyEventData { int GatewayVersion; // UserData User; // diff --git a/windows/mainwindow.cpp b/windows/mainwindow.cpp index a714c1a..827cba6 100644 --- a/windows/mainwindow.cpp +++ b/windows/mainwindow.cpp @@ -95,9 +95,14 @@ void MainWindow::UpdateComponents() { m_chat.ClearMessages(); } else { UpdateChannelListing(); + m_members.UpdateMemberList(); } } +void MainWindow::UpdateMembers() { + m_members.UpdateMemberList(); +} + void MainWindow::UpdateChannelListing() { auto &discord = m_abaddon->GetDiscordClient(); m_channel_list.SetListingFromGuilds(discord.GetGuilds()); @@ -106,10 +111,13 @@ void MainWindow::UpdateChannelListing() { void MainWindow::UpdateChatWindowContents() { auto &discord = m_abaddon->GetDiscordClient(); m_chat.SetMessages(discord.GetMessagesForChannel(m_chat.GetActiveChannel())); + m_members.UpdateMemberList(); } void MainWindow::UpdateChatActiveChannel(Snowflake id) { + auto &discord = m_abaddon->GetDiscordClient(); m_chat.SetActiveChannel(id); + m_members.SetActiveChannel(id); } Snowflake MainWindow::GetChatActiveChannel() const { @@ -119,6 +127,7 @@ Snowflake MainWindow::GetChatActiveChannel() const { void MainWindow::UpdateChatNewMessage(Snowflake id) { if (m_abaddon->GetDiscordClient().GetMessage(id)->ChannelID == GetChatActiveChannel()) m_chat.AddNewMessage(id); + m_members.UpdateMemberList(); } void MainWindow::UpdateChatMessageDeleted(Snowflake id, Snowflake channel_id) { @@ -133,10 +142,12 @@ void MainWindow::UpdateChatMessageEditContent(Snowflake id, Snowflake channel_id void MainWindow::UpdateChatPrependHistory(const std::vector &msgs) { m_chat.AddNewHistory(msgs); + m_members.UpdateMemberList(); } void MainWindow::SetAbaddon(Abaddon *ptr) { m_abaddon = ptr; m_channel_list.SetAbaddon(ptr); m_chat.SetAbaddon(ptr); + m_members.SetAbaddon(ptr); } diff --git a/windows/mainwindow.hpp b/windows/mainwindow.hpp index 9cd329f..415a405 100644 --- a/windows/mainwindow.hpp +++ b/windows/mainwindow.hpp @@ -11,6 +11,7 @@ public: void SetAbaddon(Abaddon *ptr); void UpdateComponents(); + void UpdateMembers(); void UpdateChannelListing(); void UpdateChatWindowContents(); void UpdateChatActiveChannel(Snowflake id); -- cgit v1.2.3