summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt3
-rw-r--r--abaddon.cpp25
-rw-r--r--abaddon.hpp3
-rw-r--r--components/channels.cpp53
-rw-r--r--components/chatmessage.cpp126
-rw-r--r--components/chatwindow.cpp10
-rw-r--r--components/memberlist.cpp36
-rw-r--r--discord/channel.cpp2
-rw-r--r--discord/channel.hpp2
-rw-r--r--discord/discord.cpp196
-rw-r--r--discord/discord.hpp21
-rw-r--r--discord/emoji.hpp16
-rw-r--r--discord/guild.cpp4
-rw-r--r--discord/guild.hpp86
-rw-r--r--discord/json.hpp19
-rw-r--r--discord/member.hpp14
-rw-r--r--discord/message.cpp72
-rw-r--r--discord/message.hpp102
-rw-r--r--discord/permissions.cpp2
-rw-r--r--discord/permissions.hpp6
-rw-r--r--discord/sticker.cpp13
-rw-r--r--discord/sticker.hpp1
-rw-r--r--discord/store.cpp995
-rw-r--r--discord/store.hpp107
-rw-r--r--discord/user.cpp32
-rw-r--r--discord/user.hpp26
-rw-r--r--util.hpp10
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; //
diff --git a/util.hpp b/util.hpp
index 5b90220..0a93343 100644
--- a/util.hpp
+++ b/util.hpp
@@ -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: