summaryrefslogtreecommitdiff
path: root/src/discord/store.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/discord/store.cpp')
-rw-r--r--src/discord/store.cpp2255
1 files changed, 2255 insertions, 0 deletions
diff --git a/src/discord/store.cpp b/src/discord/store.cpp
new file mode 100644
index 0000000..1cb7231
--- /dev/null
+++ b/src/discord/store.cpp
@@ -0,0 +1,2255 @@
+#include "store.hpp"
+#include <cinttypes>
+
+using namespace std::literals::string_literals;
+
+// hopefully the casting between signed and unsigned int64 doesnt cause issues
+
+Store::Store(bool mem_store)
+ : m_db_path(mem_store ? ":memory:" : std::filesystem::temp_directory_path() / "abaddon-store.db")
+ , m_db(m_db_path.string().c_str()) {
+ if (!m_db.OK()) {
+ fprintf(stderr, "error opening database: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ m_db.Execute(R"(
+ PRAGMA writable_schema = 1;
+ DELETE FROM sqlite_master;
+ PRAGMA writable_schema = 0;
+ VACUUM;
+ PRAGMA integrity_check;
+ )");
+ if (!m_db.OK()) {
+ fprintf(stderr, "failed to clear database: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ if (m_db.Execute("PRAGMA journal_mode = WAL") != SQLITE_OK) {
+ fprintf(stderr, "enabling write-ahead-log failed: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ if (m_db.Execute("PRAGMA synchronous = NORMAL") != SQLITE_OK) {
+ fprintf(stderr, "setting synchronous failed: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ m_ok &= CreateTables();
+ m_ok &= CreateStatements();
+}
+
+Store::~Store() {
+ m_db.Close();
+ if (!m_db.OK()) {
+ fprintf(stderr, "error closing database: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ if (m_db_path != ":memory:") {
+ std::error_code ec;
+ std::filesystem::remove(m_db_path, ec);
+ }
+}
+
+bool Store::IsValid() const {
+ return m_db.OK() && m_ok;
+}
+
+void Store::SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban) {
+ auto &s = m_stmt_set_ban;
+
+ s->Bind(1, guild_id);
+ s->Bind(2, user_id);
+ s->Bind(3, ban.Reason);
+
+ if (!s->Insert())
+ fprintf(stderr, "ban insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(guild_id), static_cast<uint64_t>(user_id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetChannel(Snowflake id, const ChannelData &chan) {
+ auto &s = m_stmt_set_chan;
+
+ s->Bind(1, id);
+ s->Bind(2, chan.Type);
+ s->Bind(3, chan.GuildID);
+ s->Bind(4, chan.Position);
+ s->Bind(5, chan.Name);
+ s->Bind(6, chan.Topic);
+ s->Bind(7, chan.IsNSFW);
+ s->Bind(8, chan.LastMessageID);
+ s->Bind(9, chan.Bitrate);
+ s->Bind(10, chan.UserLimit);
+ s->Bind(11, chan.RateLimitPerUser);
+ s->Bind(12, chan.Icon);
+ s->Bind(13, chan.OwnerID);
+ s->Bind(14, chan.ApplicationID);
+ s->Bind(15, chan.ParentID);
+ s->Bind(16, chan.LastPinTimestamp);
+ if (chan.ThreadMetadata.has_value()) {
+ s->Bind(17, chan.ThreadMetadata->IsArchived);
+ s->Bind(18, chan.ThreadMetadata->AutoArchiveDuration);
+ s->Bind(19, chan.ThreadMetadata->ArchiveTimestamp);
+ } else {
+ s->Bind(17);
+ s->Bind(18);
+ s->Bind(19);
+ }
+
+ if (!s->Insert())
+ fprintf(stderr, "channel insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ if (chan.Recipients.has_value()) {
+ BeginTransaction();
+ auto &s = m_stmt_set_recipient;
+ for (const auto &r : *chan.Recipients) {
+ s->Bind(1, chan.ID);
+ s->Bind(2, r.ID);
+ if (!s->Insert())
+ fprintf(stderr, "recipient insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(chan.ID), static_cast<uint64_t>(r.ID), m_db.ErrStr());
+ s->Reset();
+ }
+ EndTransaction();
+ } else if (chan.RecipientIDs.has_value()) {
+ BeginTransaction();
+ auto &s = m_stmt_set_recipient;
+ for (const auto &id : *chan.RecipientIDs) {
+ s->Bind(1, chan.ID);
+ s->Bind(2, id);
+ if (!s->Insert())
+ fprintf(stderr, "recipient insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(chan.ID), static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ }
+ EndTransaction();
+ }
+
+ s->Reset();
+}
+
+void Store::SetEmoji(Snowflake id, const EmojiData &emoji) {
+ auto &s = m_stmt_set_emoji;
+
+ s->Bind(1, id);
+ s->Bind(2, emoji.Name);
+ if (emoji.Creator.has_value())
+ s->Bind(3, emoji.Creator->ID);
+ else
+ s->Bind(3);
+ s->Bind(4, emoji.NeedsColons);
+ s->Bind(5, emoji.IsManaged);
+ s->Bind(6, emoji.IsAnimated);
+ s->Bind(7, emoji.IsAvailable);
+
+ if (emoji.Roles.has_value()) {
+ BeginTransaction();
+
+ auto &s = m_stmt_set_emoji_role;
+
+ for (const auto &r : *emoji.Roles) {
+ s->Bind(1, id);
+ s->Bind(2, r);
+ if (!s->Insert())
+ fprintf(stderr, "emoji role insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(id), static_cast<uint64_t>(r), m_db.ErrStr());
+ s->Reset();
+ }
+
+ EndTransaction();
+ }
+
+ if (!s->Insert())
+ fprintf(stderr, "emoji insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetGuild(Snowflake id, const GuildData &guild) {
+ BeginTransaction();
+ auto &s = m_stmt_set_guild;
+
+ s->Bind(1, guild.ID);
+ s->Bind(2, guild.Name);
+ s->Bind(3, guild.Icon);
+ s->Bind(4, guild.Splash);
+ s->Bind(5, guild.IsOwner);
+ s->Bind(6, guild.OwnerID);
+ s->Bind(7, guild.PermissionsNew);
+ s->Bind(8, guild.VoiceRegion);
+ s->Bind(9, guild.AFKChannelID);
+ s->Bind(10, guild.AFKTimeout);
+ s->Bind(11, guild.VerificationLevel);
+ s->Bind(12, guild.DefaultMessageNotifications);
+ s->Bind(13, guild.MFALevel);
+ s->Bind(14, guild.ApplicationID);
+ s->Bind(15, guild.IsWidgetEnabled);
+ s->Bind(16, guild.WidgetChannelID);
+ s->Bind(17, guild.SystemChannelFlags);
+ s->Bind(18, guild.RulesChannelID);
+ s->Bind(19, guild.JoinedAt);
+ s->Bind(20, guild.IsLarge);
+ s->Bind(21, guild.IsUnavailable);
+ s->Bind(22, guild.MemberCount);
+ s->Bind(23, guild.MaxPresences);
+ s->Bind(24, guild.MaxMembers);
+ s->Bind(25, guild.VanityURL);
+ s->Bind(26, guild.Description);
+ s->Bind(27, guild.BannerHash);
+ s->Bind(28, guild.PremiumTier);
+ s->Bind(29, guild.PremiumSubscriptionCount);
+ s->Bind(30, guild.PreferredLocale);
+ s->Bind(31, guild.PublicUpdatesChannelID);
+ s->Bind(32, guild.MaxVideoChannelUsers);
+ s->Bind(33, guild.ApproximateMemberCount);
+ s->Bind(34, guild.ApproximatePresenceCount);
+ s->Bind(35, guild.IsLazy);
+
+ if (!s->Insert())
+ fprintf(stderr, "guild insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(guild.ID), m_db.ErrStr());
+
+ s->Reset();
+
+ if (guild.Emojis.has_value()) {
+ auto &s = m_stmt_set_guild_emoji;
+ for (const auto &emoji : *guild.Emojis) {
+ s->Bind(1, guild.ID);
+ s->Bind(2, emoji.ID);
+ if (!s->Insert())
+ fprintf(stderr, "guild emoji insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(guild.ID), static_cast<uint64_t>(emoji.ID), m_db.ErrStr());
+ s->Reset();
+ }
+ }
+
+ if (guild.Features.has_value()) {
+ auto &s = m_stmt_set_guild_feature;
+
+ for (const auto &feature : *guild.Features) {
+ s->Bind(1, guild.ID);
+ s->Bind(2, feature);
+ if (!s->Insert())
+ fprintf(stderr, "guild feature insert failed for %" PRIu64 "/%s: %s\n", static_cast<uint64_t>(guild.ID), feature.c_str(), m_db.ErrStr());
+ s->Reset();
+ }
+ }
+
+ if (guild.Threads.has_value()) {
+ auto &s = m_stmt_set_thread;
+
+ for (const auto &thread : *guild.Threads) {
+ s->Bind(1, guild.ID);
+ s->Bind(2, thread.ID);
+ if (!s->Insert())
+ fprintf(stderr, "guild thread insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(guild.ID), static_cast<uint64_t>(thread.ID), m_db.ErrStr());
+ s->Reset();
+ }
+ }
+
+ EndTransaction();
+}
+
+void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data) {
+ auto &s = m_stmt_set_member;
+
+ s->Bind(1, user_id);
+ s->Bind(2, guild_id);
+ s->Bind(3, data.Nickname);
+ s->Bind(4, data.JoinedAt);
+ s->Bind(5, data.PremiumSince);
+ s->Bind(6, data.IsDeafened);
+ s->Bind(7, data.IsMuted);
+ s->Bind(8, data.Avatar);
+ s->Bind(9, data.IsPending);
+
+ if (!s->Insert())
+ fprintf(stderr, "member insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(user_id), static_cast<uint64_t>(guild_id), m_db.ErrStr());
+
+ s->Reset();
+
+ {
+ auto &s = m_stmt_set_member_roles;
+
+ BeginTransaction();
+ for (const auto &role : data.Roles) {
+ s->Bind(1, user_id);
+ s->Bind(2, role);
+ if (!s->Insert())
+ fprintf(stderr, "member role insert failed for %" PRIu64 "/%" PRIu64 "/%" PRIu64 ": %s\n",
+ static_cast<uint64_t>(user_id), static_cast<uint64_t>(guild_id), static_cast<uint64_t>(role), m_db.ErrStr());
+ s->Reset();
+ }
+ EndTransaction();
+ }
+}
+
+void Store::SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction) {
+ auto &s = m_stmt_set_interaction;
+
+ s->Bind(1, message_id);
+ s->Bind(2, interaction.ID);
+ s->Bind(3, interaction.Type);
+ s->Bind(4, interaction.Name);
+ s->Bind(5, interaction.User.ID);
+
+ if (!s->Insert())
+ fprintf(stderr, "message interaction failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(message_id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetMessage(Snowflake id, const Message &message) {
+ auto &s = m_stmt_set_msg;
+
+ BeginTransaction();
+
+ s->Bind(1, id);
+ s->Bind(2, message.ChannelID);
+ s->Bind(3, message.GuildID);
+ s->Bind(4, message.Author.ID);
+ s->Bind(5, message.Content);
+ s->Bind(6, message.Timestamp);
+ s->Bind(7, message.EditedTimestamp);
+ s->Bind(8, message.IsTTS);
+ s->Bind(9, message.DoesMentionEveryone);
+ s->BindAsJSON(10, message.Embeds);
+ s->Bind(11, message.IsPinned);
+ s->Bind(12, message.WebhookID);
+ s->Bind(13, message.Type);
+ s->BindAsJSON(14, message.Application);
+ s->Bind(15, message.Flags);
+ s->BindAsJSON(16, message.Stickers);
+ s->Bind(17, message.IsDeleted());
+ s->Bind(18, message.IsEdited());
+ s->Bind(19, message.IsPending);
+ s->Bind(20, message.Nonce);
+ s->BindAsJSON(21, message.StickerItems);
+
+ if (!s->Insert())
+ fprintf(stderr, "message insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+
+ if (message.MessageReference.has_value()) {
+ auto &s = m_stmt_set_msg_ref;
+ s->Bind(1, message.ID);
+ s->Bind(2, message.MessageReference->MessageID);
+ s->Bind(3, message.MessageReference->ChannelID);
+ s->Bind(4, message.MessageReference->GuildID);
+
+ if (!s->Insert())
+ fprintf(stderr, "message ref insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+ }
+
+ for (const auto &u : message.Mentions) {
+ auto &s = m_stmt_set_mention;
+ s->Bind(1, id);
+ s->Bind(2, u.ID);
+ if (!s->Insert())
+ fprintf(stderr, "message mention insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(id), static_cast<uint64_t>(u.ID), m_db.ErrStr());
+ s->Reset();
+ }
+
+ for (const auto &a : message.Attachments) {
+ auto &s = m_stmt_set_attachment;
+ s->Bind(1, id);
+ s->Bind(2, a.ID);
+ s->Bind(3, a.Filename);
+ s->Bind(4, a.Bytes);
+ s->Bind(5, a.URL);
+ s->Bind(6, a.ProxyURL);
+ s->Bind(7, a.Height);
+ s->Bind(8, a.Width);
+ if (!s->Insert())
+ fprintf(stderr, "message attachment insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(id), static_cast<uint64_t>(a.ID), m_db.ErrStr());
+ s->Reset();
+ }
+
+ if (message.Reactions.has_value()) {
+ auto &s = m_stmt_add_reaction;
+ for (size_t i = 0; i < message.Reactions->size(); i++) {
+ const auto &reaction = (*message.Reactions)[i];
+ s->Bind(1, id);
+ s->Bind(2, reaction.Emoji.ID);
+ s->Bind(3, reaction.Emoji.Name);
+ s->Bind(4, reaction.Count);
+ s->Bind(5, reaction.HasReactedWith);
+ s->Bind(6, i);
+ if (!s->Insert())
+ fprintf(stderr, "message reaction insert failed for %" PRIu64 "/%" PRIu64 "/%s: %s\n", static_cast<uint64_t>(id), static_cast<uint64_t>(reaction.Emoji.ID), reaction.Emoji.Name.c_str(), m_db.ErrStr());
+ s->Reset();
+ }
+ }
+
+ if (message.Interaction.has_value())
+ SetMessageInteractionPair(id, *message.Interaction);
+
+ EndTransaction();
+}
+
+void Store::SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm) {
+ auto &s = m_stmt_set_perm;
+
+ s->Bind(1, perm.ID);
+ s->Bind(2, channel_id);
+ s->Bind(3, perm.Type);
+ s->Bind(4, perm.Allow);
+ s->Bind(5, perm.Deny);
+
+ if (!s->Insert())
+ fprintf(stderr, "permission insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(channel_id), static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetRole(Snowflake guild_id, const RoleData &role) {
+ auto &s = m_stmt_set_role;
+
+ s->Bind(1, role.ID);
+ s->Bind(2, guild_id);
+ s->Bind(3, role.Name);
+ s->Bind(4, role.Color);
+ s->Bind(5, role.IsHoisted);
+ s->Bind(6, role.Position);
+ s->Bind(7, role.Permissions);
+ s->Bind(8, role.IsManaged);
+ s->Bind(9, role.IsMentionable);
+
+ if (!s->Insert())
+ fprintf(stderr, "role insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(role.ID), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetUser(Snowflake id, const UserData &user) {
+ auto &s = m_stmt_set_user;
+
+ s->Bind(1, id);
+ s->Bind(2, user.Username);
+ s->Bind(3, user.Discriminator);
+ s->Bind(4, user.Avatar);
+ s->Bind(5, user.IsBot);
+ s->Bind(6, user.IsSystem);
+ s->Bind(7, user.IsMFAEnabled);
+ s->Bind(8, user.PremiumType);
+ s->Bind(9, user.PublicFlags);
+
+ if (!s->Insert())
+ fprintf(stderr, "user insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+std::optional<BanData> Store::GetBan(Snowflake guild_id, Snowflake user_id) const {
+ auto &s = m_stmt_get_ban;
+
+ s->Bind(1, guild_id);
+ s->Bind(2, user_id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching ban for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(guild_id), static_cast<uint64_t>(user_id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ BanData r;
+ r.User.ID = user_id;
+ s->Get(2, r.Reason);
+
+ s->Reset();
+
+ return r;
+}
+
+std::vector<BanData> Store::GetBans(Snowflake guild_id) const {
+ auto &s = m_stmt_get_bans;
+
+ std::vector<BanData> ret;
+ s->Bind(1, guild_id);
+ while (s->FetchOne()) {
+ auto &ban = ret.emplace_back();
+ s->Get(1, ban.User.ID);
+ s->Get(2, ban.Reason);
+ }
+
+ s->Reset();
+
+ return ret;
+}
+
+std::vector<Message> Store::GetLastMessages(Snowflake id, size_t num) const {
+ auto &s = m_stmt_get_last_msgs;
+ std::vector<Message> msgs;
+ s->Bind(1, id);
+ s->Bind(2, num);
+ while (s->FetchOne()) {
+ auto msg = GetMessageBound(s);
+ msgs.push_back(std::move(msg));
+ }
+
+ s->Reset();
+
+ for (auto &msg : msgs) {
+ if (msg.MessageReference.has_value() && msg.MessageReference->MessageID.has_value()) {
+ auto ref = GetMessage(*msg.MessageReference->MessageID);
+ if (ref.has_value())
+ msg.ReferencedMessage = std::make_shared<Message>(std::move(*ref));
+ }
+ }
+
+ return msgs;
+}
+
+std::vector<Message> Store::GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const {
+ std::vector<Message> msgs;
+
+ auto &s = m_stmt_get_messages_before;
+
+ s->Bind(1, channel_id);
+ s->Bind(2, message_id);
+ s->Bind(3, limit);
+
+ while (s->FetchOne()) {
+ auto msg = GetMessageBound(s);
+ msgs.push_back(std::move(msg));
+ }
+
+ s->Reset();
+
+ for (auto &msg : msgs) {
+ if (msg.MessageReference.has_value() && msg.MessageReference->MessageID.has_value()) {
+ auto ref = GetMessage(*msg.MessageReference->MessageID);
+ if (ref.has_value())
+ msg.ReferencedMessage = std::make_shared<Message>(std::move(*ref));
+ }
+ }
+
+ return msgs;
+}
+
+std::vector<Message> Store::GetPinnedMessages(Snowflake channel_id) const {
+ std::vector<Message> msgs;
+
+ auto &s = m_stmt_get_pins;
+
+ s->Bind(1, channel_id);
+
+ while (s->FetchOne()) {
+ auto msg = GetMessageBound(s);
+ msgs.push_back(std::move(msg));
+ }
+
+ s->Reset();
+
+ for (auto &msg : msgs) {
+ if (msg.MessageReference.has_value() && msg.MessageReference->MessageID.has_value()) {
+ auto ref = GetMessage(*msg.MessageReference->MessageID);
+ if (ref.has_value())
+ msg.ReferencedMessage = std::make_shared<Message>(std::move(*ref));
+ }
+ }
+
+ return msgs;
+}
+
+std::vector<ChannelData> Store::GetActiveThreads(Snowflake channel_id) const {
+ std::vector<ChannelData> ret;
+
+ auto &s = m_stmt_get_active_threads;
+
+ s->Bind(1, channel_id);
+ while (s->FetchOne()) {
+ Snowflake x;
+ s->Get(0, x);
+ auto chan = GetChannel(x);
+ if (chan.has_value())
+ ret.push_back(*chan);
+ }
+
+ s->Reset();
+
+ return ret;
+}
+
+void Store::AddReaction(const MessageReactionAddObject &data, bool byself) {
+ auto &s = m_stmt_add_reaction;
+
+ s->Bind(1, data.MessageID);
+ s->Bind(2, data.Emoji.ID);
+ s->Bind(3, data.Emoji.Name);
+ s->Bind(4, 1);
+ if (byself)
+ s->Bind(5, true);
+ else
+ s->Bind(5);
+ s->Bind(6);
+
+ if (!s->Insert())
+ fprintf(stderr, "failed to add reaction for %" PRIu64 ": %s\n", static_cast<uint64_t>(data.MessageID), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::RemoveReaction(const MessageReactionRemoveObject &data, bool byself) {
+ auto &s = m_stmt_sub_reaction;
+
+ s->Bind(1, data.MessageID);
+ s->Bind(2, data.Emoji.ID);
+ s->Bind(3, data.Emoji.Name);
+ if (byself)
+ s->Bind(4, false);
+ else
+ s->Bind(4);
+
+ if (!s->Insert())
+ fprintf(stderr, "failed to remove reaction for %" PRIu64 ": %s\n", static_cast<uint64_t>(data.MessageID), m_db.ErrStr());
+
+ s->Reset();
+}
+
+std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
+ auto &s = m_stmt_get_chan;
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching channel %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ ChannelData r;
+
+ // uncomment as necessary
+ r.ID = id;
+ s->Get(1, r.Type);
+ s->Get(2, r.GuildID);
+ s->Get(3, r.Position);
+ s->Get(4, r.Name);
+ s->Get(5, r.Topic);
+ s->Get(6, r.IsNSFW);
+ s->Get(7, r.LastMessageID);
+ s->Get(10, r.RateLimitPerUser);
+ s->Get(12, r.OwnerID);
+ s->Get(14, r.ParentID);
+ if (!s->IsNull(16)) {
+ r.ThreadMetadata.emplace();
+ s->Get(16, r.ThreadMetadata->IsArchived);
+ s->Get(17, r.ThreadMetadata->AutoArchiveDuration);
+ s->Get(18, r.ThreadMetadata->ArchiveTimestamp);
+ }
+
+ s->Reset();
+
+ {
+ auto &s = m_stmt_get_recipients;
+ s->Bind(1, id);
+ std::vector<Snowflake> recipients;
+ while (s->FetchOne()) {
+ auto &r = recipients.emplace_back();
+ s->Get(0, r);
+ }
+ s->Reset();
+ if (!recipients.empty())
+ r.RecipientIDs = std::move(recipients);
+ }
+
+ return r;
+}
+
+std::optional<EmojiData> Store::GetEmoji(Snowflake id) const {
+ auto &s = m_stmt_get_emoji;
+
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching emoji %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ EmojiData r;
+
+ r.ID = id;
+ s->Get(1, r.Name);
+ if (!s->IsNull(2)) {
+ r.Creator.emplace();
+ s->Get(2, r.Creator->ID);
+ }
+ s->Get(3, r.NeedsColons);
+ s->Get(4, r.IsManaged);
+ s->Get(5, r.IsAnimated);
+ s->Get(6, r.IsAvailable);
+
+ {
+ auto &s = m_stmt_get_emoji_roles;
+
+ s->Bind(1, id);
+ r.Roles.emplace();
+ while (s->FetchOne()) {
+ Snowflake id;
+ s->Get(0, id);
+ r.Roles->push_back(id);
+ }
+ s->Reset();
+ }
+
+ s->Reset();
+
+ return r;
+}
+
+std::optional<GuildData> Store::GetGuild(Snowflake id) const {
+ auto &s = m_stmt_get_guild;
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching guild %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ // unfetched fields arent used anywhere
+ GuildData r;
+ r.ID = id;
+ s->Get(1, r.Name);
+ s->Get(2, r.Icon);
+ s->Get(5, r.OwnerID);
+ s->Get(20, r.IsUnavailable);
+
+ s->Reset();
+
+ {
+ auto &s = m_stmt_get_guild_emojis;
+
+ s->Bind(1, id);
+ r.Emojis.emplace();
+ while (s->FetchOne()) {
+ auto &q = r.Emojis->emplace_back();
+ s->Get(0, q.ID);
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_guild_features;
+
+ s->Bind(1, id);
+ r.Features.emplace();
+ while (s->FetchOne()) {
+ std::string feature;
+ s->Get(0, feature);
+ r.Features->insert(feature);
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_guild_chans;
+ s->Bind(1, id);
+ r.Channels.emplace();
+ while (s->FetchOne()) {
+ auto &q = r.Channels->emplace_back();
+ s->Get(0, q.ID);
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_threads;
+ s->Bind(1, id);
+ r.Threads.emplace();
+ while (s->FetchOne()) {
+ auto &q = r.Threads->emplace_back();
+ s->Get(0, q.ID);
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_guild_roles;
+ s->Bind(1, id);
+ r.Roles.emplace();
+ while (s->FetchOne()) {
+ r.Roles->push_back(GetRoleBound(s));
+ }
+ s->Reset();
+ }
+
+ return r;
+}
+
+std::optional<GuildMember> Store::GetGuildMember(Snowflake guild_id, Snowflake user_id) const {
+ auto &s = m_stmt_get_member;
+
+ s->Bind(1, user_id);
+ s->Bind(2, guild_id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching member %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(user_id), static_cast<uint64_t>(guild_id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ GuildMember r;
+ r.User.emplace().ID = user_id;
+ s->Get(2, r.Nickname);
+ s->Get(3, r.JoinedAt);
+ s->Get(4, r.PremiumSince);
+ //s->Get(5, r.IsDeafened);
+ //s->Get(6, r.IsMuted);
+ s->Get(7, r.Avatar);
+ s->Get(8, r.IsPending);
+
+ s->Reset();
+
+ {
+ auto &s = m_stmt_get_member_roles;
+
+ s->Bind(1, user_id);
+ s->Bind(2, guild_id);
+
+ while (s->FetchOne()) {
+ auto &f = r.Roles.emplace_back();
+ s->Get(0, f);
+ }
+
+ s->Reset();
+ }
+
+ return r;
+}
+
+std::optional<Message> Store::GetMessage(Snowflake id) const {
+ auto &s = m_stmt_get_msg;
+
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching message %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ auto top = GetMessageBound(s);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching message %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return top;
+ }
+
+ auto ref = GetMessageBound(s);
+ top.ReferencedMessage = std::make_shared<Message>(std::move(ref));
+
+ s->Reset();
+
+ return top;
+}
+
+Message Store::GetMessageBound(std::unique_ptr<Statement> &s) const {
+ Message r;
+
+ s->Get(0, r.ID);
+ s->Get(1, r.ChannelID);
+ s->Get(2, r.GuildID);
+ s->Get(3, r.Author.ID);
+ s->Get(4, r.Content);
+ s->Get(5, r.Timestamp);
+ s->Get(6, r.EditedTimestamp);
+ //s->Get(7, r.IsTTS);
+ //s->Get(8, r.DoesMentionEveryone);
+ s->GetJSON(9, r.Embeds);
+ s->Get(10, r.IsPinned);
+ s->Get(11, r.WebhookID);
+ s->Get(12, r.Type);
+ s->GetJSON(13, r.Application);
+ s->Get(14, r.Flags);
+ s->GetJSON(15, r.Stickers);
+ bool tmpb;
+ s->Get(16, tmpb);
+ if (tmpb) r.SetDeleted();
+ s->Get(17, tmpb);
+ if (tmpb) r.SetEdited();
+ s->Get(18, r.IsPending);
+ s->Get(19, r.Nonce);
+ s->GetJSON(20, r.StickerItems);
+
+ if (!s->IsNull(21)) {
+ auto &i = r.Interaction.emplace();
+ s->Get(21, i.ID);
+ s->Get(22, i.Name);
+ s->Get(23, i.Type);
+ s->Get(24, i.User.ID);
+ }
+
+ if (!s->IsNull(25)) {
+ auto &a = r.Attachments.emplace_back();
+ s->Get(25, a.ID);
+ s->Get(26, a.Filename);
+ s->Get(27, a.Bytes);
+ s->Get(28, a.URL);
+ s->Get(29, a.ProxyURL);
+ s->Get(30, a.Height);
+ s->Get(31, a.Width);
+ }
+
+ if (!s->IsNull(32)) {
+ auto &q = r.MessageReference.emplace();
+ s->Get(32, q.MessageID);
+ s->Get(33, q.ChannelID);
+ s->Get(34, q.GuildID);
+ }
+
+ {
+ auto &s = m_stmt_get_mentions;
+ s->Bind(1, r.ID);
+ while (s->FetchOne()) {
+ Snowflake id;
+ s->Get(0, id);
+ auto user = GetUser(id);
+ if (user.has_value())
+ r.Mentions.push_back(std::move(*user));
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_reactions;
+ s->Bind(1, r.ID);
+ std::map<size_t, ReactionData> tmp;
+ while (s->FetchOne()) {
+ size_t idx;
+ ReactionData q;
+ s->Get(0, q.Emoji.ID);
+ s->Get(1, q.Emoji.Name);
+ s->Get(2, q.Count);
+ s->Get(3, q.HasReactedWith);
+ s->Get(4, idx);
+ tmp[idx] = q;
+ }
+ s->Reset();
+
+ r.Reactions.emplace();
+ for (const auto &[idx, reaction] : tmp)
+ r.Reactions->push_back(reaction);
+ }
+
+ return r;
+}
+
+std::optional<PermissionOverwrite> Store::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const {
+ auto &s = m_stmt_get_perm;
+
+ s->Bind(1, id);
+ s->Bind(2, channel_id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "failed while fetching permission %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(channel_id), static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ PermissionOverwrite r;
+ r.ID = id;
+ s->Get(2, r.Type);
+ s->Get(3, r.Allow);
+ s->Get(4, r.Deny);
+
+ s->Reset();
+
+ return r;
+}
+
+std::optional<RoleData> Store::GetRole(Snowflake id) const {
+ auto &s = m_stmt_get_role;
+
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching role %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ auto role = GetRoleBound(s);
+
+ s->Reset();
+
+ return role;
+}
+
+RoleData Store::GetRoleBound(std::unique_ptr<Statement> &s) const {
+ RoleData r;
+
+ s->Get(0, r.ID);
+ //s->Get(1, guild id);
+ s->Get(2, r.Name);
+ s->Get(3, r.Color);
+ s->Get(4, r.IsHoisted);
+ s->Get(5, r.Position);
+ s->Get(6, r.Permissions);
+ s->Get(7, r.IsManaged);
+ s->Get(8, r.IsMentionable);
+
+ return r;
+}
+
+std::optional<UserData> Store::GetUser(Snowflake id) const {
+ auto &s = m_stmt_get_user;
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching user %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ UserData r;
+
+ r.ID = id;
+ s->Get(1, r.Username);
+ s->Get(2, r.Discriminator);
+ s->Get(3, r.Avatar);
+ s->Get(4, r.IsBot);
+ s->Get(5, r.IsSystem);
+ s->Get(6, r.IsMFAEnabled);
+ s->Get(7, r.PremiumType);
+ s->Get(8, r.PublicFlags);
+
+ s->Reset();
+
+ return r;
+}
+
+void Store::ClearGuild(Snowflake id) {
+ auto &s = m_stmt_clr_guild;
+
+ s->Bind(1, id);
+ s->Step();
+ s->Reset();
+}
+
+void Store::ClearChannel(Snowflake id) {
+ auto &s = m_stmt_clr_chan;
+
+ s->Bind(1, id);
+ s->Step();
+ s->Reset();
+}
+
+void Store::ClearBan(Snowflake guild_id, Snowflake user_id) {
+ auto &s = m_stmt_clr_ban;
+
+ s->Bind(1, guild_id);
+ s->Bind(2, user_id);
+ s->Step();
+ s->Reset();
+}
+
+void Store::ClearRecipient(Snowflake channel_id, Snowflake user_id) {
+ auto &s = m_stmt_clr_recipient;
+
+ s->Bind(1, channel_id);
+ s->Bind(2, user_id);
+ s->Step();
+ s->Reset();
+}
+
+std::unordered_set<Snowflake> Store::GetChannels() const {
+ auto &s = m_stmt_get_chan_ids;
+ std::unordered_set<Snowflake> r;
+
+ while (s->FetchOne()) {
+ Snowflake id;
+ s->Get(0, id);
+ r.insert(id);
+ }
+
+ s->Reset();
+
+ return r;
+}
+
+std::unordered_set<Snowflake> Store::GetGuilds() const {
+ auto &s = m_stmt_get_guild_ids;
+ std::unordered_set<Snowflake> r;
+
+ while (s->FetchOne()) {
+ Snowflake id;
+ s->Get(0, id);
+ r.insert(id);
+ }
+
+ s->Reset();
+
+ return r;
+}
+
+void Store::ClearAll() {
+ if (m_db.Execute(R"(
+ DELETE FROM attachments;
+ DELETE FROM bans;
+ DELETE FROM channels;
+ DELETE FROM emojis;
+ DELETE FROM emoji_roles;
+ DELETE FROM guild_emojis;
+ DELETE FROM guild_features;
+ DELETE FROM guilds;
+ DELETE FROM members;
+ DELETE FROM member_roles;
+ DELETE FROM mentions;
+ DELETE FROM message_interactions;
+ DELETE FROM message_references;
+ DELETE FROM messages;
+ DELETE FROM permissions;
+ DELETE FROM reactions;
+ DELETE FROM recipients;
+ DELETE FROM roles;
+ DELETE FROM threads;
+ DELETE FROM users;
+ )") != SQLITE_OK) {
+ fprintf(stderr, "failed to clear: %s\n", m_db.ErrStr());
+ }
+}
+
+void Store::BeginTransaction() {
+ m_db.StartTransaction();
+}
+
+void Store::EndTransaction() {
+ m_db.EndTransaction();
+}
+
+bool Store::CreateTables() {
+ 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,
+ premium INTEGER,
+ pubflags INTEGER
+ )
+ )";
+
+ 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,
+ PRIMARY KEY(id, channel_id)
+ )
+ )";
+
+ 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,
+ embeds TEXT NOT NULL, /* json */
+ pinned BOOL,
+ webhook_id INTEGER,
+ type INTEGER,
+ application TEXT, /* json */
+ flags INTEGER,
+ stickers TEXT, /* json */
+ deleted BOOL, /* extra */
+ edited BOOL, /* extra */
+ pending BOOL, /* extra */
+ nonce TEXT,
+ sticker_items TEXT /* json */
+ )
+ )";
+
+ const char *create_roles = R"(
+ CREATE TABLE IF NOT EXISTS roles (
+ id INTEGER PRIMARY KEY,
+ guild INTEGER NOT NULL,
+ 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
+ )
+ )";
+
+ 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*/
+ creator_id INTEGER,
+ colons BOOL,
+ managed BOOL,
+ animated BOOL,
+ available BOOL
+ )
+ )";
+
+ const char *create_members = R"(
+ CREATE TABLE IF NOT EXISTS members (
+ user_id INTEGER NOT NULL,
+ guild_id INTEGER NOT NULL,
+ nickname TEXT,
+ joined_at TEXT NOT NULL,
+ premium_since TEXT,
+ deaf BOOL NOT NULL,
+ mute BOOL NOT NULL,
+ avatar TEXT,
+ pending BOOL,
+ PRIMARY KEY(user_id, guild_id)
+ )
+ )";
+
+ const 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,
+ 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,
+ 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
+ )
+ )";
+
+ const char *create_channels = R"(
+ CREATE TABLE IF NOT EXISTS channels (
+ id INTEGER PRIMARY KEY,
+ type INTEGER NOT NULL,
+ guild_id INTEGER,
+ position INTEGER,
+ name TEXT,
+ topic TEXT,
+ is_nsfw BOOL,
+ last_message_id INTEGER,
+ bitrate INTEGER,
+ user_limit INTEGER,
+ rate_limit INTEGER,
+ icon TEXT,
+ owner_id INTEGER,
+ application_id INTEGER,
+ parent_id INTEGER,
+ last_pin_timestamp TEXT,
+ archived BOOL, /* threads */
+ auto_archive INTEGER, /* threads */
+ archived_ts TEXT /* threads */
+ )
+ )";
+
+ const char *create_bans = R"(
+ CREATE TABLE IF NOT EXISTS bans (
+ guild_id INTEGER NOT NULL,
+ user_id INTEGER NOT NULL,
+ reason TEXT,
+ PRIMARY KEY(user_id, guild_id)
+ )
+ )";
+
+ const char *create_interactions = R"(
+ CREATE TABLE IF NOT EXISTS message_interactions (
+ message_id INTEGER NOT NULL,
+ interaction_id INTEGER NOT NULL,
+ type INTEGER NOT NULL,
+ name STRING NOT NULL,
+ user_id INTEGER NOT NULL,
+ PRIMARY KEY(message_id)
+ )
+ )";
+
+ const char *create_references = R"(
+ CREATE TABLE IF NOT EXISTS message_references (
+ id INTEGER NOT NULL,
+ message INTEGER,
+ channel INTEGER,
+ guild INTEGER,
+ PRIMARY KEY(id)
+ )
+ )";
+
+ const char *create_member_roles = R"(
+ CREATE TABLE IF NOT EXISTS member_roles (
+ user INTEGER NOT NULL,
+ role INTEGER NOT NULL,
+ PRIMARY KEY(user, role)
+ )
+ )";
+
+ const char *create_guild_emojis = R"(
+ CREATE TABLE IF NOT EXISTS guild_emojis (
+ guild INTEGER NOT NULL,
+ emoji INTEGER NOT NULL,
+ PRIMARY KEY(guild, emoji)
+ )
+ )";
+
+ const char *create_guild_features = R"(
+ CREATE TABLE IF NOT EXISTS guild_features (
+ guild INTEGER NOT NULL,
+ feature CHAR(63) NOT NULL,
+ PRIMARY KEY(guild, feature)
+ )
+ )";
+
+ const char *create_threads = R"(
+ CREATE TABLE IF NOT EXISTS threads (
+ guild INTEGER NOT NULL,
+ id INTEGER NOT NULL,
+ PRIMARY KEY(guild, id)
+ )
+ )";
+
+ const char *create_emoji_roles = R"(
+ CREATE TABLE IF NOT EXISTS emoji_roles (
+ emoji INTEGER NOT NULL,
+ role INTEGER NOT NULL,
+ PRIMARY KEY(emoji, role)
+ )
+ )";
+
+ const char *create_mentions = R"(
+ CREATE TABLE IF NOT EXISTS mentions (
+ message INTEGER NOT NULL,
+ user INTEGER NOT NULL,
+ PRIMARY KEY(message, user)
+ )
+ )";
+
+ const char *create_attachments = R"(
+ CREATE TABLE IF NOT EXISTS attachments (
+ message INTEGER NOT NULL,
+ id INTEGER NOT NULL,
+ filename TEXT NOT NULL,
+ size INTEGER NOT NULL,
+ url TEXT NOT NULL,
+ proxy TEXT NOT NULL,
+ height INTEGER,
+ width INTEGER,
+ PRIMARY KEY(message, id)
+ )
+ )";
+
+ const char *create_recipients = R"(
+ CREATE TABLE IF NOT EXISTS recipients (
+ channel INTEGER NOT NULL,
+ user INTEGER NOT NULL,
+ PRIMARY KEY(channel, user)
+ )
+ )";
+
+ const char *create_reactions = R"(
+ CREATE TABLE IF NOT EXISTS reactions (
+ message INTEGER NOT NULL,
+ emoji_id INTEGER,
+ name TEXT NOT NULL,
+ count INTEGER NOT NULL,
+ me BOOL NOT NULL,
+ idx INTEGER NOT NULL,
+ PRIMARY KEY(message, emoji_id, name)
+ )
+ )";
+
+ if (m_db.Execute(create_users) != SQLITE_OK) {
+ fprintf(stderr, "failed to create user table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_permissions) != SQLITE_OK) {
+ fprintf(stderr, "failed to create permissions table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_messages) != SQLITE_OK) {
+ fprintf(stderr, "failed to create messages table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_roles) != SQLITE_OK) {
+ fprintf(stderr, "failed to create roles table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_emojis) != SQLITE_OK) {
+ fprintf(stderr, "failed to create emojis table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_members) != SQLITE_OK) {
+ fprintf(stderr, "failed to create members table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_guilds) != SQLITE_OK) {
+ fprintf(stderr, "failed to create guilds table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_channels) != SQLITE_OK) {
+ fprintf(stderr, "failed to create channels table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_bans) != SQLITE_OK) {
+ fprintf(stderr, "failed to create bans table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_interactions) != SQLITE_OK) {
+ fprintf(stderr, "failed to create interactions table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_references) != SQLITE_OK) {
+ fprintf(stderr, "failed to create references table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_member_roles) != SQLITE_OK) {
+ fprintf(stderr, "failed to create member roles table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_guild_emojis) != SQLITE_OK) {
+ fprintf(stderr, "failed to create guild emojis table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_guild_features) != SQLITE_OK) {
+ fprintf(stderr, "failed to create guild features table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_threads) != SQLITE_OK) {
+ fprintf(stderr, "failed to create threads table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_emoji_roles) != SQLITE_OK) {
+ fprintf(stderr, "failed to create emoji roles table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_mentions) != SQLITE_OK) {
+ fprintf(stderr, "failed to create mentions table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_attachments) != SQLITE_OK) {
+ fprintf(stderr, "failed to create attachments table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_recipients) != SQLITE_OK) {
+ fprintf(stderr, "failed to create recipients table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_reactions) != SQLITE_OK) {
+ fprintf(stderr, "failed to create reactions table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(R"(
+ CREATE TRIGGER remove_zero_reactions AFTER UPDATE ON reactions WHEN new.count = 0
+ BEGIN
+ DELETE FROM reactions WHERE message = new.message AND emoji_id = new.emoji_id AND name = new.name;
+ END
+ )") != SQLITE_OK) {
+ fprintf(stderr, "failed to create reactions trigger: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ return true;
+}
+
+bool Store::CreateStatements() {
+ m_stmt_set_guild = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO guilds VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_guild->OK()) {
+ fprintf(stderr, "failed to prepare set guild statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM guilds WHERE id = ?
+ )");
+ if (!m_stmt_get_guild->OK()) {
+ fprintf(stderr, "failed to prepare get guild statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_ids = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM guilds
+ )");
+ if (!m_stmt_get_guild_ids->OK()) {
+ fprintf(stderr, "failed to prepare get guild ids statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_guild = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM guilds WHERE id = ?
+ )");
+ if (!m_stmt_clr_guild->OK()) {
+ fprintf(stderr, "failed to prepare clear guild statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_chan = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO channels VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_chan->OK()) {
+ fprintf(stderr, "failed to prepare set channel statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_chan = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM channels WHERE id = ?
+ )");
+ if (!m_stmt_get_chan->OK()) {
+ fprintf(stderr, "failed to prepare get channel statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_chan_ids = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM channels
+ )");
+ if (!m_stmt_get_chan_ids->OK()) {
+ fprintf(stderr, "failed to prepare get channel ids statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_chan = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM channels WHERE id = ?
+ )");
+ if (!m_stmt_clr_chan->OK()) {
+ fprintf(stderr, "failed to prepare clear channel statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_msg = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO messages VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_msg->OK()) {
+ fprintf(stderr, "failed to prepare set message statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ // wew
+ m_stmt_get_msg = std::make_unique<Statement>(m_db, R"(
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message,
+ message_references.channel,
+ message_references.guild
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE messages.id = ?1
+ UNION ALL
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message,
+ message_references.channel,
+ message_references.guild
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE messages.id = (SELECT message FROM message_references WHERE id = ?1)
+ ORDER BY messages.id DESC
+ )");
+ if (!m_stmt_get_msg->OK()) {
+ fprintf(stderr, "failed to prepare get message statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_msg_ref = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO message_references VALUES (
+ ?, ?, ?, ?
+ );
+ )");
+ if (!m_stmt_set_msg_ref->OK()) {
+ fprintf(stderr, "failed to prepare set message reference statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_last_msgs = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM (
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message,
+ message_references.channel,
+ message_references.guild
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE channel_id = ? AND pending = 0 ORDER BY id DESC LIMIT ?
+ ) ORDER BY id ASC
+ )");
+ if (!m_stmt_get_last_msgs->OK()) {
+ fprintf(stderr, "failed to prepare get last messages statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_user = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO users VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_user->OK()) {
+ fprintf(stderr, "failed to prepare set user statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_user = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM users WHERE id = ?
+ )");
+ if (!m_stmt_get_user->OK()) {
+ fprintf(stderr, "failed to prepare get user statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_member = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO members VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_member->OK()) {
+ fprintf(stderr, "failed to prepare set member statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_member = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM members WHERE user_id = ? AND guild_id = ?
+ )");
+ if (!m_stmt_get_member->OK()) {
+ fprintf(stderr, "failed to prepare get member statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_role = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO roles VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_role->OK()) {
+ fprintf(stderr, "failed to prepare set role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_role = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM roles WHERE id = ?
+ )");
+ if (!m_stmt_get_role->OK()) {
+ fprintf(stderr, "failed to prepare get role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_roles = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM roles WHERE guild = ?
+ )");
+ if (!m_stmt_get_guild_roles->OK()) {
+ fprintf(stderr, "failed to prepare get guild roles statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_emoji = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO emojis VALUES (
+ ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_emoji->OK()) {
+ fprintf(stderr, "failed to prepare set emoji statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_emoji = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM emojis WHERE id = ?
+ )");
+ if (!m_stmt_get_emoji->OK()) {
+ fprintf(stderr, "failed to prepare get emoji statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_perm = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO permissions VALUES (
+ ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_perm->OK()) {
+ fprintf(stderr, "failed to prepare set permission statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_perm = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM permissions WHERE id = ? AND channel_id = ?
+ )");
+ if (!m_stmt_get_perm->OK()) {
+ fprintf(stderr, "failed to prepare get permission statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_ban = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO bans VALUES (
+ ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_ban->OK()) {
+ fprintf(stderr, "failed to prepare set ban statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_ban = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM bans WHERE guild_id = ? AND user_id = ?
+ )");
+ if (!m_stmt_get_ban->OK()) {
+ fprintf(stderr, "failed to prepare get ban statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_bans = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM bans WHERE guild_id = ?
+ )");
+ if (!m_stmt_get_bans->OK()) {
+ fprintf(stderr, "failed to prepare get bans statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_ban = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM bans WHERE guild_id = ? AND user_id = ?
+ )");
+ if (!m_stmt_clr_ban->OK()) {
+ fprintf(stderr, "failed to prepare clear ban statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_interaction = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO message_interactions VALUES (
+ ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_interaction->OK()) {
+ fprintf(stderr, "failed to prepare set interaction statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_member_roles = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO member_roles VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_member_roles->OK()) {
+ fprintf(stderr, "faile to prepare set member roles statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_member_roles = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM roles, member_roles
+ WHERE roles.id = member_roles.role
+ AND member_roles.user = ?
+ AND roles.guild = ?
+ )");
+ if (!m_stmt_get_member_roles->OK()) {
+ fprintf(stderr, "failed to prepare get member role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_guild_emoji = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO guild_emojis VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_guild_emoji->OK()) {
+ fprintf(stderr, "failed to prepare set guild emoji statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_emojis = std::make_unique<Statement>(m_db, R"(
+ SELECT emoji FROM guild_emojis WHERE guild = ?
+ )");
+ if (!m_stmt_get_guild_emojis->OK()) {
+ fprintf(stderr, "failed to prepare get guild emojis statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_guild_emoji = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM guild_emojis WHERE guild = ? AND emoji = ?
+ )");
+ if (!m_stmt_clr_guild_emoji->OK()) {
+ fprintf(stderr, "failed to prepare clear guild emoji statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_guild_feature = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO guild_features VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_guild_feature->OK()) {
+ fprintf(stderr, "failed to prepare set guild feature statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_features = std::make_unique<Statement>(m_db, R"(
+ SELECT feature FROM guild_features WHERE guild = ?
+ )");
+ if (!m_stmt_get_guild_features->OK()) {
+ fprintf(stderr, "failed to prepare get guild features statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_chans = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM channels WHERE guild_id = ?
+ )");
+ if (!m_stmt_get_guild_chans->OK()) {
+ fprintf(stderr, "failed to prepare get guild channels statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_thread = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO threads VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_thread->OK()) {
+ fprintf(stderr, "failed to prepare set thread statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_threads = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM threads WHERE guild = ?
+ )");
+ if (!m_stmt_get_threads->OK()) {
+ fprintf(stderr, "failed to prepare get threads statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_active_threads = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM channels WHERE parent_id = ? AND (type = 10 OR type = 11 OR type = 12) AND archived = FALSE
+ )");
+ if (!m_stmt_get_active_threads->OK()) {
+ fprintf(stderr, "faile to prepare get active threads statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_messages_before = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM (
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE channel_id = ? AND pending = 0 AND messages.id < ? ORDER BY id DESC LIMIT ?
+ ) ORDER BY id ASC
+ )");
+ if (!m_stmt_get_messages_before->OK()) {
+ fprintf(stderr, "failed to prepare get messages before statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_pins = std::make_unique<Statement>(m_db, R"(
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message,
+ message_references.channel,
+ message_references.guild
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE channel_id = ? AND pinned = 1 ORDER BY id ASC
+ )");
+ if (!m_stmt_get_pins->OK()) {
+ fprintf(stderr, "failed to prepare get pins statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_emoji_role = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO emoji_roles VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_emoji_role->OK()) {
+ fprintf(stderr, "failed to prepare set emoji role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_emoji_roles = std::make_unique<Statement>(m_db, R"(
+ SELECT role FROM emoji_roles WHERE emoji = ?
+ )");
+ if (!m_stmt_get_emoji_roles->OK()) {
+ fprintf(stderr, "failed to prepare get emoji role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_mention = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO mentions VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_mention->OK()) {
+ fprintf(stderr, "failed to prepare set mention statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_mentions = std::make_unique<Statement>(m_db, R"(
+ SELECT user FROM mentions WHERE message = ?
+ )");
+ if (!m_stmt_get_mentions->OK()) {
+ fprintf(stderr, "failed to prepare get mentions statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_attachment = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO attachments VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_attachment->OK()) {
+ fprintf(stderr, "failed to prepare set attachment statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_attachments = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM attachments WHERE message = ?
+ )");
+ if (!m_stmt_get_attachments->OK()) {
+ fprintf(stderr, "failed to prepare get attachments statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_recipient = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO recipients VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_recipient->OK()) {
+ fprintf(stderr, "failed to prepare set recipient statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_recipients = std::make_unique<Statement>(m_db, R"(
+ SELECT user FROM recipients WHERE channel = ?
+ )");
+ if (!m_stmt_get_recipients->OK()) {
+ fprintf(stderr, "failed to prepare get recipients statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_recipient = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM recipients WHERE channel = ? AND user = ?
+ )");
+ if (!m_stmt_clr_recipient->OK()) {
+ fprintf(stderr, "failed to prepare clear recipient statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ // probably not the best way to do this lol but i just want one statement i guess
+ m_stmt_add_reaction = std::make_unique<Statement>(m_db, R"(
+ INSERT OR REPLACE INTO reactions VALUES (
+ ?1, ?2, ?3,
+ COALESCE(
+ (SELECT count FROM reactions WHERE message = ?1 AND emoji_id = ?2 AND name = ?3),
+ 0
+ ) + ?4,
+ COALESCE(
+ ?5,
+ (SELECT me FROM reactions WHERE message = ?1 AND emoji_id = ?2 AND name = ?3),
+ false
+ ),
+ COALESCE(
+ ?6,
+ (SELECT idx FROM reactions WHERE message = ?1 AND emoji_id = ?2 AND name = ?3),
+ (SELECT MAX(idx) + 1 FROM reactions WHERE message = ?1),
+ 0
+ )
+ )
+ )");
+ if (!m_stmt_add_reaction->OK()) {
+ fprintf(stderr, "failed to prepare add reaction statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_sub_reaction = std::make_unique<Statement>(m_db, R"(
+ UPDATE reactions
+ SET count = count - 1,
+ me = COALESCE(?4, me)
+ WHERE message = ?1 AND emoji_id = ?2 AND name = ?3
+ )");
+ if (!m_stmt_sub_reaction->OK()) {
+ fprintf(stderr, "failed to prepare sub reaction statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_reactions = std::make_unique<Statement>(m_db, R"(
+ SELECT emoji_id, name, count, me, idx FROM reactions WHERE message = ?
+ )");
+ if (!m_stmt_get_reactions->OK()) {
+ fprintf(stderr, "failed to prepare get reactions statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ return true;
+}
+
+Store::Database::Database(const char *path) {
+ m_err = sqlite3_open(path, &m_db);
+}
+
+Store::Database::~Database() {
+ Close();
+}
+
+int Store::Database::Close() {
+ if (m_db == nullptr) return m_err;
+ m_signal_close.emit();
+ m_err = sqlite3_close(m_db);
+ m_db = nullptr;
+ return m_err;
+}
+
+int Store::Database::StartTransaction() {
+ return m_err = Execute("BEGIN TRANSACTION");
+}
+
+int Store::Database::EndTransaction() {
+ return m_err = Execute("COMMIT");
+}
+
+int Store::Database::Execute(const char *command) {
+ return m_err = sqlite3_exec(m_db, command, nullptr, nullptr, nullptr);
+}
+
+int Store::Database::Error() const {
+ return m_err;
+}
+
+bool Store::Database::OK() const {
+ return Error() == SQLITE_OK;
+}
+
+const char *Store::Database::ErrStr() const {
+ const char *errstr = sqlite3_errstr(m_err);
+ const char *errmsg = sqlite3_errmsg(m_db);
+ std::string tmp = errstr + std::string("\n\t") + errmsg;
+ tmp.copy(m_err_scratch, sizeof(m_err_scratch) - 1);
+ m_err_scratch[std::min(tmp.size(), sizeof(m_err_scratch) - 1)] = '\0';
+ return m_err_scratch;
+}
+
+int Store::Database::SetError(int err) {
+ return m_err = err;
+}
+
+sqlite3 *Store::Database::obj() {
+ return m_db;
+}
+
+Store::Database::type_signal_close Store::Database::signal_close() {
+ return m_signal_close;
+}
+
+Store::Statement::Statement(Database &db, const char *command)
+ : m_db(&db) {
+ if (m_db->SetError(sqlite3_prepare_v2(m_db->obj(), command, -1, &m_stmt, nullptr)) != SQLITE_OK) return;
+ m_db->signal_close().connect([this] {
+ sqlite3_finalize(m_stmt);
+ m_stmt = nullptr;
+ });
+}
+
+Store::Statement::~Statement() {
+ sqlite3_finalize(m_stmt);
+}
+
+bool Store::Statement::OK() const {
+ return m_stmt != nullptr;
+}
+
+int Store::Statement::Bind(int index, Snowflake id) {
+ return Bind(index, static_cast<uint64_t>(id));
+}
+
+int Store::Statement::Bind(int index, const char *str, size_t len) {
+ if (len == -1) len = strlen(str);
+ return m_db->SetError(sqlite3_bind_blob(m_stmt, index, str, len, SQLITE_TRANSIENT));
+}
+
+int Store::Statement::Bind(int index, const std::string &str) {
+ return m_db->SetError(sqlite3_bind_blob(m_stmt, index, str.c_str(), str.size(), SQLITE_TRANSIENT));
+}
+
+int Store::Statement::Bind(int index) {
+ return m_db->SetError(sqlite3_bind_null(m_stmt, index));
+}
+
+void Store::Statement::Get(int index, Snowflake &out) const {
+ out = static_cast<uint64_t>(sqlite3_column_int64(m_stmt, index));
+}
+
+void Store::Statement::Get(int index, std::string &out) const {
+ const unsigned char *ptr = sqlite3_column_text(m_stmt, index);
+ if (ptr == nullptr)
+ out = "";
+ else
+ out = reinterpret_cast<const char *>(ptr);
+}
+
+bool Store::Statement::IsNull(int index) const {
+ return sqlite3_column_type(m_stmt, index) == SQLITE_NULL;
+}
+
+int Store::Statement::Step() {
+ return m_db->SetError(sqlite3_step(m_stmt));
+}
+
+bool Store::Statement::Insert() {
+ return m_db->SetError(sqlite3_step(m_stmt)) == SQLITE_DONE;
+}
+
+bool Store::Statement::FetchOne() {
+ return m_db->SetError(sqlite3_step(m_stmt)) == SQLITE_ROW;
+}
+
+int Store::Statement::Reset() {
+ if (m_db->SetError(sqlite3_reset(m_stmt)) != SQLITE_OK)
+ return m_db->Error();
+ if (m_db->SetError(sqlite3_clear_bindings(m_stmt)) != SQLITE_OK)
+ return m_db->Error();
+ return m_db->Error();
+}
+
+sqlite3_stmt *Store::Statement::obj() {
+ return m_stmt;
+}