fix webhook messages not using right username + avatar (fixes #209)

This commit is contained in:
ouwou 2023-08-28 21:47:04 -04:00
parent 9724023758
commit 36f2ec0996
10 changed files with 164 additions and 16 deletions

View File

@ -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;
}
}
}
}
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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>

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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
};

View File

@ -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";
}

View File

@ -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;
};