forked from OpenGamers/abaddon
display interaction responses (again)
This commit is contained in:
@@ -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>");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
|
||||
11
discord/interactions.cpp
Normal file
11
discord/interactions.cpp
Normal file
@@ -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);
|
||||
}
|
||||
25
discord/interactions.hpp
Normal file
25
discord/interactions.hpp
Normal file
@@ -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);
|
||||
};
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
@@ -122,6 +136,12 @@ inline void Store::Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &o
|
||||
sqlite3_bind_null(stmt, index);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user