diff options
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | abaddon.cpp | 25 | ||||
-rw-r--r-- | abaddon.hpp | 3 | ||||
-rw-r--r-- | components/channels.cpp | 53 | ||||
-rw-r--r-- | components/chatmessage.cpp | 126 | ||||
-rw-r--r-- | components/chatwindow.cpp | 10 | ||||
-rw-r--r-- | components/memberlist.cpp | 36 | ||||
-rw-r--r-- | discord/channel.cpp | 2 | ||||
-rw-r--r-- | discord/channel.hpp | 2 | ||||
-rw-r--r-- | discord/discord.cpp | 196 | ||||
-rw-r--r-- | discord/discord.hpp | 21 | ||||
-rw-r--r-- | discord/emoji.hpp | 16 | ||||
-rw-r--r-- | discord/guild.cpp | 4 | ||||
-rw-r--r-- | discord/guild.hpp | 86 | ||||
-rw-r--r-- | discord/json.hpp | 19 | ||||
-rw-r--r-- | discord/member.hpp | 14 | ||||
-rw-r--r-- | discord/message.cpp | 72 | ||||
-rw-r--r-- | discord/message.hpp | 102 | ||||
-rw-r--r-- | discord/permissions.cpp | 2 | ||||
-rw-r--r-- | discord/permissions.hpp | 6 | ||||
-rw-r--r-- | discord/sticker.cpp | 13 | ||||
-rw-r--r-- | discord/sticker.hpp | 1 | ||||
-rw-r--r-- | discord/store.cpp | 995 | ||||
-rw-r--r-- | discord/store.hpp | 107 | ||||
-rw-r--r-- | discord/user.cpp | 32 | ||||
-rw-r--r-- | discord/user.hpp | 26 | ||||
-rw-r--r-- | util.hpp | 10 |
27 files changed, 1479 insertions, 503 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index e658193..a7f84a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,9 @@ if(NOT USE_PKGCONFIG) find_package(nlohmann_json 3.2.0 REQUIRED) + find_package(unofficial-sqlite3 CONFIG REQUIRED) + link_libraries(unofficial::sqlite3::sqlite3) + find_path(IXWEBSOCKET_INCLUDE_DIRS ixwebsocket/ixwebsocket.h) find_library(IXWEBSOCKET_LIBRARY ixwebsocket) link_libraries(${IXWEBSOCKET_LIBRARY}) diff --git a/abaddon.cpp b/abaddon.cpp index 888d04a..74355ea 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(); // todo: set user agent for non-client(?) @@ -58,7 +59,6 @@ int Abaddon::StartGTK() { m_main_window = std::make_unique<MainWindow>(); m_main_window->set_title(APP_TITLE); - m_main_window->show(); m_main_window->UpdateComponents(); // crashes for some stupid reason if i put it somewhere else @@ -115,6 +115,13 @@ int Abaddon::StartGTK() { dlg.run(); } + if (!m_discord.IsStoreValid()) { + Gtk::MessageDialog dlg(*m_main_window, "The Discord cache could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + dlg.run(); + return 1; + } + + m_main_window->show(); return m_gtk_app->run(*m_main_window); } @@ -358,7 +365,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(); @@ -374,8 +381,8 @@ void Abaddon::ActionInsertMention(Snowflake id) { void Abaddon::ActionLeaveGuild(Snowflake id) { ConfirmDialog dlg(*m_main_window); - const auto *guild = m_discord.GetGuild(id); - if (guild != nullptr) + const auto guild = m_discord.GetGuild(id); + if (guild.has_value()) dlg.SetConfirmText("Are you sure you want to leave " + guild->Name + "?"); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) @@ -384,8 +391,8 @@ void Abaddon::ActionLeaveGuild(Snowflake id) { void Abaddon::ActionKickMember(Snowflake user_id, Snowflake guild_id) { ConfirmDialog dlg(*m_main_window); - const auto *user = m_discord.GetUser(user_id); - if (user != nullptr) + const auto user = m_discord.GetUser(user_id); + if (user.has_value()) dlg.SetConfirmText("Are you sure you want to kick " + user->Username + "#" + user->Discriminator + "?"); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) @@ -394,8 +401,8 @@ void Abaddon::ActionKickMember(Snowflake user_id, Snowflake guild_id) { void Abaddon::ActionBanMember(Snowflake user_id, Snowflake guild_id) { ConfirmDialog dlg(*m_main_window); - const auto *user = m_discord.GetUser(user_id); - if (user != nullptr) + const auto user = m_discord.GetUser(user_id); + if (user.has_value()) dlg.SetConfirmText("Are you sure you want to ban " + user->Username + "#" + user->Discriminator + "?"); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) 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<Gtk::Application> m_gtk_app; Glib::RefPtr<Gtk::CssProvider> m_css_provider; - SettingsManager m_settings; std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you }; diff --git a/components/channels.cpp b/components/channels.cpp index 354a6d0..a192159 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) @@ -283,11 +283,11 @@ void ChannelList::UpdateRemoveChannel(Snowflake id) { // this is total shit void ChannelList::UpdateChannelCategory(Snowflake id) { const auto *data = Abaddon::Get().GetDiscordClient().GetChannel(id); - const auto *guild = Abaddon::Get().GetDiscordClient().GetGuild(data->GuildID); + const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(data->GuildID); auto git = m_guild_id_to_row.find(data->GuildID); if (git == m_guild_id_to_row.end()) return; auto *guild_row = git->second; - if (data == nullptr || guild == nullptr) return; + if (data == nullptr || !guild.has_value()) return; auto it = m_id_to_row.find(id); if (it == m_id_to_row.end()) return; auto row = dynamic_cast<ChannelListRowCategory *>(it->second); @@ -343,7 +343,7 @@ void ChannelList::UpdateChannelCategory(Snowflake id) { // so is this void ChannelList::UpdateChannel(Snowflake id) { const auto *data = Abaddon::Get().GetDiscordClient().GetChannel(id); - const auto *guild = Abaddon::Get().GetDiscordClient().GetGuild(data->GuildID); + const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(data->GuildID); const auto *guild_row = m_guild_id_to_row.at(data->GuildID); if (data->Type == ChannelType::GUILD_CATEGORY) { UpdateChannelCategory(id); @@ -402,7 +402,7 @@ void ChannelList::UpdateCreateChannel(Snowflake id) { UpdateCreateDMChannel(id); return; } - const auto *guild = discord.GetGuild(data->GuildID); + const auto guild = discord.GetGuild(data->GuildID); auto *guild_row = m_guild_id_to_row.at(data->GuildID); int pos = guild_row->get_index() + 1; @@ -437,8 +437,8 @@ void ChannelList::UpdateCreateChannel(Snowflake id) { void ChannelList::UpdateGuild(Snowflake id) { // the only thing changed is the row containing the guild item so just recreate it - const auto *data = Abaddon::Get().GetDiscordClient().GetGuild(id); - if (data == nullptr) return; + const auto data = Abaddon::Get().GetDiscordClient().GetGuild(id); + if (!data.has_value()) return; auto it = m_guild_id_to_row.find(id); if (it == m_guild_id_to_row.end()) return; auto *row = dynamic_cast<ChannelListRowGuild *>(it->second); @@ -447,7 +447,7 @@ void ChannelList::UpdateGuild(Snowflake id) { const bool old_collapsed = row->IsUserCollapsed; const bool old_gindex = row->GuildIndex; delete row; - auto *new_row = Gtk::manage(new ChannelListRowGuild(data)); + auto *new_row = Gtk::manage(new ChannelListRowGuild(&*data)); new_row->IsUserCollapsed = old_collapsed; new_row->GuildIndex = old_gindex; m_guild_id_to_row[new_row->ID] = new_row; @@ -523,21 +523,24 @@ void ChannelList::InsertGuildAt(Snowflake id, int pos) { }; const auto &discord = Abaddon::Get().GetDiscordClient(); - const auto *guild_data = discord.GetGuild(id); - if (guild_data == nullptr) return; + const auto guild_data = discord.GetGuild(id); + if (!guild_data.has_value()) return; std::map<int, const Channel *> orphan_channels; std::unordered_map<Snowflake, std::vector<const Channel *>> cat_to_channels; - for (const auto &channel : guild_data->Channels) { - if (channel.Type != ChannelType::GUILD_TEXT && channel.Type != ChannelType::GUILD_NEWS) continue; - - if (channel.ParentID.IsValid()) - cat_to_channels[channel.ParentID].push_back(&channel); - else - orphan_channels[channel.Position] = &channel; - } + if (guild_data->Channels.has_value()) + for (const auto &dc : *guild_data->Channels) { + const auto channel = discord.GetChannel(dc.ID); + if (channel == nullptr) continue; + if (channel->Type != ChannelType::GUILD_TEXT && channel->Type != ChannelType::GUILD_NEWS) continue; + + if (channel->ParentID.IsValid()) + cat_to_channels[channel->ParentID].push_back(&*channel); + else + orphan_channels[channel->Position] = &*channel; + } - auto *guild_row = Gtk::manage(new ChannelListRowGuild(guild_data)); + auto *guild_row = Gtk::manage(new ChannelListRowGuild(&*guild_data)); guild_row->show_all(); guild_row->IsUserCollapsed = true; guild_row->GuildIndex = m_guild_count++; @@ -558,9 +561,13 @@ void ChannelList::InsertGuildAt(Snowflake id, int pos) { // categories std::map<int, std::vector<const Channel *>> sorted_categories; - for (const auto &channel : guild_data->Channels) - if (channel.Type == ChannelType::GUILD_CATEGORY) - sorted_categories[channel.Position].push_back(&channel); + if (guild_data->Channels.has_value()) + for (const auto &dc : *guild_data->Channels) { + const auto channel = discord.GetChannel(dc.ID); + if (channel == nullptr) continue; + if (channel->Type == ChannelType::GUILD_CATEGORY) + sorted_categories[channel->Position].push_back(&*channel); + } for (auto &[pos, catvec] : sorted_categories) { std::sort(catvec.begin(), catvec.end(), [](const Channel *a, const Channel *b) { return a->ID < b->ID; }); @@ -619,7 +626,7 @@ void ChannelList::AddPrivateChannels() { } void ChannelList::UpdateListingInternal() { - std::unordered_set<Snowflake> guilds = Abaddon::Get().GetDiscordClient().GetGuildsID(); + std::unordered_set<Snowflake> guilds = Abaddon::Get().GetDiscordClient().GetGuilds(); auto children = m_list->get_children(); auto it = children.begin(); diff --git a/components/chatmessage.cpp b/components/chatmessage.cpp index 37a8cb7..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::RefPtr<Gdk::Pi auto it = m_img_loadmap.find(url); if (it != m_img_loadmap.end()) { - int w, h; - GetImageDimensions(it->second.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("<b>" + Glib::Markup::escape_text(embed.Title) + "</b>"); + title_label->set_markup("<b>" + Glib::Markup::escape_text(*embed.Title) + "</b>"); 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); @@ -416,9 +421,9 @@ void ChatMessageItemContainer::HandleUserMentions(Gtk::TextView *tv) { int mstart, mend; if (!match.fetch_pos(0, mstart, mend)) break; const Glib::ustring user_id = match.fetch(1); - const auto *user = discord.GetUser(user_id); + const auto user = discord.GetUser(user_id); const auto *channel = discord.GetChannel(ChannelID); - if (user == nullptr || channel == nullptr) { + if (!user.has_value() || channel == nullptr) { startpos = mend; continue; } @@ -429,8 +434,8 @@ void ChatMessageItemContainer::HandleUserMentions(Gtk::TextView *tv) { replacement = "<b>@" + Glib::Markup::escape_text(user->Username) + "#" + user->Discriminator + "</b>"; 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 = "<b>@" + Glib::Markup::escape_text(user->Username) + "#" + user->Discriminator + "</b>"; else replacement = "<b><span color=\"#" + IntToCSSColor(role->Color) + "\">@" + Glib::Markup::escape_text(user->Username) + "#" + user->Discriminator + "</span></b>"; @@ -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<Gdk::Pixbuf> 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"); @@ -814,12 +824,12 @@ void ChatMessageHeader::UpdateNameColor() { const auto &discord = Abaddon::Get().GetDiscordClient(); const auto guild_id = discord.GetChannel(ChannelID)->GuildID; const auto role_id = discord.GetMemberHoistedRole(guild_id, UserID, true); - const auto *user = discord.GetUser(UserID); - if (user == nullptr) return; - const auto *role = discord.GetRole(role_id); + const auto user = discord.GetUser(UserID); + if (!user.has_value()) return; + const auto role = discord.GetRole(role_id); std::string md; - if (role != nullptr) + if (role.has_value()) md = "<span weight='bold' color='#" + IntToCSSColor(role->Color) + "'>" + Glib::Markup::escape_text(user->Username) + "</span>"; else md = "<span weight='bold' color='#eeeeee'>" + Glib::Markup::escape_text(user->Username) + "</span>"; diff --git a/components/chatwindow.cpp b/components/chatwindow.cpp index 7a9842d..28d3fce 100644 --- a/components/chatwindow.cpp +++ b/components/chatwindow.cpp @@ -158,8 +158,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; @@ -180,10 +180,10 @@ void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) { } else { const auto guild_id = client.GetChannel(m_active_channel)->GuildID; const auto user_id = data->Author.ID; - const auto *user = client.GetUser(user_id); - if (user == nullptr) return; + 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 e04c188..04bdf8a 100644 --- a/components/memberlist.cpp +++ b/components/memberlist.cpp @@ -111,14 +111,14 @@ void MemberList::UpdateMemberListInternal() { } // process all the shit first so its in proper order - std::map<int, const Role *> pos_to_role; - std::map<int, std::vector<const User *>> pos_to_users; + std::map<int, Role> pos_to_role; + std::map<int, std::vector<User>> pos_to_users; std::unordered_map<Snowflake, int> user_to_color; std::vector<Snowflake> roleless_users; for (const auto &id : ids) { - auto *user = discord.GetUser(id); - if (user == nullptr) { + auto user = discord.GetUser(id); + if (!user.has_value()) { roleless_users.push_back(id); continue; } @@ -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_users[pos_role->Position].push_back(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; - } + pos_to_role[pos_role->Position] = *pos_role; + pos_to_users[pos_role->Position].push_back(std::move(*user)); + if (col_role.has_value()) + user_to_color[id] = col_role->Color; } auto add_user = [this, &user_to_color](const User *data) { @@ -191,17 +187,17 @@ 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; auto &users = pos_to_users.at(pos); - AlphabeticalSort(users.begin(), users.end(), [](auto e) { return e->Username; }); + AlphabeticalSort(users.begin(), users.end(), [](const auto &e) { return e.Username; }); for (const auto data : users) - add_user(data); + add_user(&data); } if (chan->Type == ChannelType::DM || chan->Type == ChannelType::GROUP_DM) @@ -209,9 +205,9 @@ void MemberList::UpdateMemberListInternal() { else add_role("@everyone"); for (const auto &id : roleless_users) { - const auto *user = discord.GetUser(id); - if (user != nullptr) - add_user(user); + const auto user = discord.GetUser(id); + if (user.has_value()) + add_user(&*user); } } 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<PermissionOverwrite> 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<PermissionOverwrite> GetOverwrite(Snowflake id) const; }; diff --git a/discord/discord.cpp b/discord/discord.cpp index 30b0345..50446a7 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -2,9 +2,10 @@ #include <cassert> #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)); @@ -47,22 +48,18 @@ bool DiscordClient::IsStarted() const { return m_client_connected; } -std::unordered_set<Snowflake> DiscordClient::GetGuildsID() const { - const auto &guilds = m_store.GetGuilds(); - std::unordered_set<Snowflake> ret; - for (const auto &[gid, data] : guilds) - ret.insert(gid); - return ret; -} - -const Store::guilds_type &DiscordClient::GetGuilds() const { - return m_store.GetGuilds(); +bool DiscordClient::IsStoreValid() const { + return m_store.IsValid(); } const UserSettings &DiscordClient::GetUserSettings() const { return m_user_settings; } +std::unordered_set<Snowflake> DiscordClient::GetGuilds() const { + return m_store.GetGuilds(); +} + const User &DiscordClient::GetUserData() const { return m_user_data; } @@ -71,7 +68,7 @@ std::vector<Snowflake> DiscordClient::GetUserSortedGuilds() const { // sort order is unfolder'd guilds sorted by id descending, then guilds in folders in array order // todo: make sure folder'd guilds are sorted properly std::vector<Snowflake> folder_order; - auto guilds = GetGuildsID(); + auto guilds = GetGuilds(); for (const auto &entry : m_user_settings.GuildFolders) { // can contain guilds not a part of for (const auto &id : entry.GuildIDs) { if (std::find(guilds.begin(), guilds.end(), id) != guilds.end()) @@ -121,6 +118,8 @@ void DiscordClient::FetchMessagesInChannel(Snowflake id, std::function<void(cons std::vector<Snowflake> ids; nlohmann::json::parse(r.text).get_to(msgs); + + m_store.BeginTransaction(); for (const auto &msg : msgs) { m_store.SetMessage(msg.ID, msg); AddMessageToChannel(msg.ID, id); @@ -128,6 +127,7 @@ void DiscordClient::FetchMessagesInChannel(Snowflake id, std::function<void(cons AddUserToGuild(msg.Author.ID, *msg.GuildID); ids.push_back(msg.ID); } + m_store.EndTransaction(); cb(ids); }); @@ -142,6 +142,8 @@ void DiscordClient::FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake std::vector<Snowflake> ids; nlohmann::json::parse(r.text).get_to(msgs); + + m_store.BeginTransaction(); for (const auto &msg : msgs) { m_store.SetMessage(msg.ID, msg); AddMessageToChannel(msg.ID, channel_id); @@ -149,12 +151,13 @@ void DiscordClient::FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake AddUserToGuild(msg.Author.ID, *msg.GuildID); ids.push_back(msg.ID); } + m_store.EndTransaction(); cb(ids); }); } -const Message *DiscordClient::GetMessage(Snowflake id) const { +std::optional<Message> DiscordClient::GetMessage(Snowflake id) const { return m_store.GetMessage(id); } @@ -162,63 +165,63 @@ const Channel *DiscordClient::GetChannel(Snowflake id) const { return m_store.GetChannel(id); } -const User *DiscordClient::GetUser(Snowflake id) const { +std::optional<User> DiscordClient::GetUser(Snowflake id) const { return m_store.GetUser(id); } -const Role *DiscordClient::GetRole(Snowflake id) const { +std::optional<Role> DiscordClient::GetRole(Snowflake id) const { return m_store.GetRole(id); } -const Guild *DiscordClient::GetGuild(Snowflake id) const { +std::optional<Guild> DiscordClient::GetGuild(Snowflake id) const { return m_store.GetGuild(id); } -const GuildMember *DiscordClient::GetMember(Snowflake user_id, Snowflake guild_id) const { - return m_store.GetGuildMemberData(guild_id, user_id); +std::optional<GuildMember> DiscordClient::GetMember(Snowflake user_id, Snowflake guild_id) const { + return m_store.GetGuildMember(guild_id, user_id); } -const PermissionOverwrite *DiscordClient::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const { +std::optional<PermissionOverwrite> DiscordClient::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const { return m_store.GetPermissionOverwrite(channel_id, id); } -const Emoji *DiscordClient::GetEmoji(Snowflake id) const { +std::optional<Emoji> DiscordClient::GetEmoji(Snowflake id) const { return m_store.GetEmoji(id); } Snowflake DiscordClient::GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color) const { - auto *data = m_store.GetGuildMemberData(guild_id, user_id); - if (data == nullptr) return Snowflake::Invalid; + const auto data = GetMember(guild_id, user_id); + if (!data.has_value()) return Snowflake::Invalid; - std::vector<const Role *> roles; + std::vector<Role> 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 { - const auto *data = GetMember(user_id, guild_id); - if (data == nullptr) return Snowflake::Invalid; + const auto data = GetMember(user_id, guild_id); + if (!data.has_value()) return Snowflake::Invalid; if (data->Roles.size() == 0) return Snowflake::Invalid; 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; }); } @@ -231,14 +234,6 @@ std::unordered_set<Snowflake> DiscordClient::GetUsersInGuild(Snowflake id) const return std::unordered_set<Snowflake>(); } -std::unordered_set<Snowflake> DiscordClient::GetRolesInGuild(Snowflake id) const { - std::unordered_set<Snowflake> ret; - const auto &roles = m_store.GetRoles(); - for (const auto &[rid, rdata] : roles) - ret.insert(rid); - return ret; -} - std::unordered_set<Snowflake> DiscordClient::GetChannelsInGuild(Snowflake id) const { auto it = m_guild_to_channels.find(id); if (it != m_guild_to_channels.end()) @@ -260,22 +255,22 @@ bool DiscordClient::HasChannelPermission(Snowflake user_id, Snowflake channel_id } Permission DiscordClient::ComputePermissions(Snowflake member_id, Snowflake guild_id) const { - const auto *member = GetMember(member_id, guild_id); - const auto *guild = GetGuild(guild_id); - if (member == nullptr || guild == nullptr) + const auto member = GetMember(member_id, guild_id); + const auto guild = GetGuild(guild_id); + if (!member.has_value() || !guild.has_value()) return Permission::NONE; 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; } @@ -290,13 +285,13 @@ Permission DiscordClient::ComputeOverwrites(Permission base, Snowflake member_id return Permission::ALL; const auto *channel = GetChannel(channel_id); - const auto *member = GetMember(member_id, channel->GuildID); - if (member == nullptr || channel == nullptr) + const auto member = GetMember(member_id, channel->GuildID); + if (!member.has_value() || channel == nullptr) 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; } @@ -304,8 +299,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; } @@ -314,8 +309,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; } @@ -324,14 +319,14 @@ Permission DiscordClient::ComputeOverwrites(Permission base, Snowflake member_id } bool DiscordClient::CanManageMember(Snowflake guild_id, Snowflake actor, Snowflake target) const { - const auto *guild = GetGuild(guild_id); - if (guild != nullptr && guild->OwnerID == target) return false; + const auto guild = GetGuild(guild_id); + if (guild.has_value() && 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; } @@ -576,21 +571,26 @@ void DiscordClient::ProcessNewGuild(Guild &guild) { return; } + m_store.BeginTransaction(); + m_store.SetGuild(guild.ID, guild); - for (auto &c : guild.Channels) { - c.GuildID = guild.ID; - m_store.SetChannel(c.ID, c); - m_guild_to_channels[guild.ID].insert(c.ID); - for (auto &p : c.PermissionOverwrites) { - m_store.SetPermissionOverwrite(c.ID, p.ID, p); + if (guild.Channels.has_value()) + for (auto &c : *guild.Channels) { + c.GuildID = guild.ID; + m_store.SetChannel(c.ID, c); + m_guild_to_channels[guild.ID].insert(c.ID); + for (auto &p : c.PermissionOverwrites) { + m_store.SetPermissionOverwrite(c.ID, p.ID, p); + } } - } for (auto &r : guild.Roles) m_store.SetRole(r.ID, r); for (auto &e : guild.Emojis) m_store.SetEmoji(e.ID, e); + + m_store.EndTransaction(); } void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) { @@ -599,11 +599,13 @@ void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) { for (auto &g : data.Guilds) ProcessNewGuild(g); + m_store.BeginTransaction(); for (const auto &dm : data.PrivateChannels) { m_store.SetChannel(dm.ID, dm); for (const auto &recipient : dm.Recipients) m_store.SetUser(recipient.ID, recipient); } + m_store.EndTransaction(); m_session_id = data.SessionID; m_user_data = data.User; @@ -622,33 +624,43 @@ 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) { GuildMemberUpdateMessage data = msg.Data; auto member = GuildMember::from_update_json(msg.Data); // meh - m_store.SetGuildMemberData(data.GuildID, data.User.ID, member); + m_store.SetGuildMember(data.GuildID, data.User.ID, member); } void DiscordClient::HandleGatewayPresenceUpdate(const GatewayMessage &msg) { PresenceUpdateMessage data = msg.Data; auto cur = m_store.GetUser(data.User.at("id").get<Snowflake>()); - if (cur != nullptr) + if (cur.has_value()) { User::update_from_json(data.User, *cur); + m_store.SetUser(cur->ID, *cur); + } } void DiscordClient::HandleGatewayChannelDelete(const GatewayMessage &msg) { @@ -672,17 +684,19 @@ 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); } void DiscordClient::HandleGatewayGuildUpdate(const GatewayMessage &msg) { Snowflake id = msg.Data.at("id"); - auto *current = m_store.GetGuild(id); - if (current == nullptr) return; + auto current = m_store.GetGuild(id); + if (!current.has_value()) return; current->update_from_json(msg.Data); m_signal_guild_update.emit(id); } @@ -708,11 +722,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); } @@ -720,6 +735,8 @@ void DiscordClient::HandleGatewayMessageUpdate(const GatewayMessage &msg) { void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg) { GuildMemberListUpdateMessage data = msg.Data; + m_store.BeginTransaction(); + bool has_sync = false; for (const auto &op : data.Ops) { if (op.Op == "SYNC") { @@ -729,12 +746,14 @@ void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg auto member = static_cast<const GuildMemberListUpdateMessage::MemberItem *>(item.get()); m_store.SetUser(member->User.ID, member->User); AddUserToGuild(member->User.ID, data.GuildID); - m_store.SetGuildMemberData(data.GuildID, member->User.ID, member->GetAsMemberData()); + m_store.SetGuildMember(data.GuildID, member->User.ID, member->GetAsMemberData()); } } } } + m_store.EndTransaction(); + // todo: manage this event a little better if (has_sync) m_signal_guild_member_list_update.emit(data.GuildID); @@ -754,16 +773,17 @@ void DiscordClient::HandleGatewayGuildDelete(const GatewayMessage &msg) { if (unavailable) printf("guild %llu became unavailable\n", static_cast<uint64_t>(id)); - auto *guild = m_store.GetGuild(id); - if (guild == nullptr) { + const auto guild = m_store.GetGuild(id); + if (!guild.has_value()) { m_store.ClearGuild(id); m_signal_guild_delete.emit(id); return; } m_store.ClearGuild(id); - for (const auto &c : guild->Channels) - m_store.ClearChannel(c.ID); + if (guild->Channels.has_value()) + for (const auto &c : *guild->Channels) + m_store.ClearChannel(c.ID); m_signal_guild_delete.emit(id); } diff --git a/discord/discord.hpp b/discord/discord.hpp index 487903f..b915d8c 100644 --- a/discord/discord.hpp +++ b/discord/discord.hpp @@ -55,10 +55,11 @@ public: static const constexpr char *GatewayIdentity = "Discord"; public: - DiscordClient(); + DiscordClient(bool mem_store = false); void Start(); void Stop(); bool IsStarted() const; + bool IsStoreValid() const; using guilds_type = Store::guilds_type; using channels_type = Store::channels_type; @@ -68,8 +69,7 @@ public: using members_type = Store::members_type; using permission_overwrites_type = Store::permission_overwrites_type; - std::unordered_set<Snowflake> GetGuildsID() const; - const guilds_type &GetGuilds() const; + std::unordered_set<Snowflake> GetGuilds() const; const User &GetUserData() const; const UserSettings &GetUserSettings() const; std::vector<Snowflake> GetUserSortedGuilds() const; @@ -79,18 +79,17 @@ public: void FetchInviteData(std::string code, std::function<void(Invite)> cb, std::function<void(bool)> err); void FetchMessagesInChannel(Snowflake id, std::function<void(const std::vector<Snowflake> &)> cb); void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function<void(const std::vector<Snowflake> &)> cb); - const Message *GetMessage(Snowflake id) const; + std::optional<Message> GetMessage(Snowflake id) const; const Channel *GetChannel(Snowflake id) const; - const User *GetUser(Snowflake id) const; - const Role *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; + std::optional<Emoji> GetEmoji(Snowflake id) const; + std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const; + std::optional<User> GetUser(Snowflake id) const; + std::optional<Role> GetRole(Snowflake id) const; + std::optional<Guild> GetGuild(Snowflake id) const; + std::optional<GuildMember> GetMember(Snowflake user_id, Snowflake guild_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<Snowflake> GetUsersInGuild(Snowflake id) const; - std::unordered_set<Snowflake> GetRolesInGuild(Snowflake id) const; std::unordered_set<Snowflake> 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<Snowflake> 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<std::vector<Snowflake>> Roles; + std::optional<User> Creator; // only reliable to access ID + std::optional<bool> NeedsColons; + std::optional<bool> IsManaged; + std::optional<bool> IsAnimated; + std::optional<bool> IsAvailable; friend void from_json(const nlohmann::json &j, Emoji &m); diff --git a/discord/guild.cpp b/discord/guild.cpp index 054a2c3..31b4dd6 100644 --- a/discord/guild.cpp +++ b/discord/guild.cpp @@ -56,10 +56,10 @@ void from_json(const nlohmann::json &j, Guild &m) { JS_O("max_video_channel_users", m.MaxVideoChannelUsers); JS_O("approximate_member_count", tmp); if (tmp.has_value()) - m.ApproximateMemberCount = std::stoull(*tmp); + m.ApproximateMemberCount = std::stol(*tmp); JS_O("approximate_presence_count", tmp); if (tmp.has_value()) - m.ApproximatePresenceCount = std::stoull(*tmp); + m.ApproximatePresenceCount = std::stol(*tmp); } void Guild::update_from_json(const nlohmann::json &j) { diff --git a/discord/guild.hpp b/discord/guild.hpp index f0edf3f..d0863d5 100644 --- a/discord/guild.hpp +++ b/discord/guild.hpp @@ -10,53 +10,53 @@ // a bot is apparently only supposed to receive the `id` and `unavailable` as false // but user tokens seem to get the full objects (minus users) struct Guild { - Snowflake ID; // - std::string Name; // - std::string Icon; // null - std::string Splash; // null - std::string DiscoverySplash; // opt, null (docs wrong) - bool IsOwner = false; // opt - Snowflake OwnerID; // - int Permissions = 0; // opt - std::string PermissionsNew; // opt - std::string VoiceRegion; // opt - Snowflake AFKChannelID; // null - int AFKTimeout; // - bool IsEmbedEnabled = false; // opt, deprecated - Snowflake EmbedChannelID; // opt, null, deprecated - int VerificationLevel; // - int DefaultMessageNotifications; // - int ExplicitContentFilter; // - std::vector<Role> Roles; // - std::vector<Emoji> Emojis; // - std::vector<std::string> Features; // - int MFALevel; // - Snowflake ApplicationID; // null - bool IsWidgetEnabled = false; // opt - Snowflake WidgetChannelID; // opt, null - Snowflake SystemChannelID; // null - int SystemChannelFlags; // - Snowflake RulesChannelID; // null - std::string JoinedAt; // opt* - bool IsLarge = false; // opt* - bool IsUnavailable = false; // opt* - int MemberCount = 0; // opt* + Snowflake ID; + std::string Name; + std::string Icon; // null + std::string Splash; // null + std::optional<std::string> DiscoverySplash; // null + std::optional<bool> IsOwner; + Snowflake OwnerID; + std::optional<uint64_t> Permissions; + std::optional<std::string> PermissionsNew; + std::optional<std::string> VoiceRegion; + Snowflake AFKChannelID; // null + int AFKTimeout; + std::optional<bool> IsEmbedEnabled; // deprecated + std::optional<Snowflake> EmbedChannelID; // null, deprecated + int VerificationLevel; + int DefaultMessageNotifications; + int ExplicitContentFilter; + std::vector<Role> Roles; // only access id + std::vector<Emoji> Emojis; // only access id + std::vector<std::string> Features; + int MFALevel; + Snowflake ApplicationID; // null + std::optional<bool> IsWidgetEnabled; + std::optional<Snowflake> WidgetChannelID; // null + Snowflake SystemChannelID; // null + int SystemChannelFlags; + Snowflake RulesChannelID; // null + std::optional<std::string> JoinedAt; // * + std::optional<bool> IsLarge; // * + std::optional<bool> IsUnavailable; // * + std::optional<int> MemberCount; // * // std::vector<VoiceStateData> VoiceStates; // opt* // std::vector<MemberData> Members; // opt* - incomplete anyways - std::vector<Channel> Channels; // opt* + std::optional<std::vector<Channel>> Channels; // * // std::vector<PresenceUpdateData> Presences; // opt* - int MaxPresences = 0; // opt, null - int MaxMembers = 0; // opt - std::string VanityURL; // null - std::string Description; // null - std::string BannerHash; // null - int PremiumTier; // - int PremiumSubscriptionCount = 0; // opt - std::string PreferredLocale; // + std::optional<int> MaxPresences; // null + std::optional<int> MaxMembers; + std::string VanityURL; // null + std::string Description; // null + std::string BannerHash; // null + int PremiumTier; + std::optional<int> PremiumSubscriptionCount; + std::string PreferredLocale; Snowflake PublicUpdatesChannelID; // null - int MaxVideoChannelUsers = 0; // opt - int ApproximateMemberCount = 0; // opt - int ApproximatePresenceCount = 0; // opt + std::optional<int> MaxVideoChannelUsers; + std::optional<int> ApproximateMemberCount; + std::optional<int> ApproximatePresenceCount; // undocumented // std::map<std::string, Unknown> GuildHashes; diff --git a/discord/json.hpp b/discord/json.hpp index 0b27d6d..de779b9 100644 --- a/discord/json.hpp +++ b/discord/json.hpp @@ -1,17 +1,12 @@ #pragma once #include <nlohmann/json.hpp> #include <optional> +#include "../util.hpp" namespace detail { // more or less because idk what to name this stuff template<typename T> -struct is_optional : ::std::false_type {}; - -template<typename T> -struct is_optional<::std::optional<T>> : ::std::true_type {}; - -template<typename T> inline void json_direct(const ::nlohmann::json &j, const char *key, T &val) { - if constexpr (is_optional<T>::value) + if constexpr (::util::is_optional<T>::value) val = j.at(key).get<typename T::value_type>(); else j.at(key).get_to(val); @@ -19,7 +14,7 @@ inline void json_direct(const ::nlohmann::json &j, const char *key, T &val) { template<typename T> inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) { - if constexpr (is_optional<T>::value) { + if constexpr (::util::is_optional<T>::value) { if (j.contains(key)) val = j.at(key).get<typename T::value_type>(); else @@ -32,7 +27,7 @@ inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) { template<typename T> inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) { - if constexpr (is_optional<T>::value) { + if constexpr (::util::is_optional<T>::value) { const auto &at = j.at(key); if (!at.is_null()) val = at.get<typename T::value_type>(); @@ -47,7 +42,7 @@ inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) { template<typename T> inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) { - if constexpr (is_optional<T>::value) { + if constexpr (::util::is_optional<T>::value) { if (j.contains(key)) { const auto &at = j.at(key); if (!at.is_null()) @@ -68,7 +63,7 @@ inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T template<typename T> inline void json_update_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) { - if constexpr (is_optional<T>::value) { + if constexpr (::util::is_optional<T>::value) { if (j.contains(key)) { const auto &at = j.at(key); if (!at.is_null()) @@ -89,7 +84,7 @@ inline void json_update_optional_nullable(const ::nlohmann::json &j, const char template<typename T, typename U> inline void json_update_optional_nullable_default(const ::nlohmann::json &j, const char *key, T &val, const U &fallback) { - if constexpr (is_optional<T>::value) { + if constexpr (::util::is_optional<T>::value) { if (j.contains(key)) { const auto &at = j.at(key); if (at.is_null()) diff --git a/discord/member.hpp b/discord/member.hpp index 0b7e0d6..9518259 100644 --- a/discord/member.hpp +++ b/discord/member.hpp @@ -6,13 +6,13 @@ #include <vector> struct GuildMember { - User User; // opt - std::string Nickname; // null - std::vector<Snowflake> Roles; // - std::string JoinedAt; // - std::string PremiumSince; // opt, null - bool IsDeafened; // - bool IsMuted; // + std::optional<User> User; // only reliable to access id. only opt in MESSAGE_* + std::string Nickname; // null + std::vector<Snowflake> Roles; + std::string JoinedAt; + std::optional<std::string> PremiumSince; // null + bool IsDeafened; + bool IsMuted; friend void from_json(const nlohmann::json &j, GuildMember &m); static GuildMember from_update_json(const nlohmann::json &j); 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<std::string> IconURL; + std::optional<std::string> 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<std::string> URL; + std::optional<std::string> ProxyURL; + std::optional<int> Height; + std::optional<int> 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<std::string> URL; + std::optional<std::string> ProxyURL; + std::optional<int> Height; + std::optional<int> 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<std::string> URL; + std::optional<int> Height; + std::optional<int> 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<std::string> Name; + std::optional<std::string> 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<std::string> Name; + std::optional<std::string> URL; + std::optional<std::string> IconURL; + std::optional<std::string> 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<bool> 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<EmbedFieldData> Fields; // opt - + std::optional<std::string> Title; + std::optional<std::string> Type; + std::optional<std::string> Description; + std::optional<std::string> URL; + std::optional<std::string> Timestamp; + std::optional<int> Color; + std::optional<EmbedFooterData> Footer; + std::optional<EmbedImageData> Image; + std::optional<EmbedThumbnailData> Thumbnail; + std::optional<EmbedVideoData> Video; + std::optional<EmbedProviderData> Provider; + std::optional<EmbedAuthorData> Author; + std::optional<std::vector<EmbedFieldData>> 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<int> Height; // null + std::optional<int> 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<User> Mentions; + std::vector<User> Mentions; // currently discarded in store // std::vector<Role> MentionRoles; // std::optional<std::vector<ChannelMentionData>> MentionChannels; std::vector<AttachmentData> 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<int>() == 0 ? PermissionOverwrite::ROLE : PermissionOverwrite::MEMBER; + m.Type = j.at("type").get<int>() == 0 ? PermissionOverwrite::ROLE : PermissionOverwrite::MEMBER; JS_D("allow", tmp); m.Allow = static_cast<Permission>(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<Permission> { }; 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<std::string> 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 b0bb2df..12f729f 100644 --- a/discord/store.cpp +++ b/discord/store.cpp @@ -1,7 +1,74 @@ #include "store.hpp" +using namespace std::literals::string_literals; + +// hopefully the casting between signed and unsigned int64 doesnt cause issues + +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; + } + + m_db_err = sqlite3_exec(m_db, "PRAGMA journal_mode = WAL", nullptr, nullptr, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "enabling write-ahead-log failed: %s\n", sqlite3_errstr(m_db_err)); + return; + } + + m_db_err = sqlite3_exec(m_db, "PRAGMA synchronous = NORMAL", nullptr, nullptr, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "setting synchronous failed: %s\n", sqlite3_errstr(m_db_err)); + return; + } + + CreateTables(); + CreateStatements(); +} + +Store::~Store() { + Cleanup(); + + m_db_err = sqlite3_close(m_db); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "error closing database: %s\n", sqlite3_errstr(m_db_err)); + return; + } + + if (m_db_path != ":memory:") + std::filesystem::remove(m_db_path); +} + +bool Store::IsValid() const { + return m_db_err == SQLITE_OK; +} + void Store::SetUser(Snowflake id, const User &user) { - m_users[id] = user; + Bind(m_set_user_stmt, 1, id); + Bind(m_set_user_stmt, 2, user.Username); + Bind(m_set_user_stmt, 3, user.Discriminator); + Bind(m_set_user_stmt, 4, user.Avatar); + Bind(m_set_user_stmt, 5, user.IsBot); + Bind(m_set_user_stmt, 6, user.IsSystem); + Bind(m_set_user_stmt, 7, user.IsMFAEnabled); + Bind(m_set_user_stmt, 8, user.Locale); + Bind(m_set_user_stmt, 9, user.IsVerified); + Bind(m_set_user_stmt, 10, user.Email); + Bind(m_set_user_stmt, 11, user.Flags); + Bind(m_set_user_stmt, 12, user.PremiumType); + Bind(m_set_user_stmt, 13, user.PublicFlags); + + if (!RunInsert(m_set_user_stmt)) { + fprintf(stderr, "user insert failed: %s\n", sqlite3_errstr(m_db_err)); + } } void Store::SetChannel(Snowflake id, const Channel &channel) { @@ -9,41 +76,416 @@ void Store::SetChannel(Snowflake id, const Channel &channel) { } void Store::SetGuild(Snowflake id, const Guild &guild) { - m_guilds[id] = guild; + Bind(m_set_guild_stmt, 1, id); + Bind(m_set_guild_stmt, 2, guild.Name); + Bind(m_set_guild_stmt, 3, guild.Icon); + Bind(m_set_guild_stmt, 4, guild.Splash); + Bind(m_set_guild_stmt, 5, guild.IsOwner); + Bind(m_set_guild_stmt, 6, guild.OwnerID); + Bind(m_set_guild_stmt, 7, guild.PermissionsNew); + Bind(m_set_guild_stmt, 8, guild.VoiceRegion); + Bind(m_set_guild_stmt, 9, guild.AFKChannelID); + Bind(m_set_guild_stmt, 10, guild.AFKTimeout); + Bind(m_set_guild_stmt, 11, guild.VerificationLevel); + Bind(m_set_guild_stmt, 12, guild.DefaultMessageNotifications); + std::vector<Snowflake> snowflakes; + for (const auto &x : guild.Roles) snowflakes.push_back(x.ID); + Bind(m_set_guild_stmt, 13, nlohmann::json(snowflakes).dump()); + snowflakes.clear(); + for (const auto &x : guild.Emojis) snowflakes.push_back(x.ID); + Bind(m_set_guild_stmt, 14, nlohmann::json(snowflakes).dump()); + Bind(m_set_guild_stmt, 15, nlohmann::json(guild.Features).dump()); + Bind(m_set_guild_stmt, 16, guild.MFALevel); + Bind(m_set_guild_stmt, 17, guild.ApplicationID); + Bind(m_set_guild_stmt, 18, guild.IsWidgetEnabled); + Bind(m_set_guild_stmt, 19, guild.WidgetChannelID); + Bind(m_set_guild_stmt, 20, guild.SystemChannelFlags); + Bind(m_set_guild_stmt, 21, guild.RulesChannelID); + Bind(m_set_guild_stmt, 22, guild.JoinedAt); + Bind(m_set_guild_stmt, 23, guild.IsLarge); + Bind(m_set_guild_stmt, 24, guild.IsUnavailable); + Bind(m_set_guild_stmt, 25, guild.MemberCount); + if (guild.Channels.has_value()) { + snowflakes.clear(); + for (const auto &x : *guild.Channels) snowflakes.push_back(x.ID); + Bind(m_set_guild_stmt, 26, nlohmann::json(snowflakes).dump()); + } else + Bind(m_set_guild_stmt, 26, "[]"s); + Bind(m_set_guild_stmt, 27, guild.MaxPresences); + Bind(m_set_guild_stmt, 28, guild.MaxMembers); + Bind(m_set_guild_stmt, 29, guild.VanityURL); + Bind(m_set_guild_stmt, 30, guild.Description); + Bind(m_set_guild_stmt, 31, guild.BannerHash); + Bind(m_set_guild_stmt, 32, guild.PremiumTier); + Bind(m_set_guild_stmt, 33, guild.PremiumSubscriptionCount); + Bind(m_set_guild_stmt, 34, guild.PreferredLocale); + Bind(m_set_guild_stmt, 35, guild.PublicUpdatesChannelID); + Bind(m_set_guild_stmt, 36, guild.MaxVideoChannelUsers); + Bind(m_set_guild_stmt, 37, guild.ApproximateMemberCount); + Bind(m_set_guild_stmt, 38, guild.ApproximatePresenceCount); + Bind(m_set_guild_stmt, 39, guild.IsLazy); + + if (!RunInsert(m_set_guild_stmt)) + fprintf(stderr, "guild insert failed: %s\n", sqlite3_errstr(m_db_err)); + + m_guilds.insert(id); } 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<uint64_t>(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, a const char* literal will call the bool overload instead of std::string + { + 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<uint64_t>(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<uint64_t>(*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) { - m_members[guild_id][user_id] = data; +void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data) { + Bind(m_set_member_stmt, 1, user_id); + Bind(m_set_member_stmt, 2, guild_id); + Bind(m_set_member_stmt, 3, data.Nickname); + Bind(m_set_member_stmt, 4, nlohmann::json(data.Roles).dump()); + Bind(m_set_member_stmt, 5, data.JoinedAt); + Bind(m_set_member_stmt, 6, data.PremiumSince); + Bind(m_set_member_stmt, 7, data.IsDeafened); + Bind(m_set_member_stmt, 8, data.IsMuted); + + if (!RunInsert(m_set_member_stmt)) + fprintf(stderr, "member insert failed: %s\n", sqlite3_errstr(m_db_err)); } 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<int>(perm.Type)); + Bind(m_set_perm_stmt, 4, static_cast<uint64_t>(perm.Allow)); + Bind(m_set_perm_stmt, 5, static_cast<uint64_t>(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) { - m_emojis[id] = 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)); } -User *Store::GetUser(Snowflake id) { - auto it = m_users.find(id); - if (it == m_users.end()) - return nullptr; - return &it->second; +std::optional<Emoji> Store::GetEmoji(Snowflake id) const { + Bind(m_get_emote_stmt, 1, id); + if (!FetchOne(m_get_emote_stmt)) { + if (m_db_err != SQLITE_DONE) + fprintf(stderr, "error while fetching emoji: %s\n", sqlite3_errstr(m_db_err)); + Reset(m_get_emote_stmt); + return std::nullopt; + } + + Emoji ret; + ret.ID = id; + Get(m_get_emote_stmt, 1, ret.Name); + std::string tmp; + Get(m_get_emote_stmt, 2, tmp); + ret.Roles = nlohmann::json::parse(tmp).get<std::vector<Snowflake>>(); + ret.Creator = std::optional<User>(User()); + Get(m_get_emote_stmt, 3, ret.Creator->ID); + Get(m_get_emote_stmt, 3, ret.NeedsColons); + Get(m_get_emote_stmt, 4, ret.IsManaged); + Get(m_get_emote_stmt, 5, ret.IsAnimated); + Get(m_get_emote_stmt, 6, ret.IsAvailable); + + Reset(m_get_emote_stmt); + + return ret; } -const User *Store::GetUser(Snowflake id) const { - auto it = m_users.find(id); - if (it == m_users.end()) - return nullptr; - return &it->second; +std::optional<Guild> Store::GetGuild(Snowflake id) const { + Bind(m_get_guild_stmt, 1, id); + if (!FetchOne(m_get_guild_stmt)) { + if (m_db_err != SQLITE_DONE) + fprintf(stderr, "error while fetching guild: %s\n", sqlite3_errstr(m_db_err)); + Reset(m_get_guild_stmt); + return std::nullopt; + } + + Guild ret; + ret.ID = id; + Get(m_get_guild_stmt, 1, ret.Name); + Get(m_get_guild_stmt, 2, ret.Icon); + Get(m_get_guild_stmt, 3, ret.Splash); + Get(m_get_guild_stmt, 4, ret.IsOwner); + Get(m_get_guild_stmt, 5, ret.OwnerID); + Get(m_get_guild_stmt, 6, ret.PermissionsNew); + Get(m_get_guild_stmt, 7, ret.VoiceRegion); + Get(m_get_guild_stmt, 8, ret.AFKChannelID); + Get(m_get_guild_stmt, 9, ret.AFKTimeout); + Get(m_get_guild_stmt, 10, ret.VerificationLevel); + Get(m_get_guild_stmt, 11, ret.DefaultMessageNotifications); + std::string tmp; + Get(m_get_guild_stmt, 12, tmp); + for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>()) + ret.Roles.emplace_back().ID = id; + Get(m_get_guild_stmt, 13, tmp); + for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>()) + ret.Emojis.emplace_back().ID = id; + Get(m_get_guild_stmt, 14, tmp); + ret.Features = nlohmann::json::parse(tmp).get<std::vector<std::string>>(); + Get(m_get_guild_stmt, 15, ret.MFALevel); + Get(m_get_guild_stmt, 16, ret.ApplicationID); + Get(m_get_guild_stmt, 17, ret.IsWidgetEnabled); + Get(m_get_guild_stmt, 18, ret.WidgetChannelID); + Get(m_get_guild_stmt, 19, ret.SystemChannelFlags); + Get(m_get_guild_stmt, 20, ret.RulesChannelID); + Get(m_get_guild_stmt, 21, ret.JoinedAt); + Get(m_get_guild_stmt, 22, ret.IsLarge); + Get(m_get_guild_stmt, 23, ret.IsUnavailable); + Get(m_get_guild_stmt, 24, ret.MemberCount); + Get(m_get_guild_stmt, 25, tmp); + ret.Channels.emplace(); + for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>()) + ret.Channels->emplace_back().ID = id; + Get(m_get_guild_stmt, 26, ret.MaxPresences); + Get(m_get_guild_stmt, 27, ret.MaxMembers); + Get(m_get_guild_stmt, 28, ret.VanityURL); + Get(m_get_guild_stmt, 29, ret.Description); + Get(m_get_guild_stmt, 30, ret.BannerHash); + Get(m_get_guild_stmt, 31, ret.PremiumTier); + Get(m_get_guild_stmt, 32, ret.PremiumSubscriptionCount); + Get(m_get_guild_stmt, 33, ret.PreferredLocale); + Get(m_get_guild_stmt, 34, ret.PublicUpdatesChannelID); + Get(m_get_guild_stmt, 35, ret.MaxVideoChannelUsers); + Get(m_get_guild_stmt, 36, ret.ApproximateMemberCount); + Get(m_get_guild_stmt, 37, ret.ApproximatePresenceCount); + Get(m_get_guild_stmt, 38, ret.IsLazy); + + Reset(m_get_guild_stmt); + + return ret; +} + +std::optional<GuildMember> Store::GetGuildMember(Snowflake guild_id, Snowflake user_id) const { + Bind(m_get_member_stmt, 1, guild_id); + Bind(m_get_member_stmt, 2, user_id); + if (!FetchOne(m_get_member_stmt)) { + if (m_db_err != SQLITE_DONE) + fprintf(stderr, "error while fetching member: %s\n", sqlite3_errstr(m_db_err)); + Reset(m_get_member_stmt); + return std::nullopt; + } + + GuildMember ret; + ret.User.emplace().ID = user_id; + Get(m_get_member_stmt, 2, ret.Nickname); + std::string tmp; + Get(m_get_member_stmt, 3, tmp); + ret.Roles = nlohmann::json::parse(tmp).get<std::vector<Snowflake>>(); + Get(m_get_member_stmt, 4, ret.JoinedAt); + Get(m_get_member_stmt, 5, ret.PremiumSince); + Get(m_get_member_stmt, 6, ret.IsDeafened); + Get(m_get_member_stmt, 7, ret.IsMuted); + + Reset(m_get_member_stmt); + + return ret; +} + +std::optional<Message> 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<MessageType>(tmpi); + Get(m_get_msg_stmt, 15, tmps); + if (tmps != "") + ret.MessageReference = nlohmann::json::parse(tmps).get<MessageReferenceData>(); + Get(m_get_msg_stmt, 16, tmpi); + ret.Flags = static_cast<MessageFlags>(tmpi); + Get(m_get_msg_stmt, 17, tmps); + if (tmps != "") + ret.Stickers = nlohmann::json::parse(tmps).get<std::vector<Sticker>>(); + 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<Message>(std::move(ret)); +} + +std::optional<PermissionOverwrite> 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<PermissionOverwrite::OverwriteType>(tmp); + Get(m_get_perm_stmt, 3, tmp); + ret.Allow = static_cast<Permission>(tmp); + Get(m_get_perm_stmt, 4, tmp); + ret.Deny = static_cast<Permission>(tmp); + + Reset(m_get_perm_stmt); + + return ret; +} + +std::optional<Role> 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<Permission>(tmp); + Get(m_get_role_stmt, 6, ret.IsManaged); + Get(m_get_role_stmt, 7, ret.IsMentionable); + + Reset(m_get_role_stmt); + + return ret; +} + +std::optional<User> Store::GetUser(Snowflake id) const { + Bind(m_get_user_stmt, 1, id); + if (!FetchOne(m_get_user_stmt)) { + 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; + } + + User ret; + Get(m_get_user_stmt, 0, ret.ID); + Get(m_get_user_stmt, 1, ret.Username); + Get(m_get_user_stmt, 2, ret.Discriminator); + Get(m_get_user_stmt, 3, ret.Avatar); + Get(m_get_user_stmt, 4, ret.IsBot); + Get(m_get_user_stmt, 5, ret.IsSystem); + Get(m_get_user_stmt, 6, ret.IsMFAEnabled); + Get(m_get_user_stmt, 7, ret.Locale); + Get(m_get_user_stmt, 8, ret.IsVerified); + Get(m_get_user_stmt, 9, ret.Email); + Get(m_get_user_stmt, 10, ret.Flags); + Get(m_get_user_stmt, 11, ret.PremiumType); + Get(m_get_user_stmt, 12, ret.PublicFlags); + + Reset(m_get_user_stmt); + + return ret; } Channel *Store::GetChannel(Snowflake id) { @@ -60,129 +502,464 @@ const Channel *Store::GetChannel(Snowflake id) const { return &it->second; } -Guild *Store::GetGuild(Snowflake id) { - auto it = m_guilds.find(id); - if (it == m_guilds.end()) - return nullptr; - return &it->second; +void Store::ClearGuild(Snowflake id) { + m_guilds.erase(id); } -const Guild *Store::GetGuild(Snowflake id) const { - auto it = m_guilds.find(id); - if (it == m_guilds.end()) - return nullptr; - return &it->second; +void Store::ClearChannel(Snowflake id) { + m_channels.erase(id); } -Role *Store::GetRole(Snowflake id) { - auto it = m_roles.find(id); - if (it == m_roles.end()) - return nullptr; - return &it->second; +const Store::channels_type &Store::GetChannels() const { + return m_channels; } -const Role *Store::GetRole(Snowflake id) const { - auto it = m_roles.find(id); - if (it == m_roles.end()) - return nullptr; - return &it->second; +const std::unordered_set<Snowflake> &Store::GetGuilds() const { + return m_guilds; +} +void Store::ClearAll() { + m_channels.clear(); + m_guilds.clear(); } -Message *Store::GetMessage(Snowflake id) { - auto it = m_messages.find(id); - if (it == m_messages.end()) - return nullptr; - return &it->second; +void Store::BeginTransaction() { + m_db_err = sqlite3_exec(m_db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr); } -const Message *Store::GetMessage(Snowflake id) const { - auto it = m_messages.find(id); - if (it == m_messages.end()) - return nullptr; - return &it->second; +void Store::EndTransaction() { + m_db_err = sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr); } -GuildMember *Store::GetGuildMemberData(Snowflake guild_id, Snowflake user_id) { - auto git = m_members.find(guild_id); - if (git == m_members.end()) - return nullptr; - auto mit = git->second.find(user_id); - if (mit == git->second.end()) - return nullptr; - return &mit->second; +bool Store::CreateTables() { + constexpr const char *create_users = R"( +CREATE TABLE IF NOT EXISTS users ( +id INTEGER PRIMARY KEY, +username TEXT NOT NULL, +discriminator TEXT NOT NULL, +avatar TEXT, +bot BOOL, +system BOOL, +mfa BOOL, +locale TEXT, +verified BOOl, +email TEXT, +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 +) +)"; + + constexpr const char *create_members = R"( +CREATE TABLE IF NOT EXISTS members ( +user_id INTEGER PRIMARY KEY, +guild_id INTEGER NOT NULL, +nickname TEXT, +roles TEXT NOT NULL, /* json */ +joined_at TEXT NOT NULL, +premium_since TEXT, +deaf BOOL NOT NULL, +mute BOOL NOT NULL +) +)"; + + constexpr char *create_guilds = R"( +CREATE TABLE IF NOT EXISTS guilds ( +id INTEGER PRIMARY KEY, +name TEXT NOT NULL, +icon TEXT NOT NULL, +splash TEXT, +owner BOOL, +owner_id INTEGER NOT NULL, +permissions INTEGER, /* new */ +voice_region TEXT, +afk_id INTEGER, +afk_timeout INTEGER NOT NULL, +verification INTEGER NOT NULL, +notifications INTEGER NOT NULL, +roles TEXT NOT NULL, /* json */ +emojis TEXT NOT NULL, /* json */ +features TEXT NOT NULL, /* json */ +mfa INTEGER NOT NULL, +application INTEGER, +widget BOOL, +widget_channel INTEGER, +system_flags INTEGER NOT NULL, +rules_channel INTEGER, +joined_at TEXT, +large BOOL, +unavailable BOOL, +member_count INTEGER, +channels TEXT NOT NULL, /* json */ +max_presences INTEGER, +max_members INTEGER, +vanity TEXT, +description TEXT, +banner_hash TEXT, +premium_tier INTEGER NOT NULL, +premium_count INTEGER, +locale TEXT NOT NULL, +public_updates_id INTEGER, +max_video_users INTEGER, +approx_members INTEGER, +approx_presences INTEGER, +lazy 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: %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; + } + + m_db_err = sqlite3_exec(m_db, create_members, nullptr, nullptr, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to create members table: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_exec(m_db, create_guilds, nullptr, nullptr, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to create guilds table: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + return true; } -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; +bool Store::CreateStatements() { + constexpr const char *set_user = R"( +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 = ? +)"; + + constexpr const char *set_member = R"( +REPLACE INTO members VALUES ( + ?, ?, ?, ?, ?, ?, ?, ? +) +)"; + + constexpr const char *get_member = R"( +SELECT * FROM members WHERE user_id = ? AND guild_id = ? +)"; + + constexpr const char *set_guild = R"( +REPLACE INTO guilds VALUES ( +?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +) +)"; + + constexpr const char *get_guild = R"( +SELECT * FROM guilds 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: %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: %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; + } + + m_db_err = sqlite3_prepare_v2(m_db, set_member, -1, &m_set_member_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare set member statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, get_member, -1, &m_get_member_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare get member statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, set_guild, -1, &m_set_guild_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare set guild statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + m_db_err = sqlite3_prepare_v2(m_db, get_guild, -1, &m_get_guild_stmt, nullptr); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "failed to prepare get guild statement: %s\n", sqlite3_errstr(m_db_err)); + return false; + } + + return true; } -Emoji *Store::GetEmoji(Snowflake id) { - auto it = m_emojis.find(id); - if (it != m_emojis.end()) - return &it->second; - return nullptr; +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); + sqlite3_finalize(m_set_member_stmt); + sqlite3_finalize(m_get_member_stmt); + sqlite3_finalize(m_set_guild_stmt); + sqlite3_finalize(m_get_guild_stmt); } -const GuildMember *Store::GetGuildMemberData(Snowflake guild_id, Snowflake user_id) const { - auto git = m_members.find(guild_id); - if (git == m_members.end()) - return nullptr; - auto mit = git->second.find(user_id); - if (mit == git->second.end()) - return nullptr; - return &mit->second; +void Store::Bind(sqlite3_stmt *stmt, int index, int num) const { + m_db_err = sqlite3_bind_int(stmt, index, num); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err)); + } } -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; +void Store::Bind(sqlite3_stmt *stmt, int index, uint64_t num) const { + m_db_err = sqlite3_bind_int64(stmt, index, num); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err)); + } } -const Emoji *Store::GetEmoji(Snowflake id) const { - auto it = m_emojis.find(id); - if (it != m_emojis.end()) - return &it->second; - return nullptr; +void Store::Bind(sqlite3_stmt *stmt, int index, const std::string &str) const { + 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)); + } } -void Store::ClearGuild(Snowflake id) { - m_guilds.erase(id); +void Store::Bind(sqlite3_stmt *stmt, int index, bool val) const { + m_db_err = sqlite3_bind_int(stmt, index, val ? 1 : 0); + if (m_db_err != SQLITE_OK) { + fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err)); + } } -void Store::ClearChannel(Snowflake id) { - m_channels.erase(id); +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)); + } } -const Store::channels_type &Store::GetChannels() const { - return m_channels; +bool Store::RunInsert(sqlite3_stmt *stmt) { + m_db_err = sqlite3_step(stmt); + Reset(stmt); + return m_db_err == SQLITE_DONE; } -const Store::guilds_type &Store::GetGuilds() const { - return m_guilds; +bool Store::FetchOne(sqlite3_stmt *stmt) const { + m_db_err = sqlite3_step(stmt); + return m_db_err == SQLITE_ROW; } -const Store::roles_type &Store::GetRoles() const { - return m_roles; +void Store::Get(sqlite3_stmt *stmt, int index, int &out) const { + out = sqlite3_column_int(stmt, index); } -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::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) + out = ""; + else + out = reinterpret_cast<const char *>(ptr); +} + +void Store::Get(sqlite3_stmt *stmt, int index, bool &out) const { + out = sqlite3_column_int(stmt, index) != 0; +} + +void Store::Get(sqlite3_stmt *stmt, int index, Snowflake &out) const { + const int64_t num = sqlite3_column_int64(stmt, index); + out = static_cast<uint64_t>(num); +} + +void Store::Reset(sqlite3_stmt *stmt) const { + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); } diff --git a/discord/store.hpp b/discord/store.hpp index 06b8faf..6d1987f 100644 --- a/discord/store.hpp +++ b/discord/store.hpp @@ -1,7 +1,11 @@ #pragma once +#include "../util.hpp" #include "objects.hpp" #include <unordered_map> +#include <unordered_set> #include <mutex> +#include <filesystem> +#include <sqlite3.h> #ifdef GetMessage // fuck you windows.h #undef GetMessage @@ -9,31 +13,31 @@ class Store { public: + Store(bool mem_store = false); + ~Store(); + + bool IsValid() const; + void SetUser(Snowflake id, const User &user); void SetChannel(Snowflake id, const Channel &channel); void SetGuild(Snowflake id, const Guild &guild); void SetRole(Snowflake id, const Role &role); void SetMessage(Snowflake id, const Message &message); - void SetGuildMemberData(Snowflake guild_id, Snowflake user_id, const GuildMember &data); + void SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data); void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm); void SetEmoji(Snowflake id, const Emoji &emoji); - User *GetUser(Snowflake id); + // slap const on everything even tho its not *really* const + 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); - const User *GetUser(Snowflake id) const; + std::optional<Emoji> GetEmoji(Snowflake id) const; + std::optional<Guild> GetGuild(Snowflake id) const; + std::optional<GuildMember> GetGuildMember(Snowflake guild_id, Snowflake user_id) const; + std::optional<Message> GetMessage(Snowflake id) const; + std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const; + std::optional<Role> GetRole(Snowflake id) const; + std::optional<User> 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); void ClearChannel(Snowflake id); @@ -48,18 +52,73 @@ public: using emojis_type = std::unordered_map<Snowflake, Emoji>; const channels_type &GetChannels() const; - const guilds_type &GetGuilds() const; - const roles_type &GetRoles() const; + const std::unordered_set<Snowflake> &GetGuilds() const; void ClearAll(); + void BeginTransaction(); + 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; + std::unordered_set<Snowflake> m_guilds; + + bool CreateTables(); + bool CreateStatements(); + void Cleanup(); + + template<typename T> + void Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const; + void Bind(sqlite3_stmt *stmt, int index, int num) const; + void Bind(sqlite3_stmt *stmt, int index, uint64_t num) const; + void Bind(sqlite3_stmt *stmt, int index, const std::string &str) const; + 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<typename T> + void Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const; + void Get(sqlite3_stmt *stmt, int index, int &out) const; + void Get(sqlite3_stmt *stmt, int index, uint64_t &out) const; + void Get(sqlite3_stmt *stmt, int index, std::string &out) const; + void Get(sqlite3_stmt *stmt, int index, bool &out) const; + void Get(sqlite3_stmt *stmt, int index, Snowflake &out) const; + void Reset(sqlite3_stmt *stmt) const; + + std::filesystem::path m_db_path; + mutable sqlite3 *m_db; + 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; + mutable sqlite3_stmt *m_set_member_stmt; + mutable sqlite3_stmt *m_get_member_stmt; + mutable sqlite3_stmt *m_set_guild_stmt; + mutable sqlite3_stmt *m_get_guild_stmt; }; + +template<typename T> +inline void Store::Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const { + if (opt.has_value()) + Bind(stmt, index, *opt); + else + sqlite3_bind_null(stmt, index); +} + +template<typename T> +inline void Store::Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const { + if (sqlite3_column_type(stmt, index) == SQLITE_NULL) + out = std::nullopt; + else { + T v; + Get(stmt, index, v); + out = std::optional<T>(v); + } +} diff --git a/discord/user.cpp b/discord/user.cpp index c7858d5..78d309b 100644 --- a/discord/user.cpp +++ b/discord/user.cpp @@ -34,20 +34,20 @@ void from_json(const nlohmann::json &j, User &m) { } void User::update_from_json(const nlohmann::json &j, User &m) { - JS_ON("username", m.Username); - JS_ON("discriminator", m.Discriminator); - JS_ON("avatar", m.Avatar); - JS_ON("bot", m.IsBot); - JS_ON("system", m.IsSystem); - JS_ON("mfa_enabled", m.IsMFAEnabled); - JS_ON("locale", m.Locale); - JS_ON("verified", m.IsVerified); - JS_ON("email", m.Email); - JS_ON("flags", m.Flags); - JS_ON("premium_type", m.PremiumType); - JS_ON("public_flags", m.PublicFlags); - JS_ON("desktop", m.IsDesktop); - JS_ON("mobile", m.IsMobile); - JS_ON("nsfw_allowed", m.IsNSFWAllowed); - JS_ON("phone", m.Phone); + JS_RD("username", m.Username); + JS_RD("discriminator", m.Discriminator); + JS_RD("avatar", m.Avatar); + JS_RD("bot", m.IsBot); + JS_RD("system", m.IsSystem); + JS_RD("mfa_enabled", m.IsMFAEnabled); + JS_RD("locale", m.Locale); + JS_RD("verified", m.IsVerified); + JS_RD("email", m.Email); + JS_RD("flags", m.Flags); + JS_RD("premium_type", m.PremiumType); + JS_RD("public_flags", m.PublicFlags); + JS_RD("desktop", m.IsDesktop); + JS_RD("mobile", m.IsMobile); + JS_RD("nsfw_allowed", m.IsNSFWAllowed); + JS_RD("phone", m.Phone); } diff --git a/discord/user.hpp b/discord/user.hpp index e3d893d..a34bd81 100644 --- a/discord/user.hpp +++ b/discord/user.hpp @@ -4,19 +4,19 @@ #include <string> struct User { - Snowflake ID; // - std::string Username; // - std::string Discriminator; // - std::string Avatar; // null - bool IsBot = false; // opt - bool IsSystem = false; // opt - bool IsMFAEnabled = false; // opt - std::string Locale; // opt - bool IsVerified = false; // opt - std::string Email; // opt, null - int Flags = 0; // opt - int PremiumType = 0; // opt, null (docs wrong) - int PublicFlags = 0; // opt + Snowflake ID; + std::string Username; + std::string Discriminator; + std::string Avatar; // null + std::optional<bool> IsBot; + std::optional<bool> IsSystem; + std::optional<bool> IsMFAEnabled; + std::optional<std::string> Locale; + std::optional<bool> IsVerified; + std::optional<std::string> Email; // null + std::optional<int> Flags; + std::optional<int> PremiumType; // null + std::optional<int> PublicFlags; // undocumented (opt) bool IsDesktop = false; // @@ -11,6 +11,16 @@ #include <regex> #include <mutex> #include <condition_variable> +#include <optional> +#include <type_traits> + +namespace util { +template<typename T> +struct is_optional : ::std::false_type {}; + +template<typename T> +struct is_optional<::std::optional<T>> : ::std::true_type {}; +} class Semaphore { public: |