From 0b8c83eaa1fdb0fcb610f9181145abde3d60d803 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 23 Nov 2020 20:34:09 -0500 Subject: [PATCH] progress 2 --- abaddon.cpp | 5 +- abaddon.hpp | 3 +- components/channels.cpp | 2 +- components/chatmessage.cpp | 118 ++++----- components/chatwindow.cpp | 6 +- components/memberlist.cpp | 18 +- discord/channel.cpp | 2 +- discord/channel.hpp | 2 +- discord/discord.cpp | 98 ++++---- discord/discord.hpp | 9 +- discord/emoji.hpp | 16 +- discord/message.cpp | 72 +++++- discord/message.hpp | 98 ++++---- discord/permissions.cpp | 2 +- discord/permissions.hpp | 6 +- discord/sticker.cpp | 13 +- discord/sticker.hpp | 1 + discord/store.cpp | 475 +++++++++++++++++++++++++++++++------ discord/store.hpp | 25 +- 19 files changed, 700 insertions(+), 271 deletions(-) diff --git a/abaddon.cpp b/abaddon.cpp index e833949..df63cf4 100644 --- a/abaddon.cpp +++ b/abaddon.cpp @@ -16,7 +16,8 @@ Abaddon::Abaddon() : m_settings("abaddon.ini") - , m_emojis("res/emojis.bin") { + , m_emojis("res/emojis.bin") + , m_discord(m_settings.GetSettingBool("discord", "memory_db", false)) { // stupid but easy LoadFromSettings(); m_discord.signal_gateway_ready().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReady)); @@ -360,7 +361,7 @@ void Abaddon::ActionChatDeleteMessage(Snowflake channel_id, Snowflake id) { } void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) { - const auto *msg = m_discord.GetMessage(id); + const auto msg = m_discord.GetMessage(id); EditMessageDialog dlg(*m_main_window); dlg.SetContent(msg->Content); auto response = dlg.run(); diff --git a/abaddon.hpp b/abaddon.hpp index 43df58e..74b198b 100644 --- a/abaddon.hpp +++ b/abaddon.hpp @@ -87,6 +87,8 @@ protected: void on_user_menu_open_dm(); private: + SettingsManager m_settings; + DiscordClient m_discord; std::string m_discord_token; // todo make these map snowflake to attribs @@ -101,6 +103,5 @@ private: mutable std::mutex m_mutex; Glib::RefPtr m_gtk_app; Glib::RefPtr m_css_provider; - SettingsManager m_settings; std::unique_ptr m_main_window; // wah wah cant create a gtkstylecontext fuck you }; diff --git a/components/channels.cpp b/components/channels.cpp index 354a6d0..93b9015 100644 --- a/components/channels.cpp +++ b/components/channels.cpp @@ -215,7 +215,7 @@ ChannelList::ChannelList() { // maybe will regret doing it this way auto &discord = Abaddon::Get().GetDiscordClient(); discord.signal_message_create().connect(sigc::track_obj([this, &discord](Snowflake message_id) { - const auto *message = discord.GetMessage(message_id); + const auto message = discord.GetMessage(message_id); const auto *channel = discord.GetChannel(message->ChannelID); if (channel == nullptr) return; if (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM) diff --git a/components/chatmessage.cpp b/components/chatmessage.cpp index 729c6d7..257f2c4 100644 --- a/components/chatmessage.cpp +++ b/components/chatmessage.cpp @@ -33,22 +33,22 @@ ChatMessageItemContainer::ChatMessageItemContainer() { } ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(Snowflake id) { - const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(id); - if (data == nullptr) return nullptr; + const auto data = Abaddon::Get().GetDiscordClient().GetMessage(id); + if (!data.has_value()) return nullptr; auto *container = Gtk::manage(new ChatMessageItemContainer); container->ID = data->ID; container->ChannelID = data->ChannelID; if (data->Content.size() > 0 || data->Type != MessageType::DEFAULT) { - container->m_text_component = container->CreateTextComponent(data); + container->m_text_component = container->CreateTextComponent(&*data); container->AttachGuildMenuHandler(container->m_text_component); container->m_main->add(*container->m_text_component); } // there should only ever be 1 embed (i think?) if (data->Embeds.size() == 1) { - container->m_embed_component = container->CreateEmbedComponent(data); + container->m_embed_component = container->CreateEmbedComponent(&*data); container->AttachGuildMenuHandler(container->m_embed_component); container->m_main->add(*container->m_embed_component); } @@ -83,7 +83,7 @@ ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(Snowflake id) { // this doesnt rly make sense void ChatMessageItemContainer::UpdateContent() { - const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(ID); + const auto data = Abaddon::Get().GetDiscordClient().GetMessage(ID); if (m_text_component != nullptr) UpdateTextComponent(m_text_component); @@ -93,7 +93,7 @@ void ChatMessageItemContainer::UpdateContent() { } if (data->Embeds.size() == 1) { - m_embed_component = CreateEmbedComponent(data); + m_embed_component = CreateEmbedComponent(&*data); if (m_embed_imgurl.size() > 0) { m_signal_image_load.emit(m_embed_imgurl); } @@ -115,15 +115,19 @@ void ChatMessageItemContainer::UpdateImage(std::string url, Glib::RefPtrsecond.second.Width, it->second.second.Height, w, h); - it->second.first->property_pixbuf() = buf->scale_simple(w, h, Gdk::INTERP_BILINEAR); + const auto inw = it->second.second.Width; + const auto inh = it->second.second.Height; + if (inw.has_value() && inh.has_value()) { + int w, h; + GetImageDimensions(*inw, *inh, w, h); + it->second.first->property_pixbuf() = buf->scale_simple(w, h, Gdk::INTERP_BILINEAR); + } } } void ChatMessageItemContainer::UpdateAttributes() { - const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(ID); - if (data == nullptr) return; + const auto data = Abaddon::Get().GetDiscordClient().GetMessage(ID); + if (!data.has_value()) return; const bool deleted = data->IsDeleted(); const bool edited = data->IsEdited(); @@ -176,9 +180,8 @@ Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message *data } void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) { - const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(ID); - if (data == nullptr) - return; + const auto data = Abaddon::Get().GetDiscordClient().GetMessage(ID); + if (!data.has_value()) return; auto b = tv->get_buffer(); b->set_text(""); @@ -208,21 +211,21 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const Message *data) Gtk::Box *main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); const auto &embed = data->Embeds[0]; - if (embed.Author.Name.length() > 0) { + if (embed.Author.has_value() && embed.Author->Name.has_value()) { auto *author_lbl = Gtk::manage(new Gtk::Label); author_lbl->set_halign(Gtk::ALIGN_START); author_lbl->set_line_wrap(true); author_lbl->set_line_wrap_mode(Pango::WRAP_WORD_CHAR); author_lbl->set_hexpand(false); - author_lbl->set_text(embed.Author.Name); + author_lbl->set_text(*embed.Author->Name); author_lbl->get_style_context()->add_class("embed-author"); main->pack_start(*author_lbl); } - if (embed.Title.length() > 0) { + if (embed.Title.has_value()) { auto *title_label = Gtk::manage(new Gtk::Label); title_label->set_use_markup(true); - title_label->set_markup("" + Glib::Markup::escape_text(embed.Title) + ""); + title_label->set_markup("" + Glib::Markup::escape_text(*embed.Title) + ""); title_label->set_halign(Gtk::ALIGN_CENTER); title_label->set_hexpand(false); title_label->get_style_context()->add_class("embed-title"); @@ -233,9 +236,9 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const Message *data) main->pack_start(*title_label); } - if (embed.Description.length() > 0) { + if (embed.Description.has_value()) { auto *desc_label = Gtk::manage(new Gtk::Label); - desc_label->set_text(embed.Description); + desc_label->set_text(*embed.Description); desc_label->set_line_wrap(true); desc_label->set_line_wrap_mode(Pango::WRAP_WORD_CHAR); desc_label->set_max_width_chars(50); @@ -246,7 +249,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const Message *data) } // todo: handle inline fields - if (embed.Fields.size() > 0) { + if (embed.Fields.has_value() && embed.Fields->size() > 0) { auto *flow = Gtk::manage(new Gtk::FlowBox); flow->set_orientation(Gtk::ORIENTATION_HORIZONTAL); flow->set_min_children_per_line(3); @@ -257,7 +260,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const Message *data) flow->set_selection_mode(Gtk::SELECTION_NONE); main->pack_start(*flow); - for (const auto &field : embed.Fields) { + for (const auto &field : *embed.Fields) { auto *field_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); auto *field_lbl = Gtk::manage(new Gtk::Label); auto *field_val = Gtk::manage(new Gtk::Label); @@ -287,42 +290,44 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const Message *data) } } - bool is_img = embed.Image.URL.size() > 0; - bool is_thumb = embed.Thumbnail.URL.size() > 0; - if (is_img || is_thumb) { - auto *img = Gtk::manage(new Gtk::Image); - img->set_halign(Gtk::ALIGN_CENTER); - int w, h; - if (is_img) - GetImageDimensions(embed.Image.Width, embed.Image.Height, w, h, 200, 150); - else - GetImageDimensions(embed.Thumbnail.Width, embed.Thumbnail.Height, w, h, 200, 150); - img->set_size_request(w, h); - main->pack_start(*img); - m_embed_img = img; - if (is_img) - m_embed_imgurl = embed.Image.ProxyURL; - else - m_embed_imgurl = embed.Thumbnail.ProxyURL; - Glib::signal_idle().connect(sigc::bind(sigc::mem_fun(*this, &ChatMessageItemContainer::EmitImageLoad), m_embed_imgurl)); + if (embed.Image.has_value()) { + bool is_img = embed.Image->URL.has_value(); + bool is_thumb = embed.Thumbnail.has_value(); + if (is_img || is_thumb) { + auto *img = Gtk::manage(new Gtk::Image); + img->set_halign(Gtk::ALIGN_CENTER); + int w = 0, h = 0; + if (is_img) + GetImageDimensions(*embed.Image->Width, *embed.Image->Height, w, h, 200, 150); + else + GetImageDimensions(*embed.Thumbnail->Width, *embed.Thumbnail->Height, w, h, 200, 150); + img->set_size_request(w, h); + main->pack_start(*img); + m_embed_img = img; + if (is_img) + m_embed_imgurl = *embed.Image->ProxyURL; + else + m_embed_imgurl = *embed.Thumbnail->ProxyURL; + Glib::signal_idle().connect(sigc::bind(sigc::mem_fun(*this, &ChatMessageItemContainer::EmitImageLoad), m_embed_imgurl)); + } } - if (embed.Footer.Text.length() > 0) { + if (embed.Footer.has_value()) { auto *footer_lbl = Gtk::manage(new Gtk::Label); footer_lbl->set_halign(Gtk::ALIGN_START); footer_lbl->set_line_wrap(true); footer_lbl->set_line_wrap_mode(Pango::WRAP_WORD_CHAR); footer_lbl->set_hexpand(false); - footer_lbl->set_text(embed.Footer.Text); + footer_lbl->set_text(embed.Footer->Text); footer_lbl->get_style_context()->add_class("embed-footer"); main->pack_start(*footer_lbl); } auto style = main->get_style_context(); - if (embed.Color != -1) { + if (embed.Color.has_value()) { auto provider = Gtk::CssProvider::create(); // this seems wrong - std::string css = ".embed { border-left: 2px solid #" + IntToCSSColor(embed.Color) + "; }"; + std::string css = ".embed { border-left: 2px solid #" + IntToCSSColor(*embed.Color) + "; }"; provider->load_from_data(css); style->add_provider(provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } @@ -343,7 +348,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const Message *data) Gtk::Widget *ChatMessageItemContainer::CreateImageComponent(const AttachmentData &data) { int w, h; - GetImageDimensions(data.Width, data.Height, w, h); + GetImageDimensions(*data.Width, *data.Height, w, h); Gtk::EventBox *ev = Gtk::manage(new Gtk::EventBox); Gtk::Image *widget = Gtk::manage(new Gtk::Image); @@ -429,8 +434,8 @@ void ChatMessageItemContainer::HandleUserMentions(Gtk::TextView *tv) { replacement = "@" + Glib::Markup::escape_text(user->Username) + "#" + user->Discriminator + ""; else { const auto role_id = user->GetHoistedRole(channel->GuildID, true); - const auto *role = discord.GetRole(role_id); - if (role == nullptr) + const auto role = discord.GetRole(role_id); + if (!role.has_value()) replacement = "@" + Glib::Markup::escape_text(user->Username) + "#" + user->Discriminator + ""; else replacement = "Color) + "\">@" + Glib::Markup::escape_text(user->Username) + "#" + user->Discriminator + ""; @@ -658,7 +663,7 @@ bool ChatMessageItemContainer::OnLinkClick(GdkEventButton *ev) { void ChatMessageItemContainer::ShowMenu(GdkEvent *event) { const auto &client = Abaddon::Get().GetDiscordClient(); - const auto *data = client.GetMessage(ID); + const auto data = client.GetMessage(ID); if (data->IsDeleted()) { m_menu_delete_message->set_sensitive(false); m_menu_edit_message->set_sensitive(false); @@ -685,8 +690,8 @@ void ChatMessageItemContainer::on_menu_edit_message() { } void ChatMessageItemContainer::on_menu_copy_content() { - const auto *msg = Abaddon::Get().GetDiscordClient().GetMessage(ID); - if (msg != nullptr) + const auto msg = Abaddon::Get().GetDiscordClient().GetMessage(ID); + if (msg.has_value()) Gtk::Clipboard::get()->set_text(msg->Content); } @@ -731,13 +736,18 @@ ChatMessageHeader::ChatMessageHeader(const Message *data) { m_timestamp = Gtk::manage(new Gtk::Label); m_avatar_ev = Gtk::manage(new Gtk::EventBox); + const auto author = Abaddon::Get().GetDiscordClient().GetUser(UserID); auto &img = Abaddon::Get().GetImageManager(); - auto buf = img.GetFromURLIfCached(data->Author.GetAvatarURL()); + Glib::RefPtr buf; + if (author.has_value()) + buf = img.GetFromURLIfCached(author->GetAvatarURL()); + if (buf) m_avatar = Gtk::manage(new Gtk::Image(buf)); else { m_avatar = Gtk::manage(new Gtk::Image(img.GetPlaceholder(32))); - img.LoadFromURL(data->Author.GetAvatarURL(), sigc::mem_fun(*this, &ChatMessageHeader::OnAvatarLoad)); + if (author.has_value()) + img.LoadFromURL(author->GetAvatarURL(), sigc::mem_fun(*this, &ChatMessageHeader::OnAvatarLoad)); } get_style_context()->add_class("message-container"); @@ -816,10 +826,10 @@ void ChatMessageHeader::UpdateNameColor() { const auto role_id = discord.GetMemberHoistedRole(guild_id, UserID, true); const auto user = discord.GetUser(UserID); if (!user.has_value()) return; - const auto *role = discord.GetRole(role_id); + const auto role = discord.GetRole(role_id); std::string md; - if (role != nullptr) + if (role.has_value()) md = "" + Glib::Markup::escape_text(user->Username) + ""; else md = "" + Glib::Markup::escape_text(user->Username) + ""; diff --git a/components/chatwindow.cpp b/components/chatwindow.cpp index 1d8829c..6f9e1c6 100644 --- a/components/chatwindow.cpp +++ b/components/chatwindow.cpp @@ -157,8 +157,8 @@ ChatMessageItemContainer *ChatWindow::CreateMessageComponent(Snowflake id) { void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) { const auto &client = Abaddon::Get().GetDiscordClient(); if (!client.IsStarted()) return; // e.g. load channel and then dc - const auto *data = client.GetMessage(id); - if (data == nullptr) return; + const auto data = client.GetMessage(id); + if (!data.has_value()) return; ChatMessageHeader *last_row = nullptr; bool should_attach = false; @@ -182,7 +182,7 @@ void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) { const auto user = client.GetUser(user_id); if (!user.has_value()) return; - header = Gtk::manage(new ChatMessageHeader(data)); + header = Gtk::manage(new ChatMessageHeader(&*data)); header->signal_action_insert_mention().connect([this, user_id]() { m_signal_action_insert_mention.emit(user_id); }); diff --git a/components/memberlist.cpp b/components/memberlist.cpp index e05f64c..04bdf8a 100644 --- a/components/memberlist.cpp +++ b/components/memberlist.cpp @@ -111,7 +111,7 @@ void MemberList::UpdateMemberListInternal() { } // process all the shit first so its in proper order - std::map pos_to_role; + std::map pos_to_role; std::map> pos_to_users; std::unordered_map user_to_color; std::vector roleless_users; @@ -128,19 +128,15 @@ void MemberList::UpdateMemberListInternal() { auto pos_role = discord.GetRole(pos_role_id); auto col_role = discord.GetRole(col_role_id); - if (pos_role == nullptr) { + if (!pos_role.has_value()) { roleless_users.push_back(id); continue; }; - pos_to_role[pos_role->Position] = pos_role; + pos_to_role[pos_role->Position] = *pos_role; pos_to_users[pos_role->Position].push_back(std::move(*user)); - if (col_role != nullptr) { - if (ColorDistance(col_role->Color, 0xFFFFFF) < 15) - user_to_color[id] = 0x000000; - else - user_to_color[id] = col_role->Color; - } + if (col_role.has_value()) + user_to_color[id] = col_role->Color; } auto add_user = [this, &user_to_color](const User *data) { @@ -191,9 +187,9 @@ void MemberList::UpdateMemberListInternal() { for (auto it = pos_to_role.crbegin(); it != pos_to_role.crend(); it++) { auto pos = it->first; - auto role = it->second; + const auto &role = it->second; - add_role(role->Name); + add_role(role.Name); if (pos_to_users.find(pos) == pos_to_users.end()) continue; diff --git a/discord/channel.cpp b/discord/channel.cpp index b99971c..ca0f06a 100644 --- a/discord/channel.cpp +++ b/discord/channel.cpp @@ -42,6 +42,6 @@ void Channel::update_from_json(const nlohmann::json &j) { JS_RD("last_pin_timestamp", LastPinTimestamp); } -const PermissionOverwrite *Channel::GetOverwrite(Snowflake id) const { +std::optional Channel::GetOverwrite(Snowflake id) const { return Abaddon::Get().GetDiscordClient().GetPermissionOverwrite(ID, id); } diff --git a/discord/channel.hpp b/discord/channel.hpp index 845cffb..178f8b6 100644 --- a/discord/channel.hpp +++ b/discord/channel.hpp @@ -39,5 +39,5 @@ struct Channel { friend void from_json(const nlohmann::json &j, Channel &m); void update_from_json(const nlohmann::json &j); - const PermissionOverwrite *GetOverwrite(Snowflake id) const; + std::optional GetOverwrite(Snowflake id) const; }; diff --git a/discord/discord.cpp b/discord/discord.cpp index 3584a5b..a787c70 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -2,9 +2,10 @@ #include #include "../util.hpp" -DiscordClient::DiscordClient() +DiscordClient::DiscordClient(bool mem_store) : m_http(DiscordAPI) - , m_decompress_buf(InflateChunkSize) { + , m_decompress_buf(InflateChunkSize) + , m_store(mem_store) { m_msg_dispatch.connect(sigc::mem_fun(*this, &DiscordClient::MessageDispatch)); m_websocket.signal_message().connect(sigc::mem_fun(*this, &DiscordClient::HandleGatewayMessageRaw)); @@ -164,7 +165,7 @@ void DiscordClient::FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake }); } -const Message *DiscordClient::GetMessage(Snowflake id) const { +std::optional DiscordClient::GetMessage(Snowflake id) const { return m_store.GetMessage(id); } @@ -176,7 +177,7 @@ std::optional DiscordClient::GetUser(Snowflake id) const { return m_store.GetUser(id); } -const Role *DiscordClient::GetRole(Snowflake id) const { +std::optional DiscordClient::GetRole(Snowflake id) const { return m_store.GetRole(id); } @@ -188,7 +189,7 @@ const GuildMember *DiscordClient::GetMember(Snowflake user_id, Snowflake guild_i return m_store.GetGuildMemberData(guild_id, user_id); } -const PermissionOverwrite *DiscordClient::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const { +std::optional DiscordClient::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const { return m_store.GetPermissionOverwrite(channel_id, id); } @@ -200,22 +201,22 @@ Snowflake DiscordClient::GetMemberHoistedRole(Snowflake guild_id, Snowflake user auto *data = m_store.GetGuildMemberData(guild_id, user_id); if (data == nullptr) return Snowflake::Invalid; - std::vector roles; + std::vector roles; for (const auto &id : data->Roles) { - auto *role = GetRole(id); - if (role != nullptr) { + const auto role = GetRole(id); + if (role.has_value()) { if (role->IsHoisted || (with_color && role->Color != 0)) - roles.push_back(role); + roles.push_back(*role); } } if (roles.size() == 0) return Snowflake::Invalid; - std::sort(roles.begin(), roles.end(), [this](const Role *a, const Role *b) -> bool { - return a->Position > b->Position; + std::sort(roles.begin(), roles.end(), [this](const Role &a, const Role &b) -> bool { + return a.Position > b.Position; }); - return roles[0]->ID; + return roles[0].ID; } Snowflake DiscordClient::GetMemberHighestRole(Snowflake guild_id, Snowflake user_id) const { @@ -226,9 +227,9 @@ Snowflake DiscordClient::GetMemberHighestRole(Snowflake guild_id, Snowflake user if (data->Roles.size() == 1) return data->Roles[0]; return *std::max(data->Roles.begin(), data->Roles.end(), [this](const auto &a, const auto &b) -> bool { - const auto *role_a = GetRole(*a); - const auto *role_b = GetRole(*b); - if (role_a == nullptr || role_b == nullptr) return false; // for some reason a Snowflake(0) sneaks into here + const auto role_a = GetRole(*a); + const auto role_b = GetRole(*b); + if (!role_a.has_value() || !role_b.has_value()) return false; // for some reason a Snowflake(0) sneaks into here return role_a->Position < role_b->Position; }); } @@ -241,14 +242,6 @@ std::unordered_set DiscordClient::GetUsersInGuild(Snowflake id) const return std::unordered_set(); } -std::unordered_set DiscordClient::GetRolesInGuild(Snowflake id) const { - std::unordered_set ret; - const auto &roles = m_store.GetRoles(); - for (const auto &[rid, rdata] : roles) - ret.insert(rid); - return ret; -} - std::unordered_set DiscordClient::GetChannelsInGuild(Snowflake id) const { auto it = m_guild_to_channels.find(id); if (it != m_guild_to_channels.end()) @@ -278,14 +271,14 @@ Permission DiscordClient::ComputePermissions(Snowflake member_id, Snowflake guil if (guild->OwnerID == member_id) return Permission::ALL; - const auto *everyone = GetRole(guild_id); - if (everyone == nullptr) + const auto everyone = GetRole(guild_id); + if (!everyone.has_value()) return Permission::NONE; Permission perms = everyone->Permissions; for (const auto role_id : member->Roles) { - const auto *role = GetRole(role_id); - if (role != nullptr) + const auto role = GetRole(role_id); + if (role.has_value()) perms |= role->Permissions; } @@ -305,8 +298,8 @@ Permission DiscordClient::ComputeOverwrites(Permission base, Snowflake member_id return Permission::NONE; Permission perms = base; - const auto *overwrite_everyone = GetPermissionOverwrite(channel_id, channel->GuildID); - if (overwrite_everyone != nullptr) { + const auto overwrite_everyone = GetPermissionOverwrite(channel_id, channel->GuildID); + if (overwrite_everyone.has_value()) { perms &= ~overwrite_everyone->Deny; perms |= overwrite_everyone->Allow; } @@ -314,8 +307,8 @@ Permission DiscordClient::ComputeOverwrites(Permission base, Snowflake member_id Permission allow = Permission::NONE; Permission deny = Permission::NONE; for (const auto role_id : member->Roles) { - const auto *overwrite = GetPermissionOverwrite(channel_id, role_id); - if (overwrite != nullptr) { + const auto overwrite = GetPermissionOverwrite(channel_id, role_id); + if (overwrite.has_value()) { allow |= overwrite->Allow; deny |= overwrite->Deny; } @@ -324,8 +317,8 @@ Permission DiscordClient::ComputeOverwrites(Permission base, Snowflake member_id perms &= ~deny; perms |= allow; - const auto *member_overwrite = GetPermissionOverwrite(channel_id, member_id); - if (member_overwrite != nullptr) { + const auto member_overwrite = GetPermissionOverwrite(channel_id, member_id); + if (member_overwrite.has_value()) { perms &= ~member_overwrite->Deny; perms |= member_overwrite->Allow; } @@ -338,10 +331,10 @@ bool DiscordClient::CanManageMember(Snowflake guild_id, Snowflake actor, Snowfla if (guild != nullptr && guild->OwnerID == target) return false; const auto actor_highest_id = GetMemberHighestRole(guild_id, actor); const auto target_highest_id = GetMemberHighestRole(guild_id, target); - const auto *actor_highest = GetRole(actor_highest_id); - const auto *target_highest = GetRole(target_highest_id); - if (actor_highest == nullptr) return false; - if (target_highest == nullptr) return true; + const auto actor_highest = GetRole(actor_highest_id); + const auto target_highest = GetRole(target_highest_id); + if (!actor_highest.has_value()) return false; + if (!target_highest.has_value()) return true; return actor_highest->Position > target_highest->Position; } @@ -581,6 +574,8 @@ void DiscordClient::ProcessNewGuild(Guild &guild) { return; } + m_store.BeginTransaction(); + m_store.SetGuild(guild.ID, guild); for (auto &c : guild.Channels) { c.GuildID = guild.ID; @@ -596,6 +591,8 @@ void DiscordClient::ProcessNewGuild(Guild &guild) { for (auto &e : guild.Emojis) m_store.SetEmoji(e.ID, e); + + m_store.EndTransaction(); } void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) { @@ -629,20 +626,28 @@ void DiscordClient::HandleGatewayMessageCreate(const GatewayMessage &msg) { void DiscordClient::HandleGatewayMessageDelete(const GatewayMessage &msg) { MessageDeleteData data = msg.Data; - auto *cur = m_store.GetMessage(data.ID); - if (cur != nullptr) - cur->SetDeleted(); + auto cur = m_store.GetMessage(data.ID); + if (!cur.has_value()) + return; + + cur->SetDeleted(); + m_store.SetMessage(data.ID, *cur); m_signal_message_delete.emit(data.ID, data.ChannelID); } void DiscordClient::HandleGatewayMessageDeleteBulk(const GatewayMessage &msg) { MessageDeleteBulkData data = msg.Data; + m_store.BeginTransaction(); for (const auto &id : data.IDs) { - auto *cur = m_store.GetMessage(id); - if (cur != nullptr) - cur->SetDeleted(); + auto cur = m_store.GetMessage(id); + if (!cur.has_value()) + return; + + cur->SetDeleted(); + m_store.SetMessage(id, *cur); m_signal_message_delete.emit(id, data.ChannelID); } + m_store.EndTransaction(); } void DiscordClient::HandleGatewayGuildMemberUpdate(const GatewayMessage &msg) { @@ -681,10 +686,12 @@ void DiscordClient::HandleGatewayChannelUpdate(const GatewayMessage &msg) { void DiscordClient::HandleGatewayChannelCreate(const GatewayMessage &msg) { Channel data = msg.Data; + m_store.BeginTransaction(); m_store.SetChannel(data.ID, data); m_guild_to_channels[data.GuildID].insert(data.ID); for (const auto &p : data.PermissionOverwrites) m_store.SetPermissionOverwrite(data.ID, p.ID, p); + m_store.EndTransaction(); m_signal_channel_create.emit(data.ID); } @@ -717,11 +724,12 @@ void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) { void DiscordClient::HandleGatewayMessageUpdate(const GatewayMessage &msg) { Snowflake id = msg.Data.at("id"); - auto *current = m_store.GetMessage(id); - if (current == nullptr) + auto current = m_store.GetMessage(id); + if (!current.has_value()) return; current->from_json_edited(msg.Data); + m_store.SetMessage(id, *current); m_signal_message_update.emit(id, current->ChannelID); } diff --git a/discord/discord.hpp b/discord/discord.hpp index c60f8ad..df7675a 100644 --- a/discord/discord.hpp +++ b/discord/discord.hpp @@ -55,7 +55,7 @@ public: static const constexpr char *GatewayIdentity = "Discord"; public: - DiscordClient(); + DiscordClient(bool mem_store = false); void Start(); void Stop(); bool IsStarted() const; @@ -80,18 +80,17 @@ public: void FetchInviteData(std::string code, std::function cb, std::function err); void FetchMessagesInChannel(Snowflake id, std::function &)> cb); void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function &)> cb); - const Message *GetMessage(Snowflake id) const; + std::optional GetMessage(Snowflake id) const; const Channel *GetChannel(Snowflake id) const; + std::optional GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const; std::optional GetUser(Snowflake id) const; - const Role *GetRole(Snowflake id) const; + std::optional GetRole(Snowflake id) const; const Guild *GetGuild(Snowflake id) const; const GuildMember *GetMember(Snowflake user_id, Snowflake guild_id) const; - const PermissionOverwrite *GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const; const Emoji *GetEmoji(Snowflake id) const; Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const; Snowflake GetMemberHighestRole(Snowflake guild_id, Snowflake user_id) const; std::unordered_set GetUsersInGuild(Snowflake id) const; - std::unordered_set GetRolesInGuild(Snowflake id) const; std::unordered_set GetChannelsInGuild(Snowflake id) const; bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const; diff --git a/discord/emoji.hpp b/discord/emoji.hpp index e4839f2..18e69d3 100644 --- a/discord/emoji.hpp +++ b/discord/emoji.hpp @@ -6,14 +6,14 @@ #include "user.hpp" struct Emoji { - Snowflake ID; // null - std::string Name; // null (in reactions) - std::vector Roles; // opt - User Creator; // opt - bool NeedsColons = false; // opt - bool IsManaged = false; // opt - bool IsAnimated = false; // opt - bool IsAvailable = false; // opt + Snowflake ID; // null + std::string Name; // null (in reactions) + std::optional> Roles; + std::optional Creator; // only reliable to access ID + std::optional NeedsColons; + std::optional IsManaged; + std::optional IsAnimated; + std::optional IsAvailable; friend void from_json(const nlohmann::json &j, Emoji &m); diff --git a/discord/message.cpp b/discord/message.cpp index 66b806f..d0f2082 100644 --- a/discord/message.cpp +++ b/discord/message.cpp @@ -1,11 +1,24 @@ #include "message.hpp" +void to_json(nlohmann::json &j, const EmbedFooterData &m) { + j["text"] = m.Text; + JS_IF("icon_url", m.IconURL); + JS_IF("proxy_icon_url", m.ProxyIconURL); +} + void from_json(const nlohmann::json &j, EmbedFooterData &m) { JS_D("text", m.Text); JS_O("icon_url", m.IconURL); JS_O("proxy_icon_url", m.ProxyIconURL); } +void to_json(nlohmann::json &j, const EmbedImageData &m) { + JS_IF("url", m.URL); + JS_IF("proxy_url", m.ProxyURL); + JS_IF("height", m.Height); + JS_IF("width", m.Width); +} + void from_json(const nlohmann::json &j, EmbedImageData &m) { JS_O("url", m.URL); JS_O("proxy_url", m.ProxyURL); @@ -13,6 +26,13 @@ void from_json(const nlohmann::json &j, EmbedImageData &m) { JS_O("width", m.Width); } +void to_json(nlohmann::json &j, const EmbedThumbnailData &m) { + JS_IF("url", m.URL); + JS_IF("proxy_url", m.ProxyURL); + JS_IF("height", m.Height); + JS_IF("width", m.Width); +} + void from_json(const nlohmann::json &j, EmbedThumbnailData &m) { JS_O("url", m.URL); JS_O("proxy_url", m.ProxyURL); @@ -20,17 +40,35 @@ void from_json(const nlohmann::json &j, EmbedThumbnailData &m) { JS_O("width", m.Width); } +void to_json(nlohmann::json &j, const EmbedVideoData &m) { + JS_IF("url", m.URL); + JS_IF("height", m.Height); + JS_IF("width", m.Width); +} + void from_json(const nlohmann::json &j, EmbedVideoData &m) { JS_O("url", m.URL); JS_O("height", m.Height); JS_O("width", m.Width); } +void to_json(nlohmann::json &j, const EmbedProviderData &m) { + JS_IF("name", m.Name); + JS_IF("url", m.URL); +} + void from_json(const nlohmann::json &j, EmbedProviderData &m) { JS_O("name", m.Name); JS_ON("url", m.URL); } +void to_json(nlohmann::json &j, const EmbedAuthorData &m) { + JS_IF("name", m.Name); + JS_IF("url", m.URL); + JS_IF("icon_url", m.IconURL); + JS_IF("proxy_icon_url", m.ProxyIconURL); +} + void from_json(const nlohmann::json &j, EmbedAuthorData &m) { JS_O("name", m.Name); JS_O("url", m.URL); @@ -38,12 +76,34 @@ void from_json(const nlohmann::json &j, EmbedAuthorData &m) { JS_O("proxy_icon_url", m.ProxyIconURL); } +void to_json(nlohmann::json &j, const EmbedFieldData &m) { + j["name"] = m.Name; + j["value"] = m.Value; + JS_IF("inline", m.Inline); +} + void from_json(const nlohmann::json &j, EmbedFieldData &m) { JS_D("name", m.Name); JS_D("value", m.Value); JS_O("inline", m.Inline); } +void to_json(nlohmann::json &j, const EmbedData &m) { + JS_IF("title", m.Title); + JS_IF("type", m.Type); + JS_IF("description", m.Description); + JS_IF("url", m.URL); + JS_IF("timestamp", m.Timestamp); + JS_IF("color", m.Color); + JS_IF("footer", m.Footer); + JS_IF("image", m.Image); + JS_IF("thumbnail", m.Thumbnail); + JS_IF("video", m.Video); + JS_IF("provider", m.Provider); + JS_IF("author", m.Author); + JS_IF("fields", m.Fields); +} + void from_json(const nlohmann::json &j, EmbedData &m) { JS_O("title", m.Title); JS_O("type", m.Type); @@ -60,6 +120,16 @@ void from_json(const nlohmann::json &j, EmbedData &m) { JS_O("fields", m.Fields); } +void to_json(nlohmann::json &j, const AttachmentData &m) { + j["id"] = m.ID; + j["filename"] = m.Filename; + j["size"] = m.Bytes; + j["url"] = m.URL; + j["proxy_url"] = m.ProxyURL; + JS_IF("height", m.Height); + JS_IF("width", m.Width); +} + void from_json(const nlohmann::json &j, AttachmentData &m) { JS_D("id", m.ID); JS_D("filename", m.Filename); @@ -107,7 +177,7 @@ void from_json(const nlohmann::json &j, Message &m) { JS_D("type", m.Type); // JS_O("activity", m.Activity); // JS_O("application", m.Application); - // JS_O("message_reference", m.MessageReference); + JS_O("message_reference", m.MessageReference); JS_O("flags", m.Flags); JS_O("stickers", m.Stickers); } diff --git a/discord/message.hpp b/discord/message.hpp index 629fc9f..99c58ab 100644 --- a/discord/message.hpp +++ b/discord/message.hpp @@ -35,89 +35,99 @@ enum class MessageFlags { }; struct EmbedFooterData { - std::string Text; // - std::string IconURL; // opt - std::string ProxyIconURL; // opt + std::string Text; + std::optional IconURL; + std::optional ProxyIconURL; + friend void to_json(nlohmann::json &j, const EmbedFooterData &m); friend void from_json(const nlohmann::json &j, EmbedFooterData &m); }; struct EmbedImageData { - std::string URL; // opt - std::string ProxyURL; // opt - int Height = 0; // opt - int Width = 0; // opt + std::optional URL; + std::optional ProxyURL; + std::optional Height; + std::optional Width; + friend void to_json(nlohmann::json &j, const EmbedImageData &m); friend void from_json(const nlohmann::json &j, EmbedImageData &m); }; struct EmbedThumbnailData { - std::string URL; // opt - std::string ProxyURL; // opt - int Height = 0; // opt - int Width = 0; // opt + std::optional URL; + std::optional ProxyURL; + std::optional Height; + std::optional Width; + friend void to_json(nlohmann::json &j, const EmbedThumbnailData &m); friend void from_json(const nlohmann::json &j, EmbedThumbnailData &m); }; struct EmbedVideoData { - std::string URL; // opt - int Height = 0; // opt - int Width = 0; // opt + std::optional URL; + std::optional Height; + std::optional Width; + + friend void to_json(nlohmann::json &j, const EmbedVideoData &m); friend void from_json(const nlohmann::json &j, EmbedVideoData &m); }; struct EmbedProviderData { - std::string Name; // opt - std::string URL; // opt, null (docs wrong) + std::optional Name; + std::optional URL; // null + friend void to_json(nlohmann::json &j, const EmbedProviderData &m); friend void from_json(const nlohmann::json &j, EmbedProviderData &m); }; struct EmbedAuthorData { - std::string Name; // opt - std::string URL; // opt - std::string IconURL; // opt - std::string ProxyIconURL; // opt + std::optional Name; + std::optional URL; + std::optional IconURL; + std::optional ProxyIconURL; + friend void to_json(nlohmann::json &j, const EmbedAuthorData &m); friend void from_json(const nlohmann::json &j, EmbedAuthorData &m); }; struct EmbedFieldData { - std::string Name; // - std::string Value; // - bool Inline = false; // opt + std::string Name; + std::string Value; + std::optional Inline; + friend void to_json(nlohmann::json &j, const EmbedFieldData &m); friend void from_json(const nlohmann::json &j, EmbedFieldData &m); }; struct EmbedData { - std::string Title; // opt - std::string Type; // opt - std::string Description; // opt - std::string URL; // opt - std::string Timestamp; // opt - int Color = -1; // opt - EmbedFooterData Footer; // opt - EmbedImageData Image; // opt - EmbedThumbnailData Thumbnail; // opt - EmbedVideoData Video; // opt - EmbedProviderData Provider; // opt - EmbedAuthorData Author; // opt - std::vector Fields; // opt + std::optional Title; + std::optional Type; + std::optional Description; + std::optional URL; + std::optional Timestamp; + std::optional Color; + std::optional Footer; + std::optional Image; + std::optional Thumbnail; + std::optional Video; + std::optional Provider; + std::optional Author; + std::optional> Fields; + friend void to_json(nlohmann::json &j, const EmbedData &m); friend void from_json(const nlohmann::json &j, EmbedData &m); }; struct AttachmentData { - Snowflake ID; // - std::string Filename; // - int Bytes; // - std::string URL; // - std::string ProxyURL; // - int Height = -1; // opt, null - int Width = -1; // opt, null + Snowflake ID; + std::string Filename; + int Bytes; + std::string URL; + std::string ProxyURL; + std::optional Height; // null + std::optional Width; // null + friend void to_json(nlohmann::json &j, const AttachmentData &m); friend void from_json(const nlohmann::json &j, AttachmentData &m); }; @@ -141,7 +151,7 @@ struct Message { std::string EditedTimestamp; // null bool IsTTS; bool DoesMentionEveryone; - std::vector Mentions; + std::vector Mentions; // currently discarded in store // std::vector MentionRoles; // std::optional> MentionChannels; std::vector Attachments; diff --git a/discord/permissions.cpp b/discord/permissions.cpp index f181e13..63eeb9f 100644 --- a/discord/permissions.cpp +++ b/discord/permissions.cpp @@ -3,7 +3,7 @@ void from_json(const nlohmann::json &j, PermissionOverwrite &m) { JS_D("id", m.ID); std::string tmp; - m.ID = j.at("type").get() == 0 ? PermissionOverwrite::ROLE : PermissionOverwrite::MEMBER; + m.Type = j.at("type").get() == 0 ? PermissionOverwrite::ROLE : PermissionOverwrite::MEMBER; JS_D("allow", tmp); m.Allow = static_cast(std::stoull(tmp)); JS_D("deny", tmp); diff --git a/discord/permissions.hpp b/discord/permissions.hpp index 65a0daa..715883a 100644 --- a/discord/permissions.hpp +++ b/discord/permissions.hpp @@ -46,9 +46,9 @@ struct Bitwise { }; struct PermissionOverwrite { - enum OverwriteType { - ROLE, - MEMBER, + enum OverwriteType : uint8_t { + ROLE = 0, + MEMBER = 1, }; Snowflake ID; diff --git a/discord/sticker.cpp b/discord/sticker.cpp index 1d59e82..9eca852 100644 --- a/discord/sticker.cpp +++ b/discord/sticker.cpp @@ -1,12 +1,23 @@ #include "sticker.hpp" +void to_json(nlohmann::json &j, const Sticker &m) { + j["id"] = m.ID; + j["pack_id"] = m.PackID; + j["name"] = m.Name; + j["description"] = m.Description; + JS_IF("tags", m.Tags); + JS_IF("asset", m.AssetHash); + JS_IF("preview_asset", m.PreviewAssetHash); + j["format_type"] = m.FormatType; +} + void from_json(const nlohmann::json &j, Sticker &m) { JS_D("id", m.ID); JS_D("pack_id", m.PackID); JS_D("name", m.Name); JS_D("description", m.Description); JS_O("tags", m.Tags); - JS_D("asset", m.AssetHash); + JS_O("asset", m.AssetHash); JS_N("preview_asset", m.PreviewAssetHash); JS_D("format_type", m.FormatType); } diff --git a/discord/sticker.hpp b/discord/sticker.hpp index e510800..e6b34e6 100644 --- a/discord/sticker.hpp +++ b/discord/sticker.hpp @@ -22,6 +22,7 @@ struct Sticker { std::optional PreviewAssetHash; StickerFormatType FormatType; + friend void to_json(nlohmann::json &j, const Sticker &m); friend void from_json(const nlohmann::json &j, Sticker &m); std::string GetURL() const; diff --git a/discord/store.cpp b/discord/store.cpp index fbba477..9c9084c 100644 --- a/discord/store.cpp +++ b/discord/store.cpp @@ -1,10 +1,18 @@ #include "store.hpp" +using namespace std::literals::string_literals; + // hopefully the casting between signed and unsigned int64 doesnt cause issues -Store::Store() { - m_db_path = std::filesystem::temp_directory_path() / "abaddon-store.db"; - m_db_err = sqlite3_open(m_db_path.string().c_str(), &m_db); +Store::Store(bool mem_store) { + if (mem_store) { + m_db_path = ":memory:"; + m_db_err = sqlite3_open(":memory:", &m_db); + } else { + m_db_path = std::filesystem::temp_directory_path() / "abaddon-store.db"; + m_db_err = sqlite3_open(m_db_path.string().c_str(), &m_db); + } + if (m_db_err != SQLITE_OK) { fprintf(stderr, "error opening database: %s\n", sqlite3_errstr(m_db_err)); return; @@ -35,7 +43,8 @@ Store::~Store() { return; } - std::filesystem::remove(m_db_path); + if (m_db_path != ":memory:") + std::filesystem::remove(m_db_path); } bool Store::IsValid() const { @@ -43,10 +52,6 @@ bool Store::IsValid() const { } void Store::SetUser(Snowflake id, const User &user) { - if ((uint64_t)id == 0) { - printf("???: %s\n", user.Username.c_str()); - } - Bind(m_set_user_stmt, 1, id); Bind(m_set_user_stmt, 2, user.Username); Bind(m_set_user_stmt, 3, user.Discriminator); @@ -64,8 +69,6 @@ void Store::SetUser(Snowflake id, const User &user) { if (!RunInsert(m_set_user_stmt)) { fprintf(stderr, "user insert failed: %s\n", sqlite3_errstr(m_db_err)); } - - // m_users[id] = user; } void Store::SetChannel(Snowflake id, const Channel &channel) { @@ -77,11 +80,65 @@ void Store::SetGuild(Snowflake id, const Guild &guild) { } void Store::SetRole(Snowflake id, const Role &role) { - m_roles[id] = role; + Bind(m_set_role_stmt, 1, id); + Bind(m_set_role_stmt, 2, role.Name); + Bind(m_set_role_stmt, 3, role.Color); + Bind(m_set_role_stmt, 4, role.IsHoisted); + Bind(m_set_role_stmt, 5, role.Position); + Bind(m_set_role_stmt, 6, static_cast(role.Permissions)); + Bind(m_set_role_stmt, 7, role.IsManaged); + Bind(m_set_role_stmt, 8, role.IsMentionable); + + if (!RunInsert(m_set_role_stmt)) + fprintf(stderr, "role insert failed: %s\n", sqlite3_errstr(m_db_err)); } void Store::SetMessage(Snowflake id, const Message &message) { - m_messages[id] = message; + Bind(m_set_msg_stmt, 1, id); + Bind(m_set_msg_stmt, 2, message.ChannelID); + Bind(m_set_msg_stmt, 3, message.GuildID); + Bind(m_set_msg_stmt, 4, message.Author.ID); + Bind(m_set_msg_stmt, 5, message.Content); + Bind(m_set_msg_stmt, 6, message.Timestamp); + Bind(m_set_msg_stmt, 7, message.EditedTimestamp); + Bind(m_set_msg_stmt, 8, message.IsTTS); + Bind(m_set_msg_stmt, 9, message.DoesMentionEveryone); + Bind(m_set_msg_stmt, 10, "[]"s); // mentions + { + std::string tmp; + tmp = nlohmann::json(message.Attachments).dump(); + Bind(m_set_msg_stmt, 11, tmp); + } + { + std::string tmp = nlohmann::json(message.Embeds).dump(); + Bind(m_set_msg_stmt, 12, tmp); + } + Bind(m_set_msg_stmt, 13, message.IsPinned); + Bind(m_set_msg_stmt, 14, message.WebhookID); + Bind(m_set_msg_stmt, 15, static_cast(message.Type)); + + if (message.MessageReference.has_value()) { + std::string tmp = nlohmann::json(*message.MessageReference).dump(); + Bind(m_set_msg_stmt, 16, tmp); + } else + Bind(m_set_msg_stmt, 16, nullptr); + + if (message.Flags.has_value()) + Bind(m_set_msg_stmt, 17, static_cast(*message.Flags)); + else + Bind(m_set_msg_stmt, 17, nullptr); + + if (message.Stickers.has_value()) { + std::string tmp = nlohmann::json(*message.Stickers).dump(); + Bind(m_set_msg_stmt, 18, tmp); + } else + Bind(m_set_msg_stmt, 18, nullptr); + + Bind(m_set_msg_stmt, 19, message.IsDeleted()); + Bind(m_set_msg_stmt, 20, message.IsEdited()); + + if (!RunInsert(m_set_msg_stmt)) + fprintf(stderr, "message insert failed: %s\n", sqlite3_errstr(m_db_err)); } void Store::SetGuildMemberData(Snowflake guild_id, Snowflake user_id, const GuildMember &data) { @@ -89,17 +146,146 @@ void Store::SetGuildMemberData(Snowflake guild_id, Snowflake user_id, const Guil } void Store::SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm) { - m_permissions[channel_id][id] = perm; + Bind(m_set_perm_stmt, 1, perm.ID); + Bind(m_set_perm_stmt, 2, channel_id); + Bind(m_set_perm_stmt, 3, static_cast(perm.Type)); + Bind(m_set_perm_stmt, 4, static_cast(perm.Allow)); + Bind(m_set_perm_stmt, 5, static_cast(perm.Deny)); + + if (!RunInsert(m_set_perm_stmt)) + fprintf(stderr, "permission insert failed: %s\n", sqlite3_errstr(m_db_err)); } void Store::SetEmoji(Snowflake id, const Emoji &emoji) { + Bind(m_set_emote_stmt, 1, id); + Bind(m_set_emote_stmt, 2, emoji.Name); + + if (emoji.Roles.has_value()) + Bind(m_set_emote_stmt, 3, nlohmann::json(*emoji.Roles).dump()); + else + Bind(m_set_emote_stmt, 3, nullptr); + + if (emoji.Creator.has_value()) + Bind(m_set_emote_stmt, 4, emoji.Creator->ID); + else + Bind(m_set_emote_stmt, 4, nullptr); + + Bind(m_set_emote_stmt, 5, emoji.NeedsColons); + Bind(m_set_emote_stmt, 6, emoji.IsManaged); + Bind(m_set_emote_stmt, 7, emoji.IsAnimated); + Bind(m_set_emote_stmt, 8, emoji.IsAvailable); + + if (!RunInsert(m_set_emote_stmt)) + fprintf(stderr, "emoji insert failed: %s\n", sqlite3_errstr(m_db_err)); + m_emojis[id] = emoji; } +std::optional Store::GetMessage(Snowflake id) const { + Bind(m_get_msg_stmt, 1, id); + if (!FetchOne(m_get_msg_stmt)) { + if (m_db_err != SQLITE_DONE) + fprintf(stderr, "error while fetching message: %s\n", sqlite3_errstr(m_db_err)); + Reset(m_get_msg_stmt); + return std::nullopt; + } + + Message ret; + ret.ID = id; + Get(m_get_msg_stmt, 1, ret.ChannelID); + Get(m_get_msg_stmt, 2, ret.GuildID); + Get(m_get_msg_stmt, 3, ret.Author.ID); // yike + Get(m_get_msg_stmt, 4, ret.Content); + Get(m_get_msg_stmt, 5, ret.Timestamp); + Get(m_get_msg_stmt, 6, ret.EditedTimestamp); + Get(m_get_msg_stmt, 7, ret.IsTTS); + Get(m_get_msg_stmt, 8, ret.DoesMentionEveryone); + std::string tmps; + Get(m_get_msg_stmt, 9, tmps); + nlohmann::json::parse(tmps).get_to(ret.Mentions); + Get(m_get_msg_stmt, 10, tmps); + nlohmann::json::parse(tmps).get_to(ret.Attachments); + Get(m_get_msg_stmt, 11, tmps); + nlohmann::json::parse(tmps).get_to(ret.Embeds); + Get(m_get_msg_stmt, 12, ret.IsPinned); + Get(m_get_msg_stmt, 13, ret.WebhookID); + uint64_t tmpi; + Get(m_get_msg_stmt, 14, tmpi); + ret.Type = static_cast(tmpi); + Get(m_get_msg_stmt, 15, tmps); + if (tmps != "") + ret.MessageReference = nlohmann::json::parse(tmps).get(); + Get(m_get_msg_stmt, 16, tmpi); + ret.Flags = static_cast(tmpi); + Get(m_get_msg_stmt, 17, tmps); + if (tmps != "") + ret.Stickers = nlohmann::json::parse(tmps).get>(); + bool tmpb = false; + Get(m_get_msg_stmt, 18, tmpb); + if (tmpb) ret.SetDeleted(); + Get(m_get_msg_stmt, 19, tmpb); + if (tmpb) ret.SetEdited(); + + Reset(m_get_msg_stmt); + + return std::optional(std::move(ret)); +} + +std::optional Store::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const { + Bind(m_get_perm_stmt, 1, id); + Bind(m_get_perm_stmt, 2, channel_id); + if (!FetchOne(m_get_perm_stmt)) { + if (m_db_err != SQLITE_DONE) + fprintf(stderr, "error while fetching permission: %s\n", sqlite3_errstr(m_db_err)); + Reset(m_get_perm_stmt); + return std::nullopt; + } + + PermissionOverwrite ret; + ret.ID = id; + uint64_t tmp; + Get(m_get_perm_stmt, 2, tmp); + ret.Type = static_cast(tmp); + Get(m_get_perm_stmt, 3, tmp); + ret.Allow = static_cast(tmp); + Get(m_get_perm_stmt, 4, tmp); + ret.Deny = static_cast(tmp); + + Reset(m_get_perm_stmt); + + return std::optional(std::move(ret)); +} + +std::optional Store::GetRole(Snowflake id) const { + Bind(m_get_role_stmt, 1, id); + if (!FetchOne(m_get_role_stmt)) { + if (m_db_err != SQLITE_DONE) + fprintf(stderr, "error while fetching role: %s\n", sqlite3_errstr(m_db_err)); + Reset(m_get_role_stmt); + return std::nullopt; + } + + Role ret; + ret.ID = id; + Get(m_get_role_stmt, 1, ret.Name); + Get(m_get_role_stmt, 2, ret.Color); + Get(m_get_role_stmt, 3, ret.IsHoisted); + Get(m_get_role_stmt, 4, ret.Position); + uint64_t tmp; + Get(m_get_role_stmt, 5, tmp); + ret.Permissions = static_cast(tmp); + Get(m_get_role_stmt, 6, ret.IsManaged); + Get(m_get_role_stmt, 7, ret.IsMentionable); + + Reset(m_get_role_stmt); + + return std::optional(std::move(ret)); +} + std::optional Store::GetUser(Snowflake id) const { Bind(m_get_user_stmt, 1, id); if (!FetchOne(m_get_user_stmt)) { - if (m_db_err != SQLITE_DONE) // not an error, just means user isnt found + if (m_db_err != SQLITE_DONE) fprintf(stderr, "error while fetching user info: %s\n", sqlite3_errstr(m_db_err)); Reset(m_get_user_stmt); return std::nullopt; @@ -153,34 +339,6 @@ const Guild *Store::GetGuild(Snowflake id) const { return &it->second; } -Role *Store::GetRole(Snowflake id) { - auto it = m_roles.find(id); - if (it == m_roles.end()) - return nullptr; - return &it->second; -} - -const Role *Store::GetRole(Snowflake id) const { - auto it = m_roles.find(id); - if (it == m_roles.end()) - return nullptr; - return &it->second; -} - -Message *Store::GetMessage(Snowflake id) { - auto it = m_messages.find(id); - if (it == m_messages.end()) - return nullptr; - return &it->second; -} - -const Message *Store::GetMessage(Snowflake id) const { - auto it = m_messages.find(id); - if (it == m_messages.end()) - return nullptr; - return &it->second; -} - GuildMember *Store::GetGuildMemberData(Snowflake guild_id, Snowflake user_id) { auto git = m_members.find(guild_id); if (git == m_members.end()) @@ -191,16 +349,6 @@ GuildMember *Store::GetGuildMemberData(Snowflake guild_id, Snowflake user_id) { return &mit->second; } -PermissionOverwrite *Store::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) { - auto cit = m_permissions.find(channel_id); - if (cit == m_permissions.end()) - return nullptr; - auto pit = cit->second.find(id); - if (pit == cit->second.end()) - return nullptr; - return &pit->second; -} - Emoji *Store::GetEmoji(Snowflake id) { auto it = m_emojis.find(id); if (it != m_emojis.end()) @@ -218,16 +366,6 @@ const GuildMember *Store::GetGuildMemberData(Snowflake guild_id, Snowflake user_ return &mit->second; } -const PermissionOverwrite *Store::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const { - auto cit = m_permissions.find(channel_id); - if (cit == m_permissions.end()) - return nullptr; - auto pit = cit->second.find(id); - if (pit == cit->second.end()) - return nullptr; - return &pit->second; -} - const Emoji *Store::GetEmoji(Snowflake id) const { auto it = m_emojis.find(id); if (it != m_emojis.end()) @@ -250,20 +388,11 @@ const Store::channels_type &Store::GetChannels() const { const Store::guilds_type &Store::GetGuilds() const { return m_guilds; } - -const Store::roles_type &Store::GetRoles() const { - return m_roles; -} - void Store::ClearAll() { m_channels.clear(); m_emojis.clear(); m_guilds.clear(); m_members.clear(); - m_messages.clear(); - m_permissions.clear(); - m_roles.clear(); - m_users.clear(); } void Store::BeginTransaction() { @@ -291,11 +420,97 @@ flags INTEGER, premium INTEGER, pubflags INTEGER ) +)"; + + constexpr const char *create_permissions = R"( +CREATE TABLE IF NOT EXISTS permissions ( +id INTEGER NOT NULL, +channel_id INTEGER NOT NULL, +type INTEGER NOT NULL, +allow INTEGER NOT NULL, +deny INTEGER NOT NULL +) +)"; + + constexpr const char *create_messages = R"( +CREATE TABLE IF NOT EXISTS messages ( +id INTEGER PRIMARY KEY, +channel_id INTEGER NOT NULL, +guild_id INTEGER, +author_id INTEGER NOT NULL, +content TEXT NOT NULL, +timestamp TEXT NOT NULL, +edited_timestamp TEXT, +tts BOOL NOT NULL, +everyone BOOL NOT NULL, +mentions TEXT NOT NULL, /* json */ +attachments TEXT NOT NULL, /* json */ +embeds TEXT NOT NULL, /* json */ +pinned BOOL, +webhook_id INTEGER, +type INTEGER, +reference TEXT, /* json */ +flags INTEGER, +stickers TEXT, /* json */ +/* extra */ +deleted BOOL, +edited BOOL +) +)"; + + constexpr const char *create_roles = R"( +CREATE TABLE IF NOT EXISTS roles ( +id INTEGER PRIMARY KEY, +name TEXT NOT NULL, +color INTEGER NOT NULL, +hoisted BOOL NOT NULL, +position INTEGER NOT NULL, +permissions INTEGER NOT NULL, +managed BOOL NOT NULL, +mentionable BOOL NOT NULL +) +)"; + + constexpr const char *create_emojis = R"( +CREATE TABLE IF NOT EXISTS emojis ( +id INTEGER PRIMARY KEY, /*though nullable, only custom emojis (with non-null ids) are stored*/ +name TEXT NOT NULL, /*same as id*/ +roles TEXT, /* json */ +creator_id INTEGER, +colons BOOL, +managed BOOL, +animated BOOL, +available BOOL +) )"; 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\n"); + fprintf(stderr, "failed to create user table: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_exec(m_db, create_permissions, nullptr, nullptr, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to create permissions table: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_exec(m_db, create_messages, nullptr, nullptr, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to create messages table: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_exec(m_db, create_roles, nullptr, nullptr, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to create roles table: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_exec(m_db, create_emojis, nullptr, nullptr, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "faile to create emojis table: %s\n", sqlite3_errstr(m_db_err)); return false; } @@ -311,17 +526,105 @@ REPLACE INTO users VALUES ( constexpr const char *get_user = R"( SELECT * FROM users WHERE id = ? +)"; + + constexpr const char *set_perm = R"( +REPLACE INTO permissions VALUES ( + ?, ?, ?, ?, ? +) +)"; + + constexpr const char *get_perm = R"( +SELECT * FROM permissions WHERE id = ? AND channel_id = ? +)"; + + constexpr const char *set_msg = R"( +REPLACE INTO messages VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +) +)"; + + constexpr const char *get_msg = R"( +SELECT * FROM messages WHERE id = ? +)"; + + constexpr const char *set_role = R"( +REPLACE INTO roles VALUES ( + ?, ?, ?, ?, ?, ?, ?, ? +) +)"; + + constexpr const char *get_role = R"( +SELECT * FROM roles WHERE id = ? +)"; + + constexpr const char *set_emoji = R"( +REPLACE INTO emojis VALUES ( + ?, ?, ?, ?, ?, ?, ?, ? +) +)"; + + constexpr const char *get_emoji = R"( +SELECT * FROM emojis WHERE id = ? )"; 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\n"); + fprintf(stderr, "failed to prepare set user statement: %s\n", sqlite3_errstr(m_db_err)); return false; } m_db_err = sqlite3_prepare_v2(m_db, get_user, -1, &m_get_user_stmt, nullptr); if (m_db_err != SQLITE_OK) { - fprintf(stderr, "failed to prepare get user statement\n"); + fprintf(stderr, "failed to prepare get user statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, set_perm, -1, &m_set_perm_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare set permission statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, get_perm, -1, &m_get_perm_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare get permission statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, set_msg, -1, &m_set_msg_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare set message statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, get_msg, -1, &m_get_msg_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare get message statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, set_role, -1, &m_set_role_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare set role statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, get_role, -1, &m_get_role_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare get role statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, set_emoji, -1, &m_set_emote_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare set emoji statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, get_emoji, -1, &m_get_emote_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare get emoji statement: %s\n", sqlite3_errstr(m_db_err)); return false; } @@ -331,6 +634,14 @@ SELECT * FROM users WHERE id = ? void Store::Cleanup() { sqlite3_finalize(m_set_user_stmt); sqlite3_finalize(m_get_user_stmt); + sqlite3_finalize(m_set_perm_stmt); + sqlite3_finalize(m_get_perm_stmt); + sqlite3_finalize(m_set_msg_stmt); + sqlite3_finalize(m_get_msg_stmt); + sqlite3_finalize(m_set_role_stmt); + sqlite3_finalize(m_get_role_stmt); + sqlite3_finalize(m_set_emote_stmt); + sqlite3_finalize(m_get_emote_stmt); } void Store::Bind(sqlite3_stmt *stmt, int index, int num) const { @@ -348,7 +659,7 @@ void Store::Bind(sqlite3_stmt *stmt, int index, uint64_t num) const { } void Store::Bind(sqlite3_stmt *stmt, int index, const std::string &str) const { - m_db_err = sqlite3_bind_text(stmt, index, str.c_str(), -1, nullptr); + m_db_err = sqlite3_bind_blob(stmt, index, str.c_str(), str.length(), SQLITE_TRANSIENT); if (m_db_err != SQLITE_OK) { fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err)); } @@ -361,10 +672,16 @@ void Store::Bind(sqlite3_stmt *stmt, int index, bool val) const { } } +void Store::Bind(sqlite3_stmt *stmt, int index, std::nullptr_t) const { + m_db_err = sqlite3_bind_null(stmt, index); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err)); + } +} + bool Store::RunInsert(sqlite3_stmt *stmt) { m_db_err = sqlite3_step(stmt); - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); + Reset(stmt); return m_db_err == SQLITE_DONE; } @@ -377,6 +694,10 @@ void Store::Get(sqlite3_stmt *stmt, int index, int &out) const { out = sqlite3_column_int(stmt, index); } +void Store::Get(sqlite3_stmt *stmt, int index, uint64_t &out) const { + out = sqlite3_column_int64(stmt, index); +} + void Store::Get(sqlite3_stmt *stmt, int index, std::string &out) const { const unsigned char *ptr = sqlite3_column_text(stmt, index); if (ptr == nullptr) diff --git a/discord/store.hpp b/discord/store.hpp index c0cd6b9..3332d70 100644 --- a/discord/store.hpp +++ b/discord/store.hpp @@ -12,7 +12,7 @@ class Store { public: - Store(); + Store(bool mem_store = false); ~Store(); bool IsValid() const; @@ -30,18 +30,15 @@ public: Channel *GetChannel(Snowflake id); Guild *GetGuild(Snowflake id); - Role *GetRole(Snowflake id); - Message *GetMessage(Snowflake id); GuildMember *GetGuildMemberData(Snowflake guild_id, Snowflake user_id); - PermissionOverwrite *GetPermissionOverwrite(Snowflake channel_id, Snowflake id); Emoji *GetEmoji(Snowflake id); + std::optional GetMessage(Snowflake id) const; + std::optional GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const; + std::optional GetRole(Snowflake id) const; std::optional GetUser(Snowflake id) const; const Channel *GetChannel(Snowflake id) const; const Guild *GetGuild(Snowflake id) const; - const Role *GetRole(Snowflake id) const; - const Message *GetMessage(Snowflake id) const; const GuildMember *GetGuildMemberData(Snowflake guild_id, Snowflake user_id) const; - const PermissionOverwrite *GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const; const Emoji *GetEmoji(Snowflake id) const; void ClearGuild(Snowflake id); @@ -58,7 +55,6 @@ public: const channels_type &GetChannels() const; const guilds_type &GetGuilds() const; - const roles_type &GetRoles() const; void ClearAll(); @@ -66,13 +62,9 @@ public: void EndTransaction(); private: - users_type m_users; channels_type m_channels; guilds_type m_guilds; - roles_type m_roles; - messages_type m_messages; members_type m_members; - permission_overwrites_type m_permissions; emojis_type m_emojis; bool CreateTables(); @@ -85,6 +77,7 @@ private: void Bind(sqlite3_stmt *stmt, int index, uint64_t num) const; void Bind(sqlite3_stmt *stmt, int index, const std::string &str) const; void Bind(sqlite3_stmt *stmt, int index, bool val) const; + void Bind(sqlite3_stmt *stmt, int index, std::nullptr_t) const; bool RunInsert(sqlite3_stmt *stmt); bool FetchOne(sqlite3_stmt *stmt) const; template @@ -101,6 +94,14 @@ private: mutable int m_db_err; mutable sqlite3_stmt *m_set_user_stmt; mutable sqlite3_stmt *m_get_user_stmt; + mutable sqlite3_stmt *m_set_perm_stmt; + mutable sqlite3_stmt *m_get_perm_stmt; + mutable sqlite3_stmt *m_set_msg_stmt; + mutable sqlite3_stmt *m_get_msg_stmt; + mutable sqlite3_stmt *m_set_role_stmt; + mutable sqlite3_stmt *m_get_role_stmt; + mutable sqlite3_stmt *m_set_emote_stmt; + mutable sqlite3_stmt *m_get_emote_stmt; }; template