display interaction responses (again)

This commit is contained in:
ouwou
2021-04-04 04:09:56 -04:00
parent a933a0ca27
commit 6f1013d146
9 changed files with 174 additions and 28 deletions

View File

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

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

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