diff options
-rw-r--r-- | src/components/chatlist.cpp | 16 | ||||
-rw-r--r-- | src/components/chatmessage.cpp | 40 | ||||
-rw-r--r-- | src/discord/discord.cpp | 23 | ||||
-rw-r--r-- | src/discord/discord.hpp | 1 | ||||
-rw-r--r-- | src/discord/message.cpp | 8 | ||||
-rw-r--r-- | src/discord/message.hpp | 4 | ||||
-rw-r--r-- | src/discord/store.cpp | 69 | ||||
-rw-r--r-- | src/discord/store.hpp | 6 | ||||
-rw-r--r-- | src/discord/webhook.cpp | 4 | ||||
-rw-r--r-- | src/discord/webhook.hpp | 9 |
10 files changed, 164 insertions, 16 deletions
diff --git a/src/components/chatlist.cpp b/src/components/chatlist.cpp index ba18c0a..4dde8a3 100644 --- a/src/components/chatlist.cpp +++ b/src/components/chatlist.cpp @@ -67,8 +67,22 @@ void ChatList::ProcessNewMessage(const Message &data, bool prepend) { if (last_row != nullptr) { const uint64_t diff = std::max(data.ID, last_row->NewestID) - std::min(data.ID, last_row->NewestID); - if (last_row->UserID == data.Author.ID && (prepend || (diff < SnowflakeSplitDifference * Snowflake::SecondsInterval))) + if (last_row->UserID == data.Author.ID && (prepend || (diff < SnowflakeSplitDifference * Snowflake::SecondsInterval))) { should_attach = true; + } + // Separate webhooks if the usernames or avatar URLs are different + if (data.IsWebhook() && last_row->UserID == data.Author.ID) { + const auto last_message = discord.GetMessage(last_row->NewestID); + if (last_message.has_value() && last_message->IsWebhook()) { + const auto last_webhook_data = last_message->GetWebhookData(); + const auto next_webhook_data = data.GetWebhookData(); + if (last_webhook_data.has_value() && next_webhook_data.has_value()) { + if (last_webhook_data->Username != next_webhook_data->Username || last_webhook_data->Avatar != next_webhook_data->Avatar) { + should_attach = false; + } + } + } + } } } diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index d1d9f72..09083e3 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -918,11 +918,22 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) const auto author = Abaddon::Get().GetDiscordClient().GetUser(UserID); auto &img = Abaddon::Get().GetImageManager(); + std::string avatar_url; + if (data.IsWebhook()) { + const auto webhook_data = Abaddon::Get().GetDiscordClient().GetWebhookMessageData(data.ID); + if (webhook_data.has_value()) { + avatar_url = webhook_data->GetAvatarURL(); + } + } + if (avatar_url.empty()) { + avatar_url = author->GetAvatarURL(data.GuildID); + } + auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) { m_static_avatar = pb->scale_simple(AvatarSize, AvatarSize, Gdk::INTERP_BILINEAR); m_avatar.property_pixbuf() = m_static_avatar; }; - img.LoadFromURL(author->GetAvatarURL(data.GuildID), sigc::track_obj(cb, *this)); + img.LoadFromURL(avatar_url, sigc::track_obj(cb, *this)); if (author->HasAnimatedAvatar(data.GuildID)) { auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) { @@ -955,10 +966,11 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) m_extra->set_can_focus(false); m_extra->set_use_markup(true); } - if (author->IsABot()) - m_extra->set_markup("<b>BOT</b>"); - else if (data.WebhookID.has_value()) + if (data.IsWebhook()) { m_extra->set_markup("<b>Webhook</b>"); + } else if (author->IsABot()) { + m_extra->set_markup("<b>BOT</b>"); + } m_timestamp.set_text(data.ID.GetLocalTimestamp()); m_timestamp.set_hexpand(true); @@ -1018,11 +1030,21 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) show_all(); auto &discord = Abaddon::Get().GetDiscordClient(); - auto role_update_cb = [this](...) { UpdateName(); }; - discord.signal_role_update().connect(sigc::track_obj(role_update_cb, *this)); - auto guild_member_update_cb = [this](const auto &, const auto &) { UpdateName(); }; - discord.signal_guild_member_update().connect(sigc::track_obj(guild_member_update_cb, *this)); - UpdateName(); + if (data.IsWebhook()) { + const auto webhook_data = discord.GetWebhookMessageData(data.ID); + if (webhook_data.has_value()) { + const auto name = Glib::Markup::escape_text(webhook_data->Username); + m_author.set_markup("<span weight='bold'>" + name + "</span>"); + } else { + UpdateName(); + } + } else { + auto role_update_cb = [this](...) { UpdateName(); }; + discord.signal_role_update().connect(sigc::track_obj(role_update_cb, *this)); + auto guild_member_update_cb = [this](const auto &, const auto &) { UpdateName(); }; + discord.signal_guild_member_update().connect(sigc::track_obj(guild_member_update_cb, *this)); + UpdateName(); + } AttachUserMenuHandler(m_meta_ev); AttachUserMenuHandler(m_avatar_ev); } diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 084e19f..ccc61b4 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -333,6 +333,10 @@ std::vector<Snowflake> DiscordClient::GetChildChannelIDs(Snowflake parent_id) co return m_store.GetChannelIDsWithParentID(parent_id); } +std::optional<WebhookMessageData> DiscordClient::GetWebhookMessageData(Snowflake message_id) const { + return m_store.GetWebhookMessage(message_id); +} + bool DiscordClient::IsThreadJoined(Snowflake thread_id) const { return std::find(m_joined_threads.begin(), m_joined_threads.end(), thread_id) != m_joined_threads.end(); } @@ -2735,15 +2739,18 @@ void DiscordClient::StoreMessageData(Message &msg) { for (const auto &r : *msg.Reactions) { if (!r.Emoji.ID.IsValid()) continue; const auto cur = m_store.GetEmoji(r.Emoji.ID); - if (!cur.has_value()) + if (!cur.has_value()) { m_store.SetEmoji(r.Emoji.ID, r.Emoji); + } } - for (const auto &user : msg.Mentions) + for (const auto &user : msg.Mentions) { m_store.SetUser(user.ID, user); + } - if (msg.Member.has_value()) + if (msg.Member.has_value()) { m_store.SetGuildMember(*msg.GuildID, msg.Author.ID, *msg.Member); + } if (msg.Interaction.has_value()) { m_store.SetUser(msg.Interaction->User.ID, msg.Interaction->User); @@ -2752,11 +2759,17 @@ void DiscordClient::StoreMessageData(Message &msg) { } } + if (msg.IsWebhook()) { + m_store.SetWebhookMessage(msg); + } + m_store.EndTransaction(); - if (msg.ReferencedMessage.has_value() && msg.MessageReference.has_value() && msg.MessageReference->ChannelID.has_value()) - if (msg.ReferencedMessage.value() != nullptr) + if (msg.ReferencedMessage.has_value() && msg.MessageReference.has_value() && msg.MessageReference->ChannelID.has_value()) { + if (msg.ReferencedMessage.value() != nullptr) { StoreMessageData(**msg.ReferencedMessage); + } + } } // some notes for myself diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index eba190a..ebbf5f9 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -63,6 +63,7 @@ public: void GetArchivedPublicThreads(Snowflake channel_id, const sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> &callback); void GetArchivedPrivateThreads(Snowflake channel_id, const sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> &callback); std::vector<Snowflake> GetChildChannelIDs(Snowflake parent_id) const; + std::optional<WebhookMessageData> GetWebhookMessageData(Snowflake message_id) const; // get ids of given list of members for who we do not have the member data template<typename Iter> diff --git a/src/discord/message.cpp b/src/discord/message.cpp index 9adc7e6..bc4c6c8 100644 --- a/src/discord/message.cpp +++ b/src/discord/message.cpp @@ -283,3 +283,11 @@ bool Message::DoesMention(Snowflake id) const noexcept { if (!member.has_value()) return false; return std::find_first_of(MentionRoles.begin(), MentionRoles.end(), member->Roles.begin(), member->Roles.end()) != MentionRoles.end(); } + +bool Message::IsWebhook() const noexcept { + return WebhookID.has_value(); +} + +std::optional<WebhookMessageData> Message::GetWebhookData() const { + return Abaddon::Get().GetDiscordClient().GetWebhookMessageData(ID); +} diff --git a/src/discord/message.hpp b/src/discord/message.hpp index fb11604..0f53021 100644 --- a/src/discord/message.hpp +++ b/src/discord/message.hpp @@ -8,6 +8,7 @@ #include "emoji.hpp" #include "member.hpp" #include "interactions.hpp" +#include "webhook.hpp" #include "misc/bitwise.hpp" enum class MessageType { @@ -226,6 +227,9 @@ struct Message { bool DoesMentionEveryoneOrUser(Snowflake id) const noexcept; bool DoesMention(Snowflake id) const noexcept; + bool IsWebhook() const noexcept; + + std::optional<WebhookMessageData> GetWebhookData() const; private: bool m_deleted = false; diff --git a/src/discord/store.cpp b/src/discord/store.cpp index 7ee4d87..d8994c4 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -57,6 +57,20 @@ void Store::SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban) { s->Reset(); } +void Store::SetWebhookMessage(const Message &message) { + auto &s = m_stmt_set_webhook_msg; + + s->Bind(1, message.ID); + s->Bind(2, message.WebhookID); + s->Bind(3, message.Author.Username); + s->Bind(4, message.Author.Avatar); + + if (!s->Insert()) + fprintf(stderr, "webhook message insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(message.ID), m_db.ErrStr()); + + s->Reset(); +} + void Store::SetChannel(Snowflake id, const ChannelData &chan) { auto &s = m_stmt_set_chan; @@ -483,6 +497,29 @@ std::vector<BanData> Store::GetBans(Snowflake guild_id) const { return ret; } +std::optional<WebhookMessageData> Store::GetWebhookMessage(Snowflake message_id) const { + auto &s = m_stmt_get_webhook_msg; + + s->Bind(1, message_id); + if (!s->FetchOne()) { + if (m_db.Error() != SQLITE_DONE) + fprintf(stderr, "error while fetching webhook message %" PRIu64 ": %s\n", static_cast<uint64_t>(message_id), m_db.ErrStr()); + s->Reset(); + return {}; + } + + WebhookMessageData data; + s->Get(0, data.MessageID); + s->Get(1, data.WebhookID); + s->Get(2, data.Username); + s->Get(3, data.Avatar); + + s->Reset(); + + return data; +} + + Snowflake Store::GetGuildOwner(Snowflake guild_id) const { auto &s = m_stmt_get_guild_owner; @@ -1503,6 +1540,15 @@ bool Store::CreateTables() { ) )"; + const char *create_webhook_messages = R"( + CREATE TABLE IF NOT EXISTS webhook_messages ( + message_id INTEGER NOT NULL, + webhook_id INTEGER NOT NULL, + username TEXT, + avatar TEXT + ) + )"; + if (m_db.Execute(create_users) != SQLITE_OK) { fprintf(stderr, "failed to create user table: %s\n", m_db.ErrStr()); return false; @@ -1608,6 +1654,11 @@ bool Store::CreateTables() { return false; } + if (m_db.Execute(create_webhook_messages) != SQLITE_OK) { + fprintf(stderr, "failed to create webhook messages table: %s\n", m_db.ErrStr()); + return false; + } + if (m_db.Execute(R"( CREATE TRIGGER remove_zero_reactions AFTER UPDATE ON reactions WHEN new.count = 0 BEGIN @@ -2288,6 +2339,24 @@ bool Store::CreateStatements() { return false; } + m_stmt_set_webhook_msg = std::make_unique<Statement>(m_db, R"( + REPLACE INTO webhook_messages VALUES ( + ?, ?, ?, ? + ) + )"); + if (!m_stmt_set_webhook_msg->OK()) { + fprintf(stderr, "failed to prepare set webhook message statement: %s\n", m_db.ErrStr()); + return false; + } + + m_stmt_get_webhook_msg = std::make_unique<Statement>(m_db, R"( + SELECT * FROM webhook_messages WHERE message_id = ? + )"); + if (!m_stmt_get_webhook_msg->OK()) { + fprintf(stderr, "failed to prepare get webhook message statement: %s\n", m_db.ErrStr()); + return false; + } + return true; } diff --git a/src/discord/store.hpp b/src/discord/store.hpp index 875a5af..b6979d0 100644 --- a/src/discord/store.hpp +++ b/src/discord/store.hpp @@ -7,7 +7,7 @@ #include <sqlite3.h> #ifdef GetMessage // fuck you windows.h - #undef GetMessage +#undef GetMessage #endif class Store { @@ -26,6 +26,7 @@ public: void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm); void SetEmoji(Snowflake id, const EmojiData &emoji); void SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban); + void SetWebhookMessage(const Message &message); std::optional<ChannelData> GetChannel(Snowflake id) const; std::optional<EmojiData> GetEmoji(Snowflake id) const; @@ -37,6 +38,7 @@ public: std::optional<UserData> GetUser(Snowflake id) const; std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const; std::vector<BanData> GetBans(Snowflake guild_id) const; + std::optional<WebhookMessageData> GetWebhookMessage(Snowflake message_id) const; Snowflake GetGuildOwner(Snowflake guild_id) const; std::vector<Snowflake> GetMemberRoles(Snowflake guild_id, Snowflake user_id) const; @@ -313,5 +315,7 @@ private: STMT(get_guild_member_ids); STMT(clr_role); STMT(get_guild_owner); + STMT(set_webhook_msg); + STMT(get_webhook_msg); #undef STMT }; diff --git a/src/discord/webhook.cpp b/src/discord/webhook.cpp index 4e8b422..318f8c8 100644 --- a/src/discord/webhook.cpp +++ b/src/discord/webhook.cpp @@ -11,3 +11,7 @@ void from_json(const nlohmann::json &j, WebhookData &m) { JS_O("token", m.Token); JS_N("application_id", m.ApplicationID); } + +std::string WebhookMessageData::GetAvatarURL() const { + return Avatar.empty() ? "" : "https://cdn.discordapp.com/avatars/" + std::to_string(WebhookID) + "/" + Avatar + ".png?size=32"; +} diff --git a/src/discord/webhook.hpp b/src/discord/webhook.hpp index f0214df..3b9eeb9 100644 --- a/src/discord/webhook.hpp +++ b/src/discord/webhook.hpp @@ -22,3 +22,12 @@ struct WebhookData { friend void from_json(const nlohmann::json &j, WebhookData &m); }; + +struct WebhookMessageData { + Snowflake MessageID; + Snowflake WebhookID; + std::string Username; + std::string Avatar; + + std::string GetAvatarURL() const; +}; |