initial sqlite, store user

This commit is contained in:
ouwou 2020-11-19 19:18:59 -05:00
parent 1463d8da9e
commit deb482a8db
13 changed files with 381 additions and 78 deletions

View File

@ -69,6 +69,9 @@ if(NOT USE_PKGCONFIG)
find_package(nlohmann_json 3.2.0 REQUIRED)
find_package(unofficial-sqlite3 CONFIG REQUIRED)
link_libraries(unofficial::sqlite3::sqlite3)
find_path(IXWEBSOCKET_INCLUDE_DIRS ixwebsocket/ixwebsocket.h)
find_library(IXWEBSOCKET_LIBRARY ixwebsocket)
link_libraries(${IXWEBSOCKET_LIBRARY})

View File

@ -54,7 +54,6 @@ int Abaddon::StartGTK() {
m_main_window = std::make_unique<MainWindow>();
m_main_window->set_title(APP_TITLE);
m_main_window->show();
m_main_window->UpdateComponents();
// crashes for some stupid reason if i put it somewhere else
@ -111,6 +110,13 @@ int Abaddon::StartGTK() {
dlg.run();
}
if (!m_discord.IsStoreValid()) {
Gtk::MessageDialog dlg(*m_main_window, "The Discord cache could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
dlg.run();
return 1;
}
m_main_window->show();
return m_gtk_app->run(*m_main_window);
}
@ -380,8 +386,8 @@ void Abaddon::ActionLeaveGuild(Snowflake id) {
void Abaddon::ActionKickMember(Snowflake user_id, Snowflake guild_id) {
ConfirmDialog dlg(*m_main_window);
const auto *user = m_discord.GetUser(user_id);
if (user != nullptr)
const auto user = m_discord.GetUser(user_id);
if (user.has_value())
dlg.SetConfirmText("Are you sure you want to kick " + user->Username + "#" + user->Discriminator + "?");
auto response = dlg.run();
if (response == Gtk::RESPONSE_OK)
@ -390,8 +396,8 @@ void Abaddon::ActionKickMember(Snowflake user_id, Snowflake guild_id) {
void Abaddon::ActionBanMember(Snowflake user_id, Snowflake guild_id) {
ConfirmDialog dlg(*m_main_window);
const auto *user = m_discord.GetUser(user_id);
if (user != nullptr)
const auto user = m_discord.GetUser(user_id);
if (user.has_value())
dlg.SetConfirmText("Are you sure you want to ban " + user->Username + "#" + user->Discriminator + "?");
auto response = dlg.run();
if (response == Gtk::RESPONSE_OK)

View File

@ -416,9 +416,9 @@ void ChatMessageItemContainer::HandleUserMentions(Gtk::TextView *tv) {
int mstart, mend;
if (!match.fetch_pos(0, mstart, mend)) break;
const Glib::ustring user_id = match.fetch(1);
const auto *user = discord.GetUser(user_id);
const auto user = discord.GetUser(user_id);
const auto *channel = discord.GetChannel(ChannelID);
if (user == nullptr || channel == nullptr) {
if (!user.has_value() || channel == nullptr) {
startpos = mend;
continue;
}
@ -814,8 +814,8 @@ void ChatMessageHeader::UpdateNameColor() {
const auto &discord = Abaddon::Get().GetDiscordClient();
const auto guild_id = discord.GetChannel(ChannelID)->GuildID;
const auto role_id = discord.GetMemberHoistedRole(guild_id, UserID, true);
const auto *user = discord.GetUser(UserID);
if (user == nullptr) return;
const auto user = discord.GetUser(UserID);
if (!user.has_value()) return;
const auto *role = discord.GetRole(role_id);
std::string md;

View File

@ -179,8 +179,8 @@ void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) {
} else {
const auto guild_id = client.GetChannel(m_active_channel)->GuildID;
const auto user_id = data->Author.ID;
const auto *user = client.GetUser(user_id);
if (user == nullptr) return;
const auto user = client.GetUser(user_id);
if (!user.has_value()) return;
header = Gtk::manage(new ChatMessageHeader(data));
header->signal_action_insert_mention().connect([this, user_id]() {

View File

@ -112,13 +112,13 @@ void MemberList::UpdateMemberListInternal() {
// process all the shit first so its in proper order
std::map<int, const Role *> pos_to_role;
std::map<int, std::vector<const User *>> pos_to_users;
std::map<int, std::vector<User>> pos_to_users;
std::unordered_map<Snowflake, int> user_to_color;
std::vector<Snowflake> roleless_users;
for (const auto &id : ids) {
auto *user = discord.GetUser(id);
if (user == nullptr) {
auto user = discord.GetUser(id);
if (!user.has_value()) {
roleless_users.push_back(id);
continue;
}
@ -134,7 +134,7 @@ void MemberList::UpdateMemberListInternal() {
};
pos_to_role[pos_role->Position] = pos_role;
pos_to_users[pos_role->Position].push_back(user);
pos_to_users[pos_role->Position].push_back(std::move(*user));
if (col_role != nullptr) {
if (ColorDistance(col_role->Color, 0xFFFFFF) < 15)
user_to_color[id] = 0x000000;
@ -198,10 +198,10 @@ void MemberList::UpdateMemberListInternal() {
if (pos_to_users.find(pos) == pos_to_users.end()) continue;
auto &users = pos_to_users.at(pos);
AlphabeticalSort(users.begin(), users.end(), [](auto e) { return e->Username; });
AlphabeticalSort(users.begin(), users.end(), [](const auto &e) { return e.Username; });
for (const auto data : users)
add_user(data);
add_user(&data);
}
if (chan->Type == ChannelType::DM || chan->Type == ChannelType::GROUP_DM)
@ -209,9 +209,9 @@ void MemberList::UpdateMemberListInternal() {
else
add_role("@everyone");
for (const auto &id : roleless_users) {
const auto *user = discord.GetUser(id);
if (user != nullptr)
add_user(user);
const auto user = discord.GetUser(id);
if (user.has_value())
add_user(&*user);
}
}

View File

@ -47,6 +47,10 @@ bool DiscordClient::IsStarted() const {
return m_client_connected;
}
bool DiscordClient::IsStoreValid() const {
return m_store.IsValid();
}
std::unordered_set<Snowflake> DiscordClient::GetGuildsID() const {
const auto &guilds = m_store.GetGuilds();
std::unordered_set<Snowflake> ret;
@ -121,6 +125,8 @@ void DiscordClient::FetchMessagesInChannel(Snowflake id, std::function<void(cons
std::vector<Snowflake> ids;
nlohmann::json::parse(r.text).get_to(msgs);
m_store.BeginTransaction();
for (const auto &msg : msgs) {
m_store.SetMessage(msg.ID, msg);
AddMessageToChannel(msg.ID, id);
@ -128,6 +134,7 @@ void DiscordClient::FetchMessagesInChannel(Snowflake id, std::function<void(cons
AddUserToGuild(msg.Author.ID, *msg.GuildID);
ids.push_back(msg.ID);
}
m_store.EndTransaction();
cb(ids);
});
@ -142,6 +149,8 @@ void DiscordClient::FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake
std::vector<Snowflake> ids;
nlohmann::json::parse(r.text).get_to(msgs);
m_store.BeginTransaction();
for (const auto &msg : msgs) {
m_store.SetMessage(msg.ID, msg);
AddMessageToChannel(msg.ID, channel_id);
@ -149,6 +158,7 @@ void DiscordClient::FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake
AddUserToGuild(msg.Author.ID, *msg.GuildID);
ids.push_back(msg.ID);
}
m_store.EndTransaction();
cb(ids);
});
@ -162,7 +172,7 @@ const Channel *DiscordClient::GetChannel(Snowflake id) const {
return m_store.GetChannel(id);
}
const User *DiscordClient::GetUser(Snowflake id) const {
std::optional<User> DiscordClient::GetUser(Snowflake id) const {
return m_store.GetUser(id);
}
@ -594,11 +604,13 @@ void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) {
for (auto &g : data.Guilds)
ProcessNewGuild(g);
m_store.BeginTransaction();
for (const auto &dm : data.PrivateChannels) {
m_store.SetChannel(dm.ID, dm);
for (const auto &recipient : dm.Recipients)
m_store.SetUser(recipient.ID, recipient);
}
m_store.EndTransaction();
m_session_id = data.SessionID;
m_user_data = data.User;
@ -642,8 +654,10 @@ void DiscordClient::HandleGatewayGuildMemberUpdate(const GatewayMessage &msg) {
void DiscordClient::HandleGatewayPresenceUpdate(const GatewayMessage &msg) {
PresenceUpdateMessage data = msg.Data;
auto cur = m_store.GetUser(data.User.at("id").get<Snowflake>());
if (cur != nullptr)
if (cur.has_value()) {
User::update_from_json(data.User, *cur);
m_store.SetUser(cur->ID, *cur);
}
}
void DiscordClient::HandleGatewayChannelDelete(const GatewayMessage &msg) {
@ -715,6 +729,8 @@ void DiscordClient::HandleGatewayMessageUpdate(const GatewayMessage &msg) {
void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg) {
GuildMemberListUpdateMessage data = msg.Data;
m_store.BeginTransaction();
bool has_sync = false;
for (const auto &op : data.Ops) {
if (op.Op == "SYNC") {
@ -730,6 +746,8 @@ void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg
}
}
m_store.EndTransaction();
// todo: manage this event a little better
if (has_sync)
m_signal_guild_member_list_update.emit(data.GuildID);

View File

@ -59,6 +59,7 @@ public:
void Start();
void Stop();
bool IsStarted() const;
bool IsStoreValid() const;
using guilds_type = Store::guilds_type;
using channels_type = Store::channels_type;
@ -81,7 +82,7 @@ public:
void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function<void(const std::vector<Snowflake> &)> cb);
const Message *GetMessage(Snowflake id) const;
const Channel *GetChannel(Snowflake id) const;
const User *GetUser(Snowflake id) const;
std::optional<User> GetUser(Snowflake id) const;
const Role *GetRole(Snowflake id) const;
const Guild *GetGuild(Snowflake id) const;
const GuildMember *GetMember(Snowflake user_id, Snowflake guild_id) const;

View File

@ -1,17 +1,12 @@
#pragma once
#include <nlohmann/json.hpp>
#include <optional>
#include "../util.hpp"
namespace detail { // more or less because idk what to name this stuff
template<typename T>
struct is_optional : ::std::false_type {};
template<typename T>
struct is_optional<::std::optional<T>> : ::std::true_type {};
template<typename T>
inline void json_direct(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value)
if constexpr (::util::is_optional<T>::value)
val = j.at(key).get<typename T::value_type>();
else
j.at(key).get_to(val);
@ -19,7 +14,7 @@ inline void json_direct(const ::nlohmann::json &j, const char *key, T &val) {
template<typename T>
inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value) {
if constexpr (::util::is_optional<T>::value) {
if (j.contains(key))
val = j.at(key).get<typename T::value_type>();
else
@ -32,7 +27,7 @@ inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) {
template<typename T>
inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value) {
if constexpr (::util::is_optional<T>::value) {
const auto &at = j.at(key);
if (!at.is_null())
val = at.get<typename T::value_type>();
@ -47,7 +42,7 @@ inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) {
template<typename T>
inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value) {
if constexpr (::util::is_optional<T>::value) {
if (j.contains(key)) {
const auto &at = j.at(key);
if (!at.is_null())
@ -68,7 +63,7 @@ inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T
template<typename T>
inline void json_update_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value) {
if constexpr (::util::is_optional<T>::value) {
if (j.contains(key)) {
const auto &at = j.at(key);
if (!at.is_null())
@ -89,7 +84,7 @@ inline void json_update_optional_nullable(const ::nlohmann::json &j, const char
template<typename T, typename U>
inline void json_update_optional_nullable_default(const ::nlohmann::json &j, const char *key, T &val, const U &fallback) {
if constexpr (is_optional<T>::value) {
if constexpr (::util::is_optional<T>::value) {
if (j.contains(key)) {
const auto &at = j.at(key);
if (at.is_null())

View File

@ -1,7 +1,71 @@
#include "store.hpp"
// hopefully the casting between signed and unsigned int64 doesnt cause issues
Store::Store() {
m_db_path = std::filesystem::temp_directory_path() / "abaddon-store.db";
m_db_err = sqlite3_open(m_db_path.string().c_str(), &m_db);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "error opening database: %s\n", sqlite3_errstr(m_db_err));
return;
}
m_db_err = sqlite3_exec(m_db, "PRAGMA journal_mode = WAL", nullptr, nullptr, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "enabling write-ahead-log failed: %s\n", sqlite3_errstr(m_db_err));
return;
}
m_db_err = sqlite3_exec(m_db, "PRAGMA synchronous = NORMAL", nullptr, nullptr, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "setting synchronous failed: %s\n", sqlite3_errstr(m_db_err));
return;
}
CreateTables();
CreateStatements();
}
Store::~Store() {
Cleanup();
m_db_err = sqlite3_close(m_db);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "error closing database: %s\n", sqlite3_errstr(m_db_err));
return;
}
std::filesystem::remove(m_db_path);
}
bool Store::IsValid() const {
return m_db_err == SQLITE_OK;
}
void Store::SetUser(Snowflake id, const User &user) {
m_users[id] = user;
if ((uint64_t)id == 0) {
printf("???: %s\n", user.Username.c_str());
}
Bind(m_set_user_stmt, 1, id);
Bind(m_set_user_stmt, 2, user.Username);
Bind(m_set_user_stmt, 3, user.Discriminator);
Bind(m_set_user_stmt, 4, user.Avatar);
Bind(m_set_user_stmt, 5, user.IsBot);
Bind(m_set_user_stmt, 6, user.IsSystem);
Bind(m_set_user_stmt, 7, user.IsMFAEnabled);
Bind(m_set_user_stmt, 8, user.Locale);
Bind(m_set_user_stmt, 9, user.IsVerified);
Bind(m_set_user_stmt, 10, user.Email);
Bind(m_set_user_stmt, 11, user.Flags);
Bind(m_set_user_stmt, 12, user.PremiumType);
Bind(m_set_user_stmt, 13, user.PublicFlags);
if (!RunInsert(m_set_user_stmt)) {
fprintf(stderr, "user insert failed: %s\n", sqlite3_errstr(m_db_err));
}
// m_users[id] = user;
}
void Store::SetChannel(Snowflake id, const Channel &channel) {
@ -32,18 +96,33 @@ void Store::SetEmoji(Snowflake id, const Emoji &emoji) {
m_emojis[id] = emoji;
}
User *Store::GetUser(Snowflake id) {
auto it = m_users.find(id);
if (it == m_users.end())
return nullptr;
return &it->second;
}
std::optional<User> Store::GetUser(Snowflake id) const {
Bind(m_get_user_stmt, 1, id);
if (!FetchOne(m_get_user_stmt)) {
if (m_db_err != SQLITE_DONE) // not an error, just means user isnt found
fprintf(stderr, "error while fetching user info: %s\n", sqlite3_errstr(m_db_err));
Reset(m_get_user_stmt);
return std::nullopt;
}
const User *Store::GetUser(Snowflake id) const {
auto it = m_users.find(id);
if (it == m_users.end())
return nullptr;
return &it->second;
User ret;
Get(m_get_user_stmt, 0, ret.ID);
Get(m_get_user_stmt, 1, ret.Username);
Get(m_get_user_stmt, 2, ret.Discriminator);
Get(m_get_user_stmt, 3, ret.Avatar);
Get(m_get_user_stmt, 4, ret.IsBot);
Get(m_get_user_stmt, 5, ret.IsSystem);
Get(m_get_user_stmt, 6, ret.IsMFAEnabled);
Get(m_get_user_stmt, 7, ret.Locale);
Get(m_get_user_stmt, 8, ret.IsVerified);
Get(m_get_user_stmt, 9, ret.Email);
Get(m_get_user_stmt, 10, ret.Flags);
Get(m_get_user_stmt, 11, ret.PremiumType);
Get(m_get_user_stmt, 12, ret.PublicFlags);
Reset(m_get_user_stmt);
return std::optional<User>(std::move(ret));
}
Channel *Store::GetChannel(Snowflake id) {
@ -186,3 +265,136 @@ void Store::ClearAll() {
m_roles.clear();
m_users.clear();
}
void Store::BeginTransaction() {
m_db_err = sqlite3_exec(m_db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr);
}
void Store::EndTransaction() {
m_db_err = sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr);
}
bool Store::CreateTables() {
constexpr const char *create_users = R"(
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
discriminator TEXT NOT NULL,
avatar TEXT,
bot BOOL,
system BOOL,
mfa BOOL,
locale TEXT,
verified BOOl,
email TEXT,
flags INTEGER,
premium INTEGER,
pubflags INTEGER
)
)";
m_db_err = sqlite3_exec(m_db, create_users, nullptr, nullptr, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "failed to create user table\n");
return false;
}
return true;
}
bool Store::CreateStatements() {
constexpr const char *set_user = R"(
REPLACE INTO users VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
)";
constexpr const char *get_user = R"(
SELECT * FROM users WHERE id = ?
)";
m_db_err = sqlite3_prepare_v2(m_db, set_user, -1, &m_set_user_stmt, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "failed to prepare set user statement\n");
return false;
}
m_db_err = sqlite3_prepare_v2(m_db, get_user, -1, &m_get_user_stmt, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "failed to prepare get user statement\n");
return false;
}
return true;
}
void Store::Cleanup() {
sqlite3_finalize(m_set_user_stmt);
sqlite3_finalize(m_get_user_stmt);
}
void Store::Bind(sqlite3_stmt *stmt, int index, int num) const {
m_db_err = sqlite3_bind_int(stmt, index, num);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
}
}
void Store::Bind(sqlite3_stmt *stmt, int index, uint64_t num) const {
m_db_err = sqlite3_bind_int64(stmt, index, num);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
}
}
void Store::Bind(sqlite3_stmt *stmt, int index, const std::string &str) const {
m_db_err = sqlite3_bind_text(stmt, index, str.c_str(), -1, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
}
}
void Store::Bind(sqlite3_stmt *stmt, int index, bool val) const {
m_db_err = sqlite3_bind_int(stmt, index, val ? 1 : 0);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
}
}
bool Store::RunInsert(sqlite3_stmt *stmt) {
m_db_err = sqlite3_step(stmt);
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
return m_db_err == SQLITE_DONE;
}
bool Store::FetchOne(sqlite3_stmt *stmt) const {
m_db_err = sqlite3_step(stmt);
return m_db_err == SQLITE_ROW;
}
void Store::Get(sqlite3_stmt *stmt, int index, int &out) const {
out = sqlite3_column_int(stmt, index);
}
void Store::Get(sqlite3_stmt *stmt, int index, std::string &out) const {
const unsigned char *ptr = sqlite3_column_text(stmt, index);
if (ptr == nullptr)
out = "";
else
out = reinterpret_cast<const char *>(ptr);
}
void Store::Get(sqlite3_stmt *stmt, int index, bool &out) const {
out = sqlite3_column_int(stmt, index) != 0;
}
void Store::Get(sqlite3_stmt *stmt, int index, Snowflake &out) const {
const int64_t num = sqlite3_column_int64(stmt, index);
out = static_cast<uint64_t>(num);
}
void Store::Reset(sqlite3_stmt *stmt) const {
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
}

View File

@ -1,7 +1,10 @@
#pragma once
#include "../util.hpp"
#include "objects.hpp"
#include <unordered_map>
#include <mutex>
#include <filesystem>
#include <sqlite3.h>
#ifdef GetMessage // fuck you windows.h
#undef GetMessage
@ -9,6 +12,11 @@
class Store {
public:
Store();
~Store();
bool IsValid() const;
void SetUser(Snowflake id, const User &user);
void SetChannel(Snowflake id, const Channel &channel);
void SetGuild(Snowflake id, const Guild &guild);
@ -18,7 +26,8 @@ public:
void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm);
void SetEmoji(Snowflake id, const Emoji &emoji);
User *GetUser(Snowflake id);
// slap const on everything even tho its not *really* const
Channel *GetChannel(Snowflake id);
Guild *GetGuild(Snowflake id);
Role *GetRole(Snowflake id);
@ -26,7 +35,7 @@ public:
GuildMember *GetGuildMemberData(Snowflake guild_id, Snowflake user_id);
PermissionOverwrite *GetPermissionOverwrite(Snowflake channel_id, Snowflake id);
Emoji *GetEmoji(Snowflake id);
const User *GetUser(Snowflake id) const;
std::optional<User> GetUser(Snowflake id) const;
const Channel *GetChannel(Snowflake id) const;
const Guild *GetGuild(Snowflake id) const;
const Role *GetRole(Snowflake id) const;
@ -53,6 +62,9 @@ public:
void ClearAll();
void BeginTransaction();
void EndTransaction();
private:
users_type m_users;
channels_type m_channels;
@ -62,4 +74,50 @@ private:
members_type m_members;
permission_overwrites_type m_permissions;
emojis_type m_emojis;
bool CreateTables();
bool CreateStatements();
void Cleanup();
template<typename T>
void Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const;
void Bind(sqlite3_stmt *stmt, int index, int num) const;
void Bind(sqlite3_stmt *stmt, int index, uint64_t num) const;
void Bind(sqlite3_stmt *stmt, int index, const std::string &str) const;
void Bind(sqlite3_stmt *stmt, int index, bool val) const;
bool RunInsert(sqlite3_stmt *stmt);
bool FetchOne(sqlite3_stmt *stmt) const;
template<typename T>
void Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const;
void Get(sqlite3_stmt *stmt, int index, int &out) const;
void Get(sqlite3_stmt *stmt, int index, uint64_t &out) const;
void Get(sqlite3_stmt *stmt, int index, std::string &out) const;
void Get(sqlite3_stmt *stmt, int index, bool &out) const;
void Get(sqlite3_stmt *stmt, int index, Snowflake &out) const;
void Reset(sqlite3_stmt *stmt) const;
std::filesystem::path m_db_path;
mutable sqlite3 *m_db;
mutable int m_db_err;
mutable sqlite3_stmt *m_set_user_stmt;
mutable sqlite3_stmt *m_get_user_stmt;
};
template<typename T>
inline void Store::Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const {
if (opt.has_value())
Bind(stmt, index, *opt);
else
sqlite3_bind_null(stmt, index);
}
template<typename T>
inline void Store::Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const {
if (sqlite3_column_type(stmt, index) == SQLITE_NULL)
out = std::nullopt;
else {
T v;
Get(stmt, index, v);
out = std::optional<T>(v);
}
}

View File

@ -34,20 +34,20 @@ void from_json(const nlohmann::json &j, User &m) {
}
void User::update_from_json(const nlohmann::json &j, User &m) {
JS_ON("username", m.Username);
JS_ON("discriminator", m.Discriminator);
JS_ON("avatar", m.Avatar);
JS_ON("bot", m.IsBot);
JS_ON("system", m.IsSystem);
JS_ON("mfa_enabled", m.IsMFAEnabled);
JS_ON("locale", m.Locale);
JS_ON("verified", m.IsVerified);
JS_ON("email", m.Email);
JS_ON("flags", m.Flags);
JS_ON("premium_type", m.PremiumType);
JS_ON("public_flags", m.PublicFlags);
JS_ON("desktop", m.IsDesktop);
JS_ON("mobile", m.IsMobile);
JS_ON("nsfw_allowed", m.IsNSFWAllowed);
JS_ON("phone", m.Phone);
JS_RD("username", m.Username);
JS_RD("discriminator", m.Discriminator);
JS_RD("avatar", m.Avatar);
JS_RD("bot", m.IsBot);
JS_RD("system", m.IsSystem);
JS_RD("mfa_enabled", m.IsMFAEnabled);
JS_RD("locale", m.Locale);
JS_RD("verified", m.IsVerified);
JS_RD("email", m.Email);
JS_RD("flags", m.Flags);
JS_RD("premium_type", m.PremiumType);
JS_RD("public_flags", m.PublicFlags);
JS_RD("desktop", m.IsDesktop);
JS_RD("mobile", m.IsMobile);
JS_RD("nsfw_allowed", m.IsNSFWAllowed);
JS_RD("phone", m.Phone);
}

View File

@ -4,19 +4,19 @@
#include <string>
struct User {
Snowflake ID; //
std::string Username; //
std::string Discriminator; //
std::string Avatar; // null
bool IsBot = false; // opt
bool IsSystem = false; // opt
bool IsMFAEnabled = false; // opt
std::string Locale; // opt
bool IsVerified = false; // opt
std::string Email; // opt, null
int Flags = 0; // opt
int PremiumType = 0; // opt, null (docs wrong)
int PublicFlags = 0; // opt
Snowflake ID;
std::string Username;
std::string Discriminator;
std::string Avatar; // null
std::optional<bool> IsBot;
std::optional<bool> IsSystem;
std::optional<bool> IsMFAEnabled;
std::optional<std::string> Locale;
std::optional<bool> IsVerified;
std::optional<std::string> Email; // null
std::optional<int> Flags;
std::optional<int> PremiumType; // null
std::optional<int> PublicFlags;
// undocumented (opt)
bool IsDesktop = false; //

View File

@ -11,6 +11,16 @@
#include <regex>
#include <mutex>
#include <condition_variable>
#include <optional>
#include <type_traits>
namespace util {
template<typename T>
struct is_optional : ::std::false_type {};
template<typename T>
struct is_optional<::std::optional<T>> : ::std::true_type {};
}
class Semaphore {
public: