display reactions + click to add/remove

This commit is contained in:
ouwou
2020-12-15 01:51:49 -05:00
parent 0c313ce5e8
commit 2667a4b30d
20 changed files with 622 additions and 201 deletions

View File

@@ -35,6 +35,8 @@ Abaddon::Abaddon()
m_discord.signal_channel_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelUpdate)); m_discord.signal_channel_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelUpdate));
m_discord.signal_channel_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelCreate)); m_discord.signal_channel_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelCreate));
m_discord.signal_guild_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildUpdate)); m_discord.signal_guild_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildUpdate));
m_discord.signal_reaction_add().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReactionAdd));
m_discord.signal_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReactionRemove));
} }
Abaddon::~Abaddon() { Abaddon::~Abaddon() {
@@ -98,6 +100,8 @@ int Abaddon::StartGTK() {
m_main_window->GetChatWindow()->signal_action_chat_load_history().connect(sigc::mem_fun(*this, &Abaddon::ActionChatLoadHistory)); m_main_window->GetChatWindow()->signal_action_chat_load_history().connect(sigc::mem_fun(*this, &Abaddon::ActionChatLoadHistory));
m_main_window->GetChatWindow()->signal_action_channel_click().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened)); m_main_window->GetChatWindow()->signal_action_channel_click().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened));
m_main_window->GetChatWindow()->signal_action_insert_mention().connect(sigc::mem_fun(*this, &Abaddon::ActionInsertMention)); m_main_window->GetChatWindow()->signal_action_insert_mention().connect(sigc::mem_fun(*this, &Abaddon::ActionInsertMention));
m_main_window->GetChatWindow()->signal_action_reaction_add().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionAdd));
m_main_window->GetChatWindow()->signal_action_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionRemove));
ActionReloadCSS(); ActionReloadCSS();
@@ -203,6 +207,14 @@ void Abaddon::DiscordOnGuildUpdate(Snowflake guild_id) {
m_main_window->UpdateChannelsUpdateGuild(guild_id); m_main_window->UpdateChannelsUpdateGuild(guild_id);
} }
void Abaddon::DiscordOnReactionAdd(Snowflake message_id, const Glib::ustring &param) {
m_main_window->UpdateChatReactionAdd(message_id, param);
}
void Abaddon::DiscordOnReactionRemove(Snowflake message_id, const Glib::ustring &param) {
m_main_window->UpdateChatReactionAdd(message_id, param);
}
const SettingsManager &Abaddon::GetSettings() const { const SettingsManager &Abaddon::GetSettings() const {
return m_settings; return m_settings;
} }
@@ -423,6 +435,14 @@ void Abaddon::ActionSetStatus() {
m_discord.UpdateStatus(status, false, activity); m_discord.UpdateStatus(status, false, activity);
} }
void Abaddon::ActionReactionAdd(Snowflake id, const Glib::ustring &param) {
m_discord.AddReaction(id, param);
}
void Abaddon::ActionReactionRemove(Snowflake id, const Glib::ustring &param) {
m_discord.RemoveReaction(id, param);
}
void Abaddon::ActionReloadCSS() { void Abaddon::ActionReloadCSS() {
try { try {
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider); Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider);

View File

@@ -43,6 +43,8 @@ public:
void ActionKickMember(Snowflake user_id, Snowflake guild_id); void ActionKickMember(Snowflake user_id, Snowflake guild_id);
void ActionBanMember(Snowflake user_id, Snowflake guild_id); void ActionBanMember(Snowflake user_id, Snowflake guild_id);
void ActionSetStatus(); void ActionSetStatus();
void ActionReactionAdd(Snowflake id, const Glib::ustring &param);
void ActionReactionRemove(Snowflake id, const Glib::ustring &param);
void ActionReloadCSS(); void ActionReloadCSS();
@@ -65,6 +67,8 @@ public:
void DiscordOnChannelUpdate(Snowflake channel_id); void DiscordOnChannelUpdate(Snowflake channel_id);
void DiscordOnChannelCreate(Snowflake channel_id); void DiscordOnChannelCreate(Snowflake channel_id);
void DiscordOnGuildUpdate(Snowflake guild_id); void DiscordOnGuildUpdate(Snowflake guild_id);
void DiscordOnReactionAdd(Snowflake message_id, const Glib::ustring &param);
void DiscordOnReactionRemove(Snowflake message_id, const Glib::ustring &param);
const SettingsManager &GetSettings() const; const SettingsManager &GetSettings() const;

View File

@@ -76,6 +76,11 @@ ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(Snowflake id) {
} }
} }
if (data->Reactions.has_value() && data->Reactions->size() > 0) {
container->m_reactions_component = container->CreateReactionsComponent(&*data);
container->m_main->add(*container->m_reactions_component);
}
container->UpdateAttributes(); container->UpdateAttributes();
return container; return container;
@@ -125,6 +130,18 @@ void ChatMessageItemContainer::UpdateImage(std::string url, Glib::RefPtr<Gdk::Pi
} }
} }
void ChatMessageItemContainer::UpdateReactions() {
if (m_reactions_component != nullptr)
delete m_reactions_component;
const auto data = Abaddon::Get().GetDiscordClient().GetMessage(ID);
if (data->Reactions.has_value() && data->Reactions->size() > 0) {
m_reactions_component = CreateReactionsComponent(&*data);
m_reactions_component->show_all();
m_main->add(*m_reactions_component);
}
}
void ChatMessageItemContainer::UpdateAttributes() { void ChatMessageItemContainer::UpdateAttributes() {
const auto data = Abaddon::Get().GetDiscordClient().GetMessage(ID); const auto data = Abaddon::Get().GetDiscordClient().GetMessage(ID);
if (!data.has_value()) return; if (!data.has_value()) return;
@@ -395,6 +412,89 @@ Gtk::Widget *ChatMessageItemContainer::CreateStickerComponent(const Sticker &dat
return box; return box;
} }
Gtk::Widget *ChatMessageItemContainer::CreateReactionsComponent(const Message *data) {
auto *flow = Gtk::manage(new Gtk::FlowBox);
flow->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
flow->set_min_children_per_line(5);
flow->set_max_children_per_line(20);
flow->set_halign(Gtk::ALIGN_START);
flow->set_hexpand(false);
flow->set_column_spacing(2);
flow->set_selection_mode(Gtk::SELECTION_NONE);
auto &imgr = Abaddon::Get().GetImageManager();
auto &emojis = Abaddon::Get().GetEmojis();
const auto &placeholder = imgr.GetPlaceholder(16);
for (const auto &reaction : *data->Reactions) {
auto *ev = Gtk::manage(new Gtk::EventBox);
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
box->get_style_context()->add_class("reaction-box");
ev->add(*box);
flow->add(*ev);
bool is_stock = !reaction.Emoji.ID.IsValid();
bool has_reacted = reaction.HasReactedWith;
if (has_reacted)
box->get_style_context()->add_class("reacted");
ev->signal_button_press_event().connect([this, has_reacted, is_stock, reaction](GdkEventButton *event) -> bool {
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
Glib::ustring param; // escaped in client
if (is_stock)
param = reaction.Emoji.Name;
else
param = std::to_string(reaction.Emoji.ID);
if (has_reacted)
m_signal_action_reaction_remove.emit(param);
else
m_signal_action_reaction_add.emit(param);
return true;
}
return false;
});
ev->signal_realize().connect([ev]() {
auto window = ev->get_window();
auto display = window->get_display();
auto cursor = Gdk::Cursor::create(display, "pointer");
window->set_cursor(cursor);
});
// image
if (is_stock) { // unicode/stock
const auto &pb = emojis.GetPixBuf(reaction.Emoji.Name);
auto *img = Gtk::manage(new Gtk::Image(pb->scale_simple(16, 16, Gdk::INTERP_BILINEAR)));
img->set_can_focus(false);
box->add(*img);
} else { // custom
const auto &pb = imgr.GetFromURLIfCached(reaction.Emoji.GetURL());
Gtk::Image *img;
if (pb) {
img = Gtk::manage(new Gtk::Image(pb->scale_simple(16, 16, Gdk::INTERP_BILINEAR)));
} else {
img = Gtk::manage(new Gtk::Image(placeholder));
// can track_obj PLEASE work ???
imgr.LoadFromURL(reaction.Emoji.GetURL(), sigc::bind<0>(sigc::mem_fun(*this, &ChatMessageItemContainer::ReactionUpdateImage), img));
}
img->set_can_focus(false);
box->add(*img);
}
auto *lbl = Gtk::manage(new Gtk::Label(std::to_string(reaction.Count)));
lbl->set_margin_left(5);
lbl->get_style_context()->add_class("reaction-count");
box->add(*lbl);
}
return flow;
}
void ChatMessageItemContainer::ReactionUpdateImage(Gtk::Image *img, const Glib::RefPtr<Gdk::Pixbuf> &pb) {
img->property_pixbuf() = pb->scale_simple(16, 16, Gdk::INTERP_BILINEAR);
}
void ChatMessageItemContainer::HandleImage(const AttachmentData &data, Gtk::Image *img, std::string url) { void ChatMessageItemContainer::HandleImage(const AttachmentData &data, Gtk::Image *img, std::string url) {
m_img_loadmap[url] = std::make_pair(img, data); m_img_loadmap[url] = std::make_pair(img, data);
// ask the chatwindow to call UpdateImage because dealing with lifetimes sucks // ask the chatwindow to call UpdateImage because dealing with lifetimes sucks
@@ -715,6 +815,14 @@ ChatMessageItemContainer::type_signal_channel_click ChatMessageItemContainer::si
return m_signal_action_channel_click; return m_signal_action_channel_click;
} }
ChatMessageItemContainer::type_signal_action_reaction_add ChatMessageItemContainer::signal_action_reaction_add() {
return m_signal_action_reaction_add;
}
ChatMessageItemContainer::type_signal_action_reaction_remove ChatMessageItemContainer::signal_action_reaction_remove() {
return m_signal_action_reaction_remove;
}
ChatMessageItemContainer::type_signal_image_load ChatMessageItemContainer::signal_image_load() { ChatMessageItemContainer::type_signal_image_load ChatMessageItemContainer::signal_image_load() {
return m_signal_image_load; return m_signal_image_load;
} }

View File

@@ -14,6 +14,7 @@ public:
void UpdateAttributes(); void UpdateAttributes();
void UpdateContent(); void UpdateContent();
void UpdateImage(std::string url, Glib::RefPtr<Gdk::Pixbuf> buf); void UpdateImage(std::string url, Glib::RefPtr<Gdk::Pixbuf> buf);
void UpdateReactions();
protected: protected:
bool EmitImageLoad(std::string url); bool EmitImageLoad(std::string url);
@@ -25,6 +26,8 @@ protected:
Gtk::Widget *CreateImageComponent(const AttachmentData &data); Gtk::Widget *CreateImageComponent(const AttachmentData &data);
Gtk::Widget *CreateAttachmentComponent(const AttachmentData &data); // non-image attachments Gtk::Widget *CreateAttachmentComponent(const AttachmentData &data); // non-image attachments
Gtk::Widget *CreateStickerComponent(const Sticker &data); Gtk::Widget *CreateStickerComponent(const Sticker &data);
Gtk::Widget *CreateReactionsComponent(const Message *data);
void ReactionUpdateImage(Gtk::Image *img, const Glib::RefPtr<Gdk::Pixbuf> &pb);
void HandleImage(const AttachmentData &data, Gtk::Image *img, std::string url); void HandleImage(const AttachmentData &data, Gtk::Image *img, std::string url);
void OnEmbedImageLoad(const Glib::RefPtr<Gdk::Pixbuf> &pixbuf); void OnEmbedImageLoad(const Glib::RefPtr<Gdk::Pixbuf> &pixbuf);
@@ -75,6 +78,7 @@ protected:
Gtk::TextView *m_text_component = nullptr; Gtk::TextView *m_text_component = nullptr;
Gtk::Widget *m_embed_component = nullptr; Gtk::Widget *m_embed_component = nullptr;
Gtk::Widget *m_reactions_component = nullptr;
public: public:
typedef sigc::signal<void, std::string> type_signal_image_load; typedef sigc::signal<void, std::string> type_signal_image_load;
@@ -82,10 +86,14 @@ public:
typedef sigc::signal<void> type_signal_action_delete; typedef sigc::signal<void> type_signal_action_delete;
typedef sigc::signal<void> type_signal_action_edit; typedef sigc::signal<void> type_signal_action_edit;
typedef sigc::signal<void, Snowflake> type_signal_channel_click; typedef sigc::signal<void, Snowflake> type_signal_channel_click;
typedef sigc::signal<void, Glib::ustring> type_signal_action_reaction_add;
typedef sigc::signal<void, Glib::ustring> type_signal_action_reaction_remove;
type_signal_action_delete signal_action_delete(); type_signal_action_delete signal_action_delete();
type_signal_action_edit signal_action_edit(); type_signal_action_edit signal_action_edit();
type_signal_channel_click signal_action_channel_click(); type_signal_channel_click signal_action_channel_click();
type_signal_action_reaction_add signal_action_reaction_add();
type_signal_action_reaction_remove signal_action_reaction_remove();
type_signal_image_load signal_image_load(); type_signal_image_load signal_image_load();
@@ -93,6 +101,8 @@ private:
type_signal_action_delete m_signal_action_delete; type_signal_action_delete m_signal_action_delete;
type_signal_action_edit m_signal_action_edit; type_signal_action_edit m_signal_action_edit;
type_signal_channel_click m_signal_action_channel_click; type_signal_channel_click m_signal_action_channel_click;
type_signal_action_reaction_add m_signal_action_reaction_add;
type_signal_action_reaction_remove m_signal_action_reaction_remove;
type_signal_image_load m_signal_image_load; type_signal_image_load m_signal_image_load;
}; };

View File

@@ -128,6 +128,14 @@ Snowflake ChatWindow::GetOldestListedMessage() {
return m; return m;
} }
void ChatWindow::UpdateReactions(Snowflake id) {
auto it = m_id_to_widget.find(id);
if (it == m_id_to_widget.end()) return;
auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
if (widget == nullptr) return;
widget->UpdateReactions();
}
Snowflake ChatWindow::GetActiveChannel() const { Snowflake ChatWindow::GetActiveChannel() const {
return m_active_channel; return m_active_channel;
} }
@@ -206,6 +214,12 @@ void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) {
content->signal_action_edit().connect([this, id] { content->signal_action_edit().connect([this, id] {
m_signal_action_message_edit.emit(m_active_channel, id); m_signal_action_message_edit.emit(m_active_channel, id);
}); });
content->signal_action_reaction_add().connect([this, id](const Glib::ustring &param) {
m_signal_action_reaction_add.emit(id, param);
});
content->signal_action_reaction_remove().connect([this, id](const Glib::ustring &param) {
m_signal_action_reaction_remove.emit(id, param);
});
content->signal_image_load().connect([this, id](std::string url) { content->signal_image_load().connect([this, id](std::string url) {
auto &mgr = Abaddon::Get().GetImageManager(); auto &mgr = Abaddon::Get().GetImageManager();
mgr.LoadFromURL(url, [this, id, url](Glib::RefPtr<Gdk::Pixbuf> buf) { mgr.LoadFromURL(url, [this, id, url](Glib::RefPtr<Gdk::Pixbuf> buf) {
@@ -339,3 +353,11 @@ ChatWindow::type_signal_action_insert_mention ChatWindow::signal_action_insert_m
ChatWindow::type_signal_action_open_user_menu ChatWindow::signal_action_open_user_menu() { ChatWindow::type_signal_action_open_user_menu ChatWindow::signal_action_open_user_menu() {
return m_signal_action_open_user_menu; return m_signal_action_open_user_menu;
} }
ChatWindow::type_signal_action_reaction_add ChatWindow::signal_action_reaction_add() {
return m_signal_action_reaction_add;
}
ChatWindow::type_signal_action_reaction_remove ChatWindow::signal_action_reaction_remove() {
return m_signal_action_reaction_remove;
}

View File

@@ -23,6 +23,7 @@ public:
void AddNewHistory(const std::vector<Snowflake> &id); // prepend messages void AddNewHistory(const std::vector<Snowflake> &id); // prepend messages
void InsertChatInput(std::string text); void InsertChatInput(std::string text);
Snowflake GetOldestListedMessage(); // oldest message that is currently in the ListBox Snowflake GetOldestListedMessage(); // oldest message that is currently in the ListBox
void UpdateReactions(Snowflake id);
protected: protected:
ChatMessageItemContainer *CreateMessageComponent(Snowflake id); // to be inserted into header's content box ChatMessageItemContainer *CreateMessageComponent(Snowflake id); // to be inserted into header's content box
@@ -72,6 +73,8 @@ public:
typedef sigc::signal<void, Snowflake> type_signal_action_channel_click; typedef sigc::signal<void, Snowflake> type_signal_action_channel_click;
typedef sigc::signal<void, Snowflake> type_signal_action_insert_mention; typedef sigc::signal<void, Snowflake> type_signal_action_insert_mention;
typedef sigc::signal<void, const GdkEvent *, Snowflake, Snowflake> type_signal_action_open_user_menu; typedef sigc::signal<void, const GdkEvent *, Snowflake, Snowflake> type_signal_action_open_user_menu;
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_action_reaction_add;
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_action_reaction_remove;
type_signal_action_message_delete signal_action_message_delete(); type_signal_action_message_delete signal_action_message_delete();
type_signal_action_message_edit signal_action_message_edit(); type_signal_action_message_edit signal_action_message_edit();
@@ -80,6 +83,8 @@ public:
type_signal_action_channel_click signal_action_channel_click(); type_signal_action_channel_click signal_action_channel_click();
type_signal_action_insert_mention signal_action_insert_mention(); type_signal_action_insert_mention signal_action_insert_mention();
type_signal_action_open_user_menu signal_action_open_user_menu(); type_signal_action_open_user_menu signal_action_open_user_menu();
type_signal_action_reaction_add signal_action_reaction_add();
type_signal_action_reaction_remove signal_action_reaction_remove();
private: private:
type_signal_action_message_delete m_signal_action_message_delete; type_signal_action_message_delete m_signal_action_message_delete;
@@ -89,4 +94,6 @@ private:
type_signal_action_channel_click m_signal_action_channel_click; type_signal_action_channel_click m_signal_action_channel_click;
type_signal_action_insert_mention m_signal_action_insert_mention; type_signal_action_insert_mention m_signal_action_insert_mention;
type_signal_action_open_user_menu m_signal_action_open_user_menu; type_signal_action_open_user_menu m_signal_action_open_user_menu;
type_signal_action_reaction_add m_signal_action_reaction_add;
type_signal_action_reaction_remove m_signal_action_reaction_remove;
}; };

View File

@@ -105,6 +105,22 @@
margin: 5px; margin: 5px;
} }
.reaction-box {
padding: 2px 5px 2px 5px;
margin: 0px 0px 0px 0px;
background-color: rgba(0.4, 0.4, 0.4, 0.4);
border-radius: 5px;
border: 1px solid transparent;
}
.reaction-box.reacted {
border: 1px solid white;
}
.reaction-count {
color: #cfd8dc;
}
paned separator { paned separator {
background:#37474f; background:#37474f;
} }

View File

@@ -411,6 +411,34 @@ std::optional<Snowflake> DiscordClient::FindDM(Snowflake user_id) {
return std::nullopt; return std::nullopt;
} }
void DiscordClient::AddReaction(Snowflake id, Glib::ustring param) {
if (!param.is_ascii()) // means unicode param
param = Glib::uri_escape_string(param, "", false);
else {
const auto &tmp = m_store.GetEmoji(param);
if (tmp.has_value())
param = tmp->Name + ":" + std::to_string(tmp->ID);
else
return;
}
Snowflake channel_id = m_store.GetMessage(id)->ChannelID;
m_http.MakePUT("/channels/" + std::to_string(channel_id) + "/messages/" + std::to_string(id) + "/reactions/" + param + "/@me", "", [](auto) {});
}
void DiscordClient::RemoveReaction(Snowflake id, Glib::ustring param) {
if (!param.is_ascii()) // means unicode param
param = Glib::uri_escape_string(param, "", false);
else {
const auto &tmp = m_store.GetEmoji(param);
if (tmp.has_value())
param = tmp->Name + ":" + std::to_string(tmp->ID);
else
return;
}
Snowflake channel_id = m_store.GetMessage(id)->ChannelID;
m_http.MakeDELETE("/channels/" + std::to_string(channel_id) + "/messages/" + std::to_string(id) + "/reactions/" + param + "/@me", [](auto) {});
}
void DiscordClient::UpdateToken(std::string token) { void DiscordClient::UpdateToken(std::string token) {
if (!IsStarted()) { if (!IsStarted()) {
m_token = token; m_token = token;
@@ -552,6 +580,12 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
case GatewayEvent::GUILD_ROLE_DELETE: { case GatewayEvent::GUILD_ROLE_DELETE: {
HandleGatewayGuildRoleDelete(m); HandleGatewayGuildRoleDelete(m);
} break; } break;
case GatewayEvent::MESSAGE_REACTION_ADD: {
HandleGatewayMessageReactionAdd(m);
} break;
case GatewayEvent::MESSAGE_REACTION_REMOVE: {
HandleGatewayMessageReactionRemove(m);
} break;
} }
} break; } break;
default: default:
@@ -735,6 +769,80 @@ void DiscordClient::HandleGatewayGuildRoleDelete(const GatewayMessage &msg) {
m_signal_role_delete.emit(data.RoleID); m_signal_role_delete.emit(data.RoleID);
} }
void DiscordClient::HandleGatewayMessageReactionAdd(const GatewayMessage &msg) {
MessageReactionAddObject data = msg.Data;
auto to = m_store.GetMessage(data.MessageID);
if (!to.has_value()) return;
if (!to->Reactions.has_value()) to->Reactions.emplace();
// add if present
bool stock;
auto it = std::find_if(to->Reactions->begin(), to->Reactions->end(), [&](const ReactionData &x) {
if (data.Emoji.ID.IsValid() && x.Emoji.ID.IsValid()) {
stock = false;
return data.Emoji.ID == x.Emoji.ID;
} else {
stock = true;
return data.Emoji.Name == x.Emoji.Name;
}
});
if (it != to->Reactions->end()) {
it->Count++;
if (data.UserID == GetUserData().ID)
it->HasReactedWith = true;
m_store.SetMessage(data.MessageID, *to);
if (stock)
m_signal_reaction_add.emit(data.MessageID, data.Emoji.Name);
else
m_signal_reaction_add.emit(data.MessageID, std::to_string(data.Emoji.ID));
return;
}
// create new
auto &rdata = to->Reactions->emplace_back();
rdata.Count = 1;
rdata.Emoji = data.Emoji;
rdata.HasReactedWith = data.UserID == GetUserData().ID;
m_store.SetMessage(data.MessageID, *to);
if (stock)
m_signal_reaction_add.emit(data.MessageID, data.Emoji.Name);
else
m_signal_reaction_add.emit(data.MessageID, std::to_string(data.Emoji.ID));
}
void DiscordClient::HandleGatewayMessageReactionRemove(const GatewayMessage &msg) {
MessageReactionRemoveObject data = msg.Data;
auto to = m_store.GetMessage(data.MessageID);
if (!to.has_value()) return;
if (!to->Reactions.has_value()) return;
bool stock;
auto it = std::find_if(to->Reactions->begin(), to->Reactions->end(), [&](const ReactionData &x) {
if (data.Emoji.ID.IsValid() && x.Emoji.ID.IsValid()) {
stock = false;
return data.Emoji.ID == x.Emoji.ID;
} else {
stock = true;
return data.Emoji.Name == x.Emoji.Name;
}
});
if (it == to->Reactions->end()) return;
if (it->Count == 1)
to->Reactions->erase(it);
else {
if (data.UserID == GetUserData().ID)
it->HasReactedWith = false;
it->Count--;
}
m_store.SetMessage(data.MessageID, *to);
if (stock)
m_signal_reaction_remove.emit(data.MessageID, data.Emoji.Name);
else
m_signal_reaction_remove.emit(data.MessageID, std::to_string(data.Emoji.ID));
}
void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) { void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) {
m_signal_disconnected.emit(true); m_signal_disconnected.emit(true);
inflateEnd(&m_zstream); inflateEnd(&m_zstream);
@@ -928,6 +1036,8 @@ void DiscordClient::LoadEventMap() {
m_event_map["GUILD_ROLE_UPDATE"] = GatewayEvent::GUILD_ROLE_UPDATE; m_event_map["GUILD_ROLE_UPDATE"] = GatewayEvent::GUILD_ROLE_UPDATE;
m_event_map["GUILD_ROLE_CREATE"] = GatewayEvent::GUILD_ROLE_CREATE; m_event_map["GUILD_ROLE_CREATE"] = GatewayEvent::GUILD_ROLE_CREATE;
m_event_map["GUILD_ROLE_DELETE"] = GatewayEvent::GUILD_ROLE_DELETE; m_event_map["GUILD_ROLE_DELETE"] = GatewayEvent::GUILD_ROLE_DELETE;
m_event_map["MESSAGE_REACTION_ADD"] = GatewayEvent::MESSAGE_REACTION_ADD;
m_event_map["MESSAGE_REACTION_REMOVE"] = GatewayEvent::MESSAGE_REACTION_REMOVE;
} }
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() { DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
@@ -993,3 +1103,11 @@ DiscordClient::type_signal_role_create DiscordClient::signal_role_create() {
DiscordClient::type_signal_role_delete DiscordClient::signal_role_delete() { DiscordClient::type_signal_role_delete DiscordClient::signal_role_delete() {
return m_signal_role_delete; return m_signal_role_delete;
} }
DiscordClient::type_signal_reaction_add DiscordClient::signal_reaction_add() {
return m_signal_reaction_add;
}
DiscordClient::type_signal_reaction_remove DiscordClient::signal_reaction_remove() {
return m_signal_reaction_remove;
}

View File

@@ -108,6 +108,8 @@ public:
void UpdateStatus(const std::string &status, bool is_afk, const Activity &obj); void UpdateStatus(const std::string &status, bool is_afk, const Activity &obj);
void CreateDM(Snowflake user_id); void CreateDM(Snowflake user_id);
std::optional<Snowflake> FindDM(Snowflake user_id); // wont find group dms std::optional<Snowflake> FindDM(Snowflake user_id); // wont find group dms
void AddReaction(Snowflake id, Glib::ustring param);
void RemoveReaction(Snowflake id, Glib::ustring param);
void UpdateToken(std::string token); void UpdateToken(std::string token);
void SetUserAgent(std::string agent); void SetUserAgent(std::string agent);
@@ -140,6 +142,8 @@ private:
void HandleGatewayGuildRoleUpdate(const GatewayMessage &msg); void HandleGatewayGuildRoleUpdate(const GatewayMessage &msg);
void HandleGatewayGuildRoleCreate(const GatewayMessage &msg); void HandleGatewayGuildRoleCreate(const GatewayMessage &msg);
void HandleGatewayGuildRoleDelete(const GatewayMessage &msg); void HandleGatewayGuildRoleDelete(const GatewayMessage &msg);
void HandleGatewayMessageReactionAdd(const GatewayMessage &msg);
void HandleGatewayMessageReactionRemove(const GatewayMessage &msg);
void HandleGatewayReconnect(const GatewayMessage &msg); void HandleGatewayReconnect(const GatewayMessage &msg);
void HeartbeatThread(); void HeartbeatThread();
void SendIdentify(); void SendIdentify();
@@ -202,6 +206,8 @@ public:
typedef sigc::signal<void, Snowflake> type_signal_role_update; typedef sigc::signal<void, Snowflake> type_signal_role_update;
typedef sigc::signal<void, Snowflake> type_signal_role_create; typedef sigc::signal<void, Snowflake> type_signal_role_create;
typedef sigc::signal<void, Snowflake> type_signal_role_delete; typedef sigc::signal<void, Snowflake> type_signal_role_delete;
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_reaction_add;
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_reaction_remove;
typedef sigc::signal<void, bool> type_signal_disconnected; // bool true if reconnecting typedef sigc::signal<void, bool> type_signal_disconnected; // bool true if reconnecting
typedef sigc::signal<void> type_signal_connected; typedef sigc::signal<void> type_signal_connected;
@@ -219,6 +225,8 @@ public:
type_signal_role_update signal_role_update(); type_signal_role_update signal_role_update();
type_signal_role_create signal_role_create(); type_signal_role_create signal_role_create();
type_signal_role_delete signal_role_delete(); type_signal_role_delete signal_role_delete();
type_signal_reaction_add signal_reaction_add();
type_signal_reaction_remove signal_reaction_remove();
type_signal_disconnected signal_disconnected(); type_signal_disconnected signal_disconnected();
type_signal_connected signal_connected(); type_signal_connected signal_connected();
@@ -237,6 +245,8 @@ protected:
type_signal_role_update m_signal_role_update; type_signal_role_update m_signal_role_update;
type_signal_role_create m_signal_role_create; type_signal_role_create m_signal_role_create;
type_signal_role_delete m_signal_role_delete; type_signal_role_delete m_signal_role_delete;
type_signal_reaction_add m_signal_reaction_add;
type_signal_reaction_remove m_signal_reaction_remove;
type_signal_disconnected m_signal_disconnected; type_signal_disconnected m_signal_disconnected;
type_signal_connected m_signal_connected; type_signal_connected m_signal_connected;
}; };

View File

@@ -11,6 +11,23 @@ void from_json(const nlohmann::json &j, Emoji &m) {
JS_O("available", m.IsAvailable); JS_O("available", m.IsAvailable);
} }
void to_json(nlohmann::json &j, const Emoji &m) {
if (m.ID.IsValid())
j["id"] = m.ID;
else
j["id"] = nullptr;
if (m.Name != "")
j["name"] = m.Name;
else
j["name"] = nullptr;
JS_IF("roles", m.Roles);
JS_IF("user", m.Creator);
JS_IF("require_colons", m.NeedsColons);
JS_IF("managed", m.IsManaged);
JS_IF("animated", m.IsAnimated);
JS_IF("available", m.IsAvailable);
}
std::string Emoji::GetURL() const { std::string Emoji::GetURL() const {
return "https://cdn.discordapp.com/emojis/" + std::to_string(ID) + ".png"; return "https://cdn.discordapp.com/emojis/" + std::to_string(ID) + ".png";
} }

View File

@@ -16,6 +16,7 @@ struct Emoji {
std::optional<bool> IsAvailable; std::optional<bool> IsAvailable;
friend void from_json(const nlohmann::json &j, Emoji &m); friend void from_json(const nlohmann::json &j, Emoji &m);
friend void to_json(nlohmann::json &j, const Emoji &m);
std::string GetURL() const; std::string GetURL() const;
static std::string URLFromID(std::string emoji_id); static std::string URLFromID(std::string emoji_id);

View File

@@ -152,6 +152,18 @@ void to_json(nlohmann::json &j, const MessageReferenceData &m) {
JS_IF("guild_id", m.GuildID); JS_IF("guild_id", m.GuildID);
} }
void from_json(const nlohmann::json &j, ReactionData &m) {
JS_D("count", m.Count);
JS_D("me", m.HasReactedWith);
JS_D("emoji", m.Emoji);
}
void to_json(nlohmann::json &j, const ReactionData &m) {
j["count"] = m.Count;
j["me"] = m.HasReactedWith;
j["emoji"] = m.Emoji;
}
void from_json(const nlohmann::json &j, Message &m) { void from_json(const nlohmann::json &j, Message &m) {
JS_D("id", m.ID); JS_D("id", m.ID);
JS_D("channel_id", m.ChannelID); JS_D("channel_id", m.ChannelID);
@@ -170,7 +182,7 @@ void from_json(const nlohmann::json &j, Message &m) {
// JS_O("mention_channels", m.MentionChannels); // JS_O("mention_channels", m.MentionChannels);
JS_D("attachments", m.Attachments); JS_D("attachments", m.Attachments);
JS_D("embeds", m.Embeds); JS_D("embeds", m.Embeds);
// JS_O("reactions", m.Reactions); JS_O("reactions", m.Reactions);
JS_O("nonce", m.Nonce); JS_O("nonce", m.Nonce);
JS_D("pinned", m.IsPinned); JS_D("pinned", m.IsPinned);
JS_O("webhook_id", m.WebhookID); JS_O("webhook_id", m.WebhookID);

View File

@@ -3,6 +3,7 @@
#include "json.hpp" #include "json.hpp"
#include "user.hpp" #include "user.hpp"
#include "sticker.hpp" #include "sticker.hpp"
#include "emoji.hpp"
#include <string> #include <string>
#include <vector> #include <vector>
@@ -140,6 +141,15 @@ struct MessageReferenceData {
friend void to_json(nlohmann::json &j, const MessageReferenceData &m); friend void to_json(nlohmann::json &j, const MessageReferenceData &m);
}; };
struct ReactionData {
int Count;
bool HasReactedWith;
Emoji Emoji;
friend void from_json(const nlohmann::json &j, ReactionData &m);
friend void to_json(nlohmann::json &j, const ReactionData &m);
};
struct Message { struct Message {
Snowflake ID; Snowflake ID;
Snowflake ChannelID; Snowflake ChannelID;
@@ -156,7 +166,7 @@ struct Message {
// std::optional<std::vector<ChannelMentionData>> MentionChannels; // std::optional<std::vector<ChannelMentionData>> MentionChannels;
std::vector<AttachmentData> Attachments; std::vector<AttachmentData> Attachments;
std::vector<EmbedData> Embeds; std::vector<EmbedData> Embeds;
// std::optional<std::vector<ReactionData>> Reactions; std::optional<std::vector<ReactionData>> Reactions;
std::optional<std::string> Nonce; std::optional<std::string> Nonce;
bool IsPinned; bool IsPinned;
std::optional<Snowflake> WebhookID; std::optional<Snowflake> WebhookID;

View File

@@ -216,3 +216,20 @@ void from_json(const nlohmann::json &j, GuildRoleDeleteObject &m) {
JS_D("guild_id", m.GuildID); JS_D("guild_id", m.GuildID);
JS_D("role_id", m.RoleID); JS_D("role_id", m.RoleID);
} }
void from_json(const nlohmann::json &j, MessageReactionAddObject &m) {
JS_D("user_id", m.UserID);
JS_D("channel_id", m.ChannelID);
JS_D("message_id", m.MessageID);
JS_O("guild_id", m.GuildID);
JS_O("member", m.Member);
JS_D("emoji", m.Emoji);
}
void from_json(const nlohmann::json &j, MessageReactionRemoveObject &m) {
JS_D("user_id", m.UserID);
JS_D("channel_id", m.ChannelID);
JS_D("message_id", m.MessageID);
JS_O("guild_id", m.GuildID);
JS_D("emoji", m.Emoji);
}

View File

@@ -49,6 +49,8 @@ enum class GatewayEvent : int {
GUILD_ROLE_UPDATE, GUILD_ROLE_UPDATE,
GUILD_ROLE_CREATE, GUILD_ROLE_CREATE,
GUILD_ROLE_DELETE, GUILD_ROLE_DELETE,
MESSAGE_REACTION_ADD,
MESSAGE_REACTION_REMOVE,
}; };
struct GatewayMessage { struct GatewayMessage {
@@ -298,3 +300,24 @@ struct GuildRoleDeleteObject {
friend void from_json(const nlohmann::json &j, GuildRoleDeleteObject &m); friend void from_json(const nlohmann::json &j, GuildRoleDeleteObject &m);
}; };
struct MessageReactionAddObject {
Snowflake UserID;
Snowflake ChannelID;
Snowflake MessageID;
std::optional<Snowflake> GuildID;
std::optional<GuildMember> Member;
Emoji Emoji;
friend void from_json(const nlohmann::json &j, MessageReactionAddObject &m);
};
struct MessageReactionRemoveObject {
Snowflake UserID;
Snowflake ChannelID;
Snowflake MessageID;
std::optional<Snowflake> GuildID;
Emoji Emoji;
friend void from_json(const nlohmann::json &j, MessageReactionRemoveObject &m);
};

View File

@@ -219,9 +219,13 @@ void Store::SetMessage(Snowflake id, const Message &message) {
Bind(m_set_msg_stmt, 18, tmp); Bind(m_set_msg_stmt, 18, tmp);
} else } else
Bind(m_set_msg_stmt, 18, nullptr); Bind(m_set_msg_stmt, 18, nullptr);
if (message.Reactions.has_value()) {
Bind(m_set_msg_stmt, 19, message.IsDeleted()); std::string tmp = nlohmann::json(*message.Reactions).dump();
Bind(m_set_msg_stmt, 20, message.IsEdited()); Bind(m_set_msg_stmt, 19, tmp);
} else
Bind(m_set_msg_stmt, 19, nullptr);
Bind(m_set_msg_stmt, 20, message.IsDeleted());
Bind(m_set_msg_stmt, 21, message.IsEdited());
if (!RunInsert(m_set_msg_stmt)) if (!RunInsert(m_set_msg_stmt))
fprintf(stderr, "message insert failed: %s\n", sqlite3_errstr(m_db_err)); fprintf(stderr, "message insert failed: %s\n", sqlite3_errstr(m_db_err));
@@ -468,10 +472,13 @@ std::optional<Message> Store::GetMessage(Snowflake id) const {
Get(m_get_msg_stmt, 17, tmps); Get(m_get_msg_stmt, 17, tmps);
if (tmps != "") if (tmps != "")
ret.Stickers = nlohmann::json::parse(tmps).get<std::vector<Sticker>>(); ret.Stickers = nlohmann::json::parse(tmps).get<std::vector<Sticker>>();
Get(m_get_msg_stmt, 18, tmps);
if (tmps != "")
ret.Reactions = nlohmann::json::parse(tmps).get<std::vector<ReactionData>>();
bool tmpb = false; bool tmpb = false;
Get(m_get_msg_stmt, 18, tmpb);
if (tmpb) ret.SetDeleted();
Get(m_get_msg_stmt, 19, tmpb); Get(m_get_msg_stmt, 19, tmpb);
if (tmpb) ret.SetDeleted();
Get(m_get_msg_stmt, 20, tmpb);
if (tmpb) ret.SetEdited(); if (tmpb) ret.SetEdited();
Reset(m_get_msg_stmt); Reset(m_get_msg_stmt);
@@ -589,164 +596,165 @@ void Store::EndTransaction() {
bool Store::CreateTables() { bool Store::CreateTables() {
constexpr const char *create_users = R"( constexpr const char *create_users = R"(
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
username TEXT NOT NULL, username TEXT NOT NULL,
discriminator TEXT NOT NULL, discriminator TEXT NOT NULL,
avatar TEXT, avatar TEXT,
bot BOOL, bot BOOL,
system BOOL, system BOOL,
mfa BOOL, mfa BOOL,
locale TEXT, locale TEXT,
verified BOOl, verified BOOl,
email TEXT, email TEXT,
flags INTEGER, flags INTEGER,
premium INTEGER, premium INTEGER,
pubflags INTEGER pubflags INTEGER
) )
)"; )";
constexpr const char *create_permissions = R"( constexpr const char *create_permissions = R"(
CREATE TABLE IF NOT EXISTS permissions ( CREATE TABLE IF NOT EXISTS permissions (
id INTEGER NOT NULL, id INTEGER NOT NULL,
channel_id INTEGER NOT NULL, channel_id INTEGER NOT NULL,
type INTEGER NOT NULL, type INTEGER NOT NULL,
allow INTEGER NOT NULL, allow INTEGER NOT NULL,
deny INTEGER NOT NULL deny INTEGER NOT NULL
) )
)"; )";
constexpr const char *create_messages = R"( constexpr const char *create_messages = R"(
CREATE TABLE IF NOT EXISTS messages ( CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
channel_id INTEGER NOT NULL, channel_id INTEGER NOT NULL,
guild_id INTEGER, guild_id INTEGER,
author_id INTEGER NOT NULL, author_id INTEGER NOT NULL,
content TEXT NOT NULL, content TEXT NOT NULL,
timestamp TEXT NOT NULL, timestamp TEXT NOT NULL,
edited_timestamp TEXT, edited_timestamp TEXT,
tts BOOL NOT NULL, tts BOOL NOT NULL,
everyone BOOL NOT NULL, everyone BOOL NOT NULL,
mentions TEXT NOT NULL, /* json */ mentions TEXT NOT NULL, /* json */
attachments TEXT NOT NULL, /* json */ attachments TEXT NOT NULL, /* json */
embeds TEXT NOT NULL, /* json */ embeds TEXT NOT NULL, /* json */
pinned BOOL, pinned BOOL,
webhook_id INTEGER, webhook_id INTEGER,
type INTEGER, type INTEGER,
reference TEXT, /* json */ reference TEXT, /* json */
flags INTEGER, flags INTEGER,
stickers TEXT, /* json */ stickers TEXT, /* json */
/* extra */ reactions TEXT, /* json */
deleted BOOL, /* extra */
edited BOOL deleted BOOL,
) edited BOOL
)"; )
)";
constexpr const char *create_roles = R"( constexpr const char *create_roles = R"(
CREATE TABLE IF NOT EXISTS roles ( CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
color INTEGER NOT NULL, color INTEGER NOT NULL,
hoisted BOOL NOT NULL, hoisted BOOL NOT NULL,
position INTEGER NOT NULL, position INTEGER NOT NULL,
permissions INTEGER NOT NULL, permissions INTEGER NOT NULL,
managed BOOL NOT NULL, managed BOOL NOT NULL,
mentionable BOOL NOT NULL mentionable BOOL NOT NULL
) )
)"; )";
constexpr const char *create_emojis = R"( constexpr const char *create_emojis = R"(
CREATE TABLE IF NOT EXISTS emojis ( CREATE TABLE IF NOT EXISTS emojis (
id INTEGER PRIMARY KEY, /*though nullable, only custom emojis (with non-null ids) are stored*/ id INTEGER PRIMARY KEY, /*though nullable, only custom emojis (with non-null ids) are stored*/
name TEXT NOT NULL, /*same as id*/ name TEXT NOT NULL, /*same as id*/
roles TEXT, /* json */ roles TEXT, /* json */
creator_id INTEGER, creator_id INTEGER,
colons BOOL, colons BOOL,
managed BOOL, managed BOOL,
animated BOOL, animated BOOL,
available BOOL available BOOL
) )
)"; )";
constexpr const char *create_members = R"( constexpr const char *create_members = R"(
CREATE TABLE IF NOT EXISTS members ( CREATE TABLE IF NOT EXISTS members (
user_id INTEGER PRIMARY KEY, user_id INTEGER PRIMARY KEY,
guild_id INTEGER NOT NULL, guild_id INTEGER NOT NULL,
nickname TEXT, nickname TEXT,
roles TEXT NOT NULL, /* json */ roles TEXT NOT NULL, /* json */
joined_at TEXT NOT NULL, joined_at TEXT NOT NULL,
premium_since TEXT, premium_since TEXT,
deaf BOOL NOT NULL, deaf BOOL NOT NULL,
mute BOOL NOT NULL mute BOOL NOT NULL
) )
)"; )";
constexpr char *create_guilds = R"( constexpr const char *create_guilds = R"(
CREATE TABLE IF NOT EXISTS guilds ( CREATE TABLE IF NOT EXISTS guilds (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
icon TEXT NOT NULL, icon TEXT NOT NULL,
splash TEXT, splash TEXT,
owner BOOL, owner BOOL,
owner_id INTEGER NOT NULL, owner_id INTEGER NOT NULL,
permissions INTEGER, /* new */ permissions INTEGER, /* new */
voice_region TEXT, voice_region TEXT,
afk_id INTEGER, afk_id INTEGER,
afk_timeout INTEGER NOT NULL, afk_timeout INTEGER NOT NULL,
verification INTEGER NOT NULL, verification INTEGER NOT NULL,
notifications INTEGER NOT NULL, notifications INTEGER NOT NULL,
roles TEXT NOT NULL, /* json */ roles TEXT NOT NULL, /* json */
emojis TEXT NOT NULL, /* json */ emojis TEXT NOT NULL, /* json */
features TEXT NOT NULL, /* json */ features TEXT NOT NULL, /* json */
mfa INTEGER NOT NULL, mfa INTEGER NOT NULL,
application INTEGER, application INTEGER,
widget BOOL, widget BOOL,
widget_channel INTEGER, widget_channel INTEGER,
system_flags INTEGER NOT NULL, system_flags INTEGER NOT NULL,
rules_channel INTEGER, rules_channel INTEGER,
joined_at TEXT, joined_at TEXT,
large BOOL, large BOOL,
unavailable BOOL, unavailable BOOL,
member_count INTEGER, member_count INTEGER,
channels TEXT NOT NULL, /* json */ channels TEXT NOT NULL, /* json */
max_presences INTEGER, max_presences INTEGER,
max_members INTEGER, max_members INTEGER,
vanity TEXT, vanity TEXT,
description TEXT, description TEXT,
banner_hash TEXT, banner_hash TEXT,
premium_tier INTEGER NOT NULL, premium_tier INTEGER NOT NULL,
premium_count INTEGER, premium_count INTEGER,
locale TEXT NOT NULL, locale TEXT NOT NULL,
public_updates_id INTEGER, public_updates_id INTEGER,
max_video_users INTEGER, max_video_users INTEGER,
approx_members INTEGER, approx_members INTEGER,
approx_presences INTEGER, approx_presences INTEGER,
lazy BOOL lazy BOOL
) )
)"; )";
constexpr char *create_channels = R"( constexpr const char *create_channels = R"(
CREATE TABLE IF NOT EXISTS channels ( CREATE TABLE IF NOT EXISTS channels (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
type INTEGER NOT NULL, type INTEGER NOT NULL,
guild_id INTEGER, guild_id INTEGER,
position INTEGER, position INTEGER,
overwrites TEXT, /* json */ overwrites TEXT, /* json */
name TEXT, name TEXT,
topic TEXT, topic TEXT,
is_nsfw BOOL, is_nsfw BOOL,
last_message_id INTEGER, last_message_id INTEGER,
bitrate INTEGER, bitrate INTEGER,
user_limit INTEGER, user_limit INTEGER,
rate_limit INTEGER, rate_limit INTEGER,
recipients TEXT, /* json */ recipients TEXT, /* json */
icon TEXT, icon TEXT,
owner_id INTEGER, owner_id INTEGER,
application_id INTEGER, application_id INTEGER,
parent_id INTEGER, parent_id INTEGER,
last_pin_timestamp TEXT last_pin_timestamp TEXT
) )
)"; )";
m_db_err = sqlite3_exec(m_db, create_users, nullptr, nullptr, nullptr); m_db_err = sqlite3_exec(m_db, create_users, nullptr, nullptr, nullptr);
if (m_db_err != SQLITE_OK) { if (m_db_err != SQLITE_OK) {
@@ -801,84 +809,84 @@ last_pin_timestamp TEXT
bool Store::CreateStatements() { bool Store::CreateStatements() {
constexpr const char *set_user = R"( constexpr const char *set_user = R"(
REPLACE INTO users VALUES ( REPLACE INTO users VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
) )
)"; )";
constexpr const char *get_user = R"( constexpr const char *get_user = R"(
SELECT * FROM users WHERE id = ? SELECT * FROM users WHERE id = ?
)"; )";
constexpr const char *set_perm = R"( constexpr const char *set_perm = R"(
REPLACE INTO permissions VALUES ( REPLACE INTO permissions VALUES (
?, ?, ?, ?, ? ?, ?, ?, ?, ?
) )
)"; )";
constexpr const char *get_perm = R"( constexpr const char *get_perm = R"(
SELECT * FROM permissions WHERE id = ? AND channel_id = ? SELECT * FROM permissions WHERE id = ? AND channel_id = ?
)"; )";
constexpr const char *set_msg = R"( constexpr const char *set_msg = R"(
REPLACE INTO messages VALUES ( REPLACE INTO messages VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
) )
)"; )";
constexpr const char *get_msg = R"( constexpr const char *get_msg = R"(
SELECT * FROM messages WHERE id = ? SELECT * FROM messages WHERE id = ?
)"; )";
constexpr const char *set_role = R"( constexpr const char *set_role = R"(
REPLACE INTO roles VALUES ( REPLACE INTO roles VALUES (
?, ?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?
) )
)"; )";
constexpr const char *get_role = R"( constexpr const char *get_role = R"(
SELECT * FROM roles WHERE id = ? SELECT * FROM roles WHERE id = ?
)"; )";
constexpr const char *set_emoji = R"( constexpr const char *set_emoji = R"(
REPLACE INTO emojis VALUES ( REPLACE INTO emojis VALUES (
?, ?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?
) )
)"; )";
constexpr const char *get_emoji = R"( constexpr const char *get_emoji = R"(
SELECT * FROM emojis WHERE id = ? SELECT * FROM emojis WHERE id = ?
)"; )";
constexpr const char *set_member = R"( constexpr const char *set_member = R"(
REPLACE INTO members VALUES ( REPLACE INTO members VALUES (
?, ?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?
) )
)"; )";
constexpr const char *get_member = R"( constexpr const char *get_member = R"(
SELECT * FROM members WHERE user_id = ? AND guild_id = ? SELECT * FROM members WHERE user_id = ? AND guild_id = ?
)"; )";
constexpr const char *set_guild = R"( constexpr const char *set_guild = R"(
REPLACE INTO guilds VALUES ( REPLACE INTO guilds VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
) )
)"; )";
constexpr const char *get_guild = R"( constexpr const char *get_guild = R"(
SELECT * FROM guilds WHERE id = ? SELECT * FROM guilds WHERE id = ?
)"; )";
constexpr const char *set_chan = R"( constexpr const char *set_chan = R"(
REPLACE INTO channels VALUES ( REPLACE INTO channels VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
) )
)"; )";
constexpr const char *get_chan = R"( constexpr const char *get_chan = R"(
SELECT * FROM channels WHERE id = ? SELECT * FROM channels WHERE id = ?
)"; )";
m_db_err = sqlite3_prepare_v2(m_db, set_user, -1, &m_set_user_stmt, nullptr); m_db_err = sqlite3_prepare_v2(m_db, set_user, -1, &m_set_user_stmt, nullptr);
if (m_db_err != SQLITE_OK) { if (m_db_err != SQLITE_OK) {

View File

@@ -33,6 +33,13 @@ void from_json(const nlohmann::json &j, User &m) {
JS_ON("phone", m.Phone); JS_ON("phone", m.Phone);
} }
void to_json(nlohmann::json &j, const User &m) {
j["id"] = m.ID;
j["username"] = m.Username;
j["avatar"] = m.Avatar;
// rest of stuff as needed im too lazy and its probably not necessary
}
void User::update_from_json(const nlohmann::json &j, User &m) { void User::update_from_json(const nlohmann::json &j, User &m) {
JS_RD("username", m.Username); JS_RD("username", m.Username);
JS_RD("discriminator", m.Discriminator); JS_RD("discriminator", m.Discriminator);

View File

@@ -25,6 +25,7 @@ struct User {
std::string Phone; // null? std::string Phone; // null?
friend void from_json(const nlohmann::json &j, User &m); friend void from_json(const nlohmann::json &j, User &m);
friend void to_json(nlohmann::json &j, const User &m);
static void update_from_json(const nlohmann::json &j, User &m); static void update_from_json(const nlohmann::json &j, User &m);
bool HasAvatar() const; bool HasAvatar() const;

View File

@@ -211,6 +211,14 @@ Snowflake MainWindow::GetChatOldestListedMessage() {
return m_chat.GetOldestListedMessage(); return m_chat.GetOldestListedMessage();
} }
void MainWindow::UpdateChatReactionAdd(Snowflake id, const Glib::ustring &param) {
m_chat.UpdateReactions(id);
}
void MainWindow::UpdateChatReactionRemove(Snowflake id, const Glib::ustring &param) {
m_chat.UpdateReactions(id);
}
ChannelList *MainWindow::GetChannelList() { ChannelList *MainWindow::GetChannelList() {
return &m_channel_list; return &m_channel_list;
} }

View File

@@ -26,6 +26,8 @@ public:
void UpdateChatPrependHistory(const std::vector<Snowflake> &msgs); void UpdateChatPrependHistory(const std::vector<Snowflake> &msgs);
void InsertChatInput(std::string text); void InsertChatInput(std::string text);
Snowflake GetChatOldestListedMessage(); Snowflake GetChatOldestListedMessage();
void UpdateChatReactionAdd(Snowflake id, const Glib::ustring &param);
void UpdateChatReactionRemove(Snowflake id, const Glib::ustring &param);
ChannelList *GetChannelList(); ChannelList *GetChannelList();
ChatWindow *GetChatWindow(); ChatWindow *GetChatWindow();