#include "store.hpp" #include 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; } 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(guild_id), static_cast(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(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(chan.ID), static_cast(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(chan.ID), static_cast(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(id), static_cast(r), m_db.ErrStr()); s->Reset(); } EndTransaction(); } if (!s->Insert()) fprintf(stderr, "emoji insert failed for %" PRIu64 ": %s\n", static_cast(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(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(guild.ID), static_cast(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(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(guild.ID), static_cast(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(user_id), static_cast(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(user_id), static_cast(guild_id), static_cast(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(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(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(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(id), static_cast(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(id), static_cast(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(id), static_cast(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(channel_id), static_cast(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(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(id), m_db.ErrStr()); s->Reset(); } std::optional 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(guild_id), static_cast(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 Store::GetBans(Snowflake guild_id) const { auto &s = m_stmt_get_bans; std::vector 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 Store::GetLastMessages(Snowflake id, size_t num) const { auto &s = m_stmt_get_last_msgs; std::vector 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(std::move(*ref)); } } return msgs; } std::vector Store::GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const { std::vector 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(std::move(*ref)); } } return msgs; } std::vector Store::GetPinnedMessages(Snowflake channel_id) const { std::vector 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(std::move(*ref)); } } return msgs; } std::vector Store::GetActiveThreads(Snowflake channel_id) const { std::vector 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; } std::vector Store::GetChannelIDsWithParentID(Snowflake channel_id) const { auto &s = m_stmt_get_chan_ids_parent; s->Bind(1, channel_id); std::vector ret; while (s->FetchOne()) { Snowflake x; s->Get(0, x); ret.push_back(x); } s->Reset(); return ret; } std::unordered_set Store::GetMembersInGuild(Snowflake guild_id) const { auto &s = m_stmt_get_guild_member_ids; s->Bind(1, guild_id); std::unordered_set ret; while (s->FetchOne()) { Snowflake x; s->Get(0, x); ret.insert(x); } 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(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(data.MessageID), m_db.ErrStr()); s->Reset(); } std::optional 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(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(11, r.Icon); 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 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 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(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 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(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->Get(27, r.PremiumTier); 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 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(user_id), static_cast(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 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(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(id), m_db.ErrStr()); s->Reset(); return top; } auto ref = GetMessageBound(s); top.ReferencedMessage = std::make_shared(std::move(ref)); s->Reset(); return top; } Message Store::GetMessageBound(std::unique_ptr &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 &q = r.MessageReference.emplace(); s->Get(25, q.MessageID); s->Get(26, q.ChannelID); s->Get(27, q.GuildID); } int num_attachments; s->Get(28, num_attachments); if (num_attachments > 0) { auto &s = m_stmt_get_attachments; s->Bind(1, r.ID); while (s->FetchOne()) { auto &q = r.Attachments.emplace_back(); s->Get(1, q.ID); s->Get(2, q.Filename); s->Get(3, q.Bytes); s->Get(4, q.URL); s->Get(5, q.ProxyURL); s->Get(6, q.Height); s->Get(7, q.Width); } s->Reset(); } { 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 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 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(channel_id), static_cast(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 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(id), m_db.ErrStr()); s->Reset(); return {}; } auto role = GetRoleBound(s); s->Reset(); return role; } RoleData Store::GetRoleBound(std::unique_ptr &s) { 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 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(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(); } void Store::ClearRole(Snowflake id) { auto &s = m_stmt_clr_role; s->Bind(1, id); s->Step(); s->Reset(); } std::unordered_set Store::GetChannels() const { auto &s = m_stmt_get_chan_ids; std::unordered_set r; while (s->FetchOne()) { Snowflake id; s->Get(0, id); r.insert(id); } s->Reset(); return r; } std::unordered_set Store::GetGuilds() const { auto &s = m_stmt_get_guild_ids; std::unordered_set 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; } if (m_db.Execute(R"( CREATE TRIGGER remove_deleted_roles AFTER DELETE ON roles BEGIN DELETE FROM member_roles WHERE role = old.id; END )") != SQLITE_OK) { fprintf(stderr, "failed to create roles trigger: %s\n", m_db.ErrStr()); return false; } return true; } bool Store::CreateStatements() { m_stmt_set_guild = std::make_unique(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(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(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(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(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(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(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(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(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(m_db, R"( SELECT messages.*, message_interactions.interaction_id, message_interactions.name, message_interactions.type, message_interactions.user_id, message_references.message, message_references.channel, message_references.guild, COUNT(attachments.id) FROM messages LEFT OUTER JOIN message_interactions ON messages.id = message_interactions.message_id LEFT OUTER JOIN message_references ON messages.id = message_references.id LEFT JOIN attachments ON messages.id = attachments.message WHERE messages.id = ?1 GROUP BY messages.id UNION ALL SELECT messages.*, message_interactions.interaction_id, message_interactions.name, message_interactions.type, message_interactions.user_id, message_references.message, message_references.channel, message_references.guild, COUNT(attachments.id) FROM messages LEFT OUTER JOIN message_interactions ON messages.id = message_interactions.message_id LEFT OUTER JOIN message_references ON messages.id = message_references.id LEFT JOIN attachments ON messages.id = attachments.message WHERE messages.id = (SELECT message FROM message_references WHERE id = ?1) GROUP BY messages.id 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(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(m_db, R"( SELECT * FROM ( SELECT messages.*, message_interactions.interaction_id, message_interactions.name, message_interactions.type, message_interactions.user_id, message_references.message, message_references.channel, message_references.guild, COUNT(attachments.id) FROM messages LEFT OUTER JOIN message_interactions ON messages.id = message_interactions.message_id LEFT OUTER JOIN message_references ON messages.id = message_references.id LEFT JOIN attachments ON messages.id = attachments.message WHERE channel_id = ? AND pending = 0 GROUP BY messages.id 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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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; } m_stmt_get_chan_ids_parent = std::make_unique(m_db, R"( SELECT id FROM channels WHERE parent_id = ? )"); if (!m_stmt_get_chan_ids_parent->OK()) { fprintf(stderr, "failed to prepare get channel ids for parent statement: %s\n", m_db.ErrStr()); return false; } m_stmt_get_guild_member_ids = std::make_unique(m_db, R"( SELECT user_id FROM members WHERE guild_id = ? )"); if (!m_stmt_get_guild_member_ids->OK()) { fprintf(stderr, "failed to prepare get guild member ids statement: %s\n", m_db.ErrStr()); return false; } m_stmt_clr_role = std::make_unique(m_db, R"( DELETE FROM roles WHERE id = ?1; )"); if (!m_stmt_clr_role->OK()) { fprintf(stderr, "failed to prepare clear role statement: %s\n", m_db.ErrStr()); return false; } return true; } Store::Database::Database(const char *path) { if (path != ":memory:"s) { std::error_code ec; if (std::filesystem::exists(path, ec) && !std::filesystem::remove(path, ec)) { fprintf(stderr, "the database could not be removed. the database may be corrupted as a result\n"); } } 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(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, static_cast(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(), static_cast(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(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(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; }