diff options
-rw-r--r-- | components/chatmessage.cpp | 58 | ||||
-rw-r--r-- | components/chatwindow.hpp | 3 | ||||
-rw-r--r-- | discord/discord.cpp | 5 | ||||
-rw-r--r-- | discord/interactions.cpp | 11 | ||||
-rw-r--r-- | discord/interactions.hpp | 25 | ||||
-rw-r--r-- | discord/message.cpp | 2 | ||||
-rw-r--r-- | discord/message.hpp | 6 | ||||
-rw-r--r-- | discord/store.cpp | 66 | ||||
-rw-r--r-- | discord/store.hpp | 26 |
9 files changed, 174 insertions, 28 deletions
diff --git a/components/chatmessage.cpp b/components/chatmessage.cpp index 90daa87..b527ce4 100644 --- a/components/chatmessage.cpp +++ b/components/chatmessage.cpp @@ -60,7 +60,7 @@ ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(Snowflake id) { container->m_main->add(*container->m_text_component); } - if (data->MessageReference.has_value()) { + if (data->MessageReference.has_value() || data->Interaction.has_value()) { auto *widget = container->CreateReplyComponent(*data); container->m_main->add(*widget); container->m_main->child_property_position(*widget) = 0; // eek @@ -230,6 +230,12 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) { const auto app = data->Application->Name; b->insert_markup(s, "<i>used <span color='#697ec4'>" + cmd + "</span> with " + app + "</i>"); } + } else { + b->insert(s, data->Content); + HandleUserMentions(b); + HandleLinks(*tv); + HandleChannelMentions(tv); + HandleEmojis(*tv); } } break; case MessageType::RECIPIENT_ADD: { @@ -567,7 +573,32 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) lbl->get_style_context()->add_class("message-reply"); box->add(*lbl); - if (data.ReferencedMessage.has_value()) { + const auto &discord = Abaddon::Get().GetDiscordClient(); + + const auto get_author_markup = [&](Snowflake author_id, Snowflake guild_id = Snowflake::Invalid) -> std::string { + if (guild_id.IsValid()) { + const auto role_id = discord.GetMemberHoistedRole(guild_id, author_id, true); + if (role_id.IsValid()) { + const auto role = discord.GetRole(role_id); + if (role.has_value()) { + const auto author = discord.GetUser(author_id); + return "<b><span color=\"#" + IntToCSSColor(role->Color) + "\">" + author->GetEscapedString() + "</span></b>"; + } + } + } + + const auto author = discord.GetUser(author_id); + return author->GetEscapedBoldString<false>(); + }; + + if (data.Interaction.has_value()) { + const auto user = *discord.GetUser(data.Interaction->User.ID); + lbl->set_markup( + get_author_markup(user.ID, data.GuildID.has_value() ? *data.GuildID : Snowflake::Invalid) + + " used <span color='#697ec4'>/" + + Glib::Markup::escape_text(data.Interaction->Name) + + "</span>"); + } else if (data.ReferencedMessage.has_value()) { if (data.ReferencedMessage.value().get() == nullptr) { lbl->set_markup("<i>deleted message</i>"); } else { @@ -595,28 +626,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) // which of course would not be an issue if i could figure out how to get fonts to work on this god-forsaken framework // oh well // but ill manually get colors for the user who is being replied to - const auto &discord = Abaddon::Get().GetDiscordClient(); - if (referenced.GuildID.has_value()) { - const auto role_id = discord.GetMemberHoistedRole(*referenced.GuildID, referenced.Author.ID, true); - if (role_id.IsValid()) { - const auto role = discord.GetRole(role_id); - if (role.has_value()) { - const auto author = discord.GetUser(referenced.Author.ID); - // clang-format off - lbl->set_markup( - "<b><span color=\"#" + IntToCSSColor(role->Color) + "\">" - + author->GetEscapedString() - + "</span></b>: " - + text - ); - // clang-format on - return box; - } - } - } - - const auto author = discord.GetUser(referenced.Author.ID); - lbl->set_markup(author->GetEscapedBoldString<false>() + ": " + text); + lbl->set_markup(get_author_markup(referenced.Author.ID, *referenced.GuildID) + ": " + text); } } else { lbl->set_markup("<i>reply unavailable</i>"); diff --git a/components/chatwindow.hpp b/components/chatwindow.hpp index 066a804..f8336dd 100644 --- a/components/chatwindow.hpp +++ b/components/chatwindow.hpp @@ -3,9 +3,10 @@ #include <string> #include <set> #include "../discord/discord.hpp" -#include "chatmessage.hpp" #include "completer.hpp" +class ChatMessageHeader; +class ChatMessageItemContainer; class ChatInput; class ChatInputIndicator; class ChatWindow { diff --git a/discord/discord.cpp b/discord/discord.cpp index 4d7464b..54a7b52 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -1739,6 +1739,11 @@ void DiscordClient::StoreMessageData(Message &msg) { if (msg.Member.has_value()) m_store.SetGuildMember(*msg.GuildID, msg.Author.ID, *msg.Member); + if (msg.Interaction.has_value() && msg.Interaction->Member.has_value()) { + m_store.SetUser(msg.Interaction->User.ID, msg.Interaction->User); + m_store.SetGuildMember(*msg.GuildID, msg.Interaction->User.ID, *msg.Interaction->Member); + } + m_store.EndTransaction(); if (msg.ReferencedMessage.has_value() && msg.MessageReference.has_value() && msg.MessageReference->ChannelID.has_value()) diff --git a/discord/interactions.cpp b/discord/interactions.cpp new file mode 100644 index 0000000..0bb09b1 --- /dev/null +++ b/discord/interactions.cpp @@ -0,0 +1,11 @@ +#include "interactions.hpp" +#include "json.hpp" +#include "../abaddon.hpp" + +void from_json(const nlohmann::json &j, MessageInteractionData &m) { + JS_D("id", m.ID); + JS_D("type", m.Type); + JS_D("name", m.Name); + JS_D("user", m.User); + JS_O("member", m.Member); +} diff --git a/discord/interactions.hpp b/discord/interactions.hpp new file mode 100644 index 0000000..c076145 --- /dev/null +++ b/discord/interactions.hpp @@ -0,0 +1,25 @@ +#pragma once +#include <string> +#include <optional> +#include "member.hpp" +#include "user.hpp" +#include "snowflake.hpp" + +enum class InteractionType { + Pong = 1, // ACK a Ping + Acknowledge = 2, // DEPRECATED ACK a command without sending a message, eating the user's input + ChannelMessage = 3, // DEPRECATED respond with a message, eating the user's input + ChannelMessageWithSource = 4, // respond to an interaction with a message + DeferredChannelMessageWithSource = 5, // ACK an interaction and edit to a response later, the user sees a loading state +}; + +struct MessageInteractionData { + Snowflake ID; // id of the interaction + InteractionType Type; // the type of interaction + std::string Name; // the name of the ApplicationCommand + UserData User; // the user who invoked the interaction + // undocumented??? + std::optional<GuildMember> Member; // the member who invoked the interaction (in a guild) + + friend void from_json(const nlohmann::json &j, MessageInteractionData &m); +}; diff --git a/discord/message.cpp b/discord/message.cpp index 5728df2..b072ba8 100644 --- a/discord/message.cpp +++ b/discord/message.cpp @@ -217,6 +217,7 @@ void from_json(const nlohmann::json &j, Message &m) { } else m.ReferencedMessage = nullptr; } + JS_O("interaction", m.Interaction); } void Message::from_json_edited(const nlohmann::json &j) { @@ -242,6 +243,7 @@ void Message::from_json_edited(const nlohmann::json &j) { JS_O("message_reference", MessageReference); JS_O("flags", Flags); JS_O("stickers", Stickers); + JS_O("interaction", Interaction); } void Message::SetDeleted() { diff --git a/discord/message.hpp b/discord/message.hpp index 7b2a051..ca60f08 100644 --- a/discord/message.hpp +++ b/discord/message.hpp @@ -1,12 +1,13 @@ #pragma once +#include <string> +#include <vector> #include "snowflake.hpp" #include "json.hpp" #include "user.hpp" #include "sticker.hpp" #include "emoji.hpp" #include "member.hpp" -#include <string> -#include <vector> +#include "interactions.hpp" enum class MessageType { DEFAULT = 0, // yep @@ -197,6 +198,7 @@ struct Message { std::optional<MessageFlags> Flags = MessageFlags::NONE; std::optional<std::vector<StickerData>> Stickers; std::optional<std::shared_ptr<Message>> ReferencedMessage; // has_value && null means deleted + std::optional<MessageInteractionData> Interaction; friend void from_json(const nlohmann::json &j, Message &m); void from_json_edited(const nlohmann::json &j); // for MESSAGE_UPDATE diff --git a/discord/store.cpp b/discord/store.cpp index a0ec6ea..d2b2375 100644 --- a/discord/store.cpp +++ b/discord/store.cpp @@ -264,6 +264,9 @@ void Store::SetMessage(Snowflake id, const Message &message) { if (!RunInsert(m_set_msg_stmt)) fprintf(stderr, "message insert failed: %s\n", sqlite3_errstr(m_db_err)); + + if (message.Interaction.has_value()) + SetMessageInteractionPair(id, *message.Interaction); } void Store::SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm) { @@ -311,6 +314,18 @@ void Store::SetUser(Snowflake id, const UserData &user) { } } +void Store::SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction) { + Bind(m_set_msg_interaction_stmt, 1, message_id); + Bind(m_set_msg_interaction_stmt, 2, interaction.ID); + Bind(m_set_msg_interaction_stmt, 3, interaction.Type); + Bind(m_set_msg_interaction_stmt, 4, interaction.Name); + Bind(m_set_msg_interaction_stmt, 5, interaction.User.ID); + + if (!RunInsert(m_set_msg_interaction_stmt)) { + fprintf(stderr, "message interaction insert failed: %s\n", sqlite3_errstr(m_db_err)); + } +} + std::optional<BanData> Store::GetBan(Snowflake guild_id, Snowflake user_id) const { Bind(m_get_ban_stmt, 1, guild_id); Bind(m_get_ban_stmt, 2, user_id); @@ -572,6 +587,16 @@ std::optional<Message> Store::GetMessage(Snowflake id) const { Get(m_get_msg_stmt, 22, ret.IsPending); Get(m_get_msg_stmt, 23, ret.Nonce); + // interaction data from join + + if (!IsNull(m_get_msg_stmt, 24)) { + auto &interaction = ret.Interaction.emplace(); + Get(m_get_msg_stmt, 24, interaction.ID); + Get(m_get_msg_stmt, 25, interaction.Name); + Get(m_get_msg_stmt, 26, interaction.Type); + Get(m_get_msg_stmt, 27, interaction.User.ID); + } + Reset(m_get_msg_stmt); if (ret.MessageReference.has_value() && ret.MessageReference->MessageID.has_value()) { @@ -877,6 +902,17 @@ bool Store::CreateTables() { ) )"; + constexpr const char *create_interactions = R"( + CREATE TABLE IF NOT EXISTS message_interactions ( + message_id INTEGER NOT NULL, + interaction_id INTEGER NOT NULL, + type INTEGER NOT NULL, + name STRING NOT NULL, + user_id INTEGER NOT NULL, + PRIMARY KEY(message_id) + ) + )"; + m_db_err = sqlite3_exec(m_db, create_users, nullptr, nullptr, nullptr); if (m_db_err != SQLITE_OK) { fprintf(stderr, "failed to create user table: %s\n", sqlite3_errstr(m_db_err)); @@ -931,6 +967,12 @@ bool Store::CreateTables() { return false; } + m_db_err = sqlite3_exec(m_db, create_interactions, nullptr, nullptr, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to create message interactions table: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + return true; } @@ -962,7 +1004,16 @@ bool Store::CreateStatements() { )"; constexpr const char *get_msg = R"( - SELECT * FROM messages WHERE id = ? + SELECT messages.*, + message_interactions.interaction_id as interaction_id, + message_interactions.name as interaction_name, + message_interactions.type as interaction_type, + message_interactions.user_id as interaction_user_id + FROM messages + LEFT OUTER JOIN + message_interactions + ON messages.id = message_interactions.message_id + WHERE id = ? )"; constexpr const char *set_role = R"( @@ -1033,6 +1084,12 @@ bool Store::CreateStatements() { SELECT * FROM bans WHERE guild_id = ? )"; + constexpr const char *set_interaction = R"( + REPLACE INTO message_interactions VALUES ( + ?, ?, ?, ?, ? + ) + )"; + m_db_err = sqlite3_prepare_v2(m_db, set_user, -1, &m_set_user_stmt, nullptr); if (m_db_err != SQLITE_OK) { fprintf(stderr, "failed to prepare set user statement: %s\n", sqlite3_errstr(m_db_err)); @@ -1153,6 +1210,12 @@ bool Store::CreateStatements() { return false; } + m_db_err = sqlite3_prepare_v2(m_db, set_interaction, -1, &m_set_msg_interaction_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare set message interaction statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + return true; } @@ -1177,6 +1240,7 @@ void Store::Cleanup() { sqlite3_finalize(m_get_ban_stmt); sqlite3_finalize(m_clear_ban_stmt); sqlite3_finalize(m_get_bans_stmt); + sqlite3_finalize(m_set_msg_interaction_stmt); } void Store::Bind(sqlite3_stmt *stmt, int index, int num) const { diff --git a/discord/store.hpp b/discord/store.hpp index 7559f23..23d2c0d 100644 --- a/discord/store.hpp +++ b/discord/store.hpp @@ -63,6 +63,8 @@ public: void EndTransaction(); private: + void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction); + std::unordered_set<Snowflake> m_channels; std::unordered_set<Snowflake> m_guilds; @@ -72,6 +74,11 @@ private: template<typename T> void Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const; + + template<typename T> + typename std::enable_if<std::is_enum<T>::value, void>::type + Bind(sqlite3_stmt *stmt, int index, T val) const; + void Bind(sqlite3_stmt *stmt, int index, int num) const; void Bind(sqlite3_stmt *stmt, int index, uint64_t num) const; void Bind(sqlite3_stmt *stmt, int index, const std::string &str) const; @@ -79,8 +86,14 @@ private: void Bind(sqlite3_stmt *stmt, int index, std::nullptr_t) const; bool RunInsert(sqlite3_stmt *stmt); bool FetchOne(sqlite3_stmt *stmt) const; + template<typename T> void Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const; + + template<typename T> + typename std::enable_if<std::is_enum<T>::value, void>::type + Get(sqlite3_stmt *stmt, int index, T &out) const; + void Get(sqlite3_stmt *stmt, int index, int &out) const; void Get(sqlite3_stmt *stmt, int index, uint64_t &out) const; void Get(sqlite3_stmt *stmt, int index, std::string &out) const; @@ -112,6 +125,7 @@ private: mutable sqlite3_stmt *m_get_ban_stmt; mutable sqlite3_stmt *m_clear_ban_stmt; mutable sqlite3_stmt *m_get_bans_stmt; + mutable sqlite3_stmt *m_set_msg_interaction_stmt; }; template<typename T> @@ -123,6 +137,12 @@ inline void Store::Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &o } template<typename T> +inline typename std::enable_if<std::is_enum<T>::value, void>::type +Store::Bind(sqlite3_stmt *stmt, int index, T val) const { + Bind(stmt, index, static_cast<typename std::underlying_type<T>::type>(val)); +} + +template<typename T> inline void Store::Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const { if (sqlite3_column_type(stmt, index) == SQLITE_NULL) out = std::nullopt; @@ -132,3 +152,9 @@ inline void Store::Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) con out = std::optional<T>(v); } } + +template<typename T> +inline typename std::enable_if<std::is_enum<T>::value, void>::type +Store::Get(sqlite3_stmt *stmt, int index, T &out) const { + out = static_cast<typename T>(sqlite3_column_int(stmt, index)); +} |