diff options
Diffstat (limited to 'src/discord/store.cpp')
-rw-r--r-- | src/discord/store.cpp | 2232 |
1 files changed, 2232 insertions, 0 deletions
diff --git a/src/discord/store.cpp b/src/discord/store.cpp new file mode 100644 index 0000000..9b615fd --- /dev/null +++ b/src/discord/store.cpp @@ -0,0 +1,2232 @@ +#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(); + } + + 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 {}; + } + + RoleData r; + + r.ID = 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); + + s->Reset(); + + 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_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; + std::string tmp = command; + m_db->signal_close().connect([tmp, 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; +} |