view and delete invites, more tweaking

This commit is contained in:
ouwou
2021-01-21 23:49:42 -05:00
parent 7b6fb861ac
commit 547124c94f
17 changed files with 477 additions and 104 deletions

View File

@@ -98,14 +98,17 @@ std::set<Snowflake> DiscordClient::GetMessagesForChannel(Snowflake id) const {
return ret;
}
void DiscordClient::FetchInviteData(std::string code, std::function<void(Invite)> cb, std::function<void(bool)> err) {
m_http.MakeGET("/invites/" + code + "?with_counts=true", [this, cb, err](cpr::Response r) {
void DiscordClient::FetchInvite(std::string code, sigc::slot<void(std::optional<InviteData>)> callback) {
sigc::signal<void, std::optional<InviteData>> signal;
signal.connect(callback);
m_http.MakeGET("/invites/" + code + "?with_counts=true", [this, callback](cpr::Response r) {
if (!CheckCode(r)) {
err(r.status_code == 404);
if (r.status_code == 404)
callback(std::nullopt);
return;
};
cb(nlohmann::json::parse(r.text));
callback(nlohmann::json::parse(r.text).get<InviteData>());
});
}
@@ -483,6 +486,18 @@ void DiscordClient::UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<
});
}
void DiscordClient::DeleteInvite(const std::string &code) {
DeleteInvite(code, [](const auto) {});
}
void DiscordClient::DeleteInvite(const std::string &code, sigc::slot<void(bool success)> callback) {
sigc::signal<void, bool> signal;
signal.connect(callback);
m_http.MakeDELETE("/invites/" + code, [this, callback](const cpr::Response &response) {
callback(CheckCode(response));
});
}
std::vector<BanData> DiscordClient::GetBansInGuild(Snowflake guild_id) {
return m_store.GetBans(guild_id);
}
@@ -491,7 +506,7 @@ void DiscordClient::FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::s
sigc::signal<void, BanData> signal;
signal.connect(callback);
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/bans/" + std::to_string(user_id), [this, callback, guild_id](const cpr::Response &response) {
if (response.status_code != 200) return;
if (!CheckCode(response)) return;
auto ban = nlohmann::json::parse(response.text).get<BanData>();
m_store.SetBan(guild_id, ban.User.ID, ban);
m_store.SetUser(ban.User.ID, ban.User);
@@ -503,6 +518,7 @@ void DiscordClient::FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vect
sigc::signal<void, std::vector<BanData>> signal;
signal.connect(callback);
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/bans", [this, callback, guild_id](const cpr::Response &response) {
if (!CheckCode(response)) return;
auto bans = nlohmann::json::parse(response.text).get<std::vector<BanData>>();
m_store.BeginTransaction();
for (const auto &ban : bans) {
@@ -514,6 +530,24 @@ void DiscordClient::FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vect
});
}
void DiscordClient::FetchGuildInvites(Snowflake guild_id, sigc::slot<void(std::vector<InviteData>)> callback) {
sigc::signal<void, std::vector<InviteData>> signal;
signal.connect(callback);
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/invites", [this, callback, guild_id](const cpr::Response &response) {
// store?
if (!CheckCode(response)) return;
auto invites = nlohmann::json::parse(response.text).get<std::vector<InviteData>>();
m_store.BeginTransaction();
for (const auto &invite : invites)
if (invite.Inviter.has_value())
m_store.SetUser(invite.Inviter->ID, *invite.Inviter);
m_store.EndTransaction();
callback(invites);
});
}
void DiscordClient::UpdateToken(std::string token) {
if (!IsStarted()) {
m_token = token;
@@ -676,6 +710,12 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
case GatewayEvent::GUILD_BAN_ADD: {
HandleGatewayGuildBanAdd(m);
} break;
case GatewayEvent::INVITE_CREATE: {
HandleGatewayInviteCreate(m);
} break;
case GatewayEvent::INVITE_DELETE: {
HandleGatewayInviteDelete(m);
} break;
}
} break;
default:
@@ -719,10 +759,10 @@ void DiscordClient::ProcessNewGuild(GuildData &guild) {
}
}
for (auto &r : guild.Roles)
for (auto &r : *guild.Roles)
m_store.SetRole(r.ID, r);
for (auto &e : guild.Emojis)
for (auto &e : *guild.Emojis)
m_store.SetEmoji(e.ID, e);
m_store.EndTransaction();
@@ -1000,6 +1040,32 @@ void DiscordClient::HandleGatewayGuildBanAdd(const GatewayMessage &msg) {
m_signal_guild_ban_add.emit(data.GuildID, data.User.ID);
}
void DiscordClient::HandleGatewayInviteCreate(const GatewayMessage &msg) {
InviteCreateObject data = msg.Data;
InviteData invite;
invite.Code = std::move(data.Code);
invite.CreatedAt = std::move(data.CreatedAt);
invite.Channel = *m_store.GetChannel(data.ChannelID);
invite.Guild = *m_store.GetGuild(*invite.Channel->GuildID);
invite.Inviter = std::move(data.Inviter);
invite.IsTemporary = std::move(data.IsTemporary);
invite.MaxAge = std::move(data.MaxAge);
invite.MaxUses = std::move(data.MaxUses);
invite.TargetUser = std::move(data.TargetUser);
invite.TargetUserType = std::move(data.TargetUserType);
invite.Uses = std::move(data.Uses);
m_signal_invite_create.emit(invite);
}
void DiscordClient::HandleGatewayInviteDelete(const GatewayMessage &msg) {
InviteDeleteObject data = msg.Data;
if (!data.GuildID.has_value()) {
const auto chan = GetChannel(data.ChannelID);
data.GuildID = chan->ID;
}
m_signal_invite_delete.emit(data);
}
void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) {
m_signal_disconnected.emit(true);
inflateEnd(&m_zstream);
@@ -1223,6 +1289,8 @@ void DiscordClient::LoadEventMap() {
m_event_map["TYPING_START"] = GatewayEvent::TYPING_START;
m_event_map["GUILD_BAN_REMOVE"] = GatewayEvent::GUILD_BAN_REMOVE;
m_event_map["GUILD_BAN_ADD"] = GatewayEvent::GUILD_BAN_ADD;
m_event_map["INVITE_CREATE"] = GatewayEvent::INVITE_CREATE;
m_event_map["INVITE_DELETE"] = GatewayEvent::INVITE_DELETE;
}
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
@@ -1312,3 +1380,11 @@ DiscordClient::type_signal_guild_ban_remove DiscordClient::signal_guild_ban_remo
DiscordClient::type_signal_guild_ban_add DiscordClient::signal_guild_ban_add() {
return m_signal_guild_ban_add;
}
DiscordClient::type_signal_invite_create DiscordClient::signal_invite_create() {
return m_signal_invite_create;
}
DiscordClient::type_signal_invite_delete DiscordClient::signal_invite_delete() {
return m_signal_invite_delete;
}

View File

@@ -75,7 +75,6 @@ public:
std::set<Snowflake> GetMessagesForChannel(Snowflake id) const;
std::set<Snowflake> GetPrivateChannels() const;
void FetchInviteData(std::string code, std::function<void(Invite)> cb, std::function<void(bool)> err);
void FetchMessagesInChannel(Snowflake id, std::function<void(const std::vector<Snowflake> &)> cb);
void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function<void(const std::vector<Snowflake> &)> cb);
std::optional<Message> GetMessage(Snowflake id) const;
@@ -117,12 +116,17 @@ public:
void SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(bool success)> callback);
void UnbanUser(Snowflake guild_id, Snowflake user_id);
void UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(bool success)> callback);
void DeleteInvite(const std::string &code);
void DeleteInvite(const std::string &code, sigc::slot<void(bool success)> callback);
// FetchGuildBans fetches all bans+reasons via api, this func fetches stored bans (so usually just GUILD_BAN_ADD data)
std::vector<BanData> GetBansInGuild(Snowflake guild_id);
void FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::slot<void(BanData)> callback);
void FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vector<BanData>)> callback);
void FetchInvite(std::string code, sigc::slot<void(std::optional<InviteData>)> callback);
void FetchGuildInvites(Snowflake guild_id, sigc::slot<void(std::vector<InviteData>)> callback);
void UpdateToken(std::string token);
void SetUserAgent(std::string agent);
@@ -161,6 +165,8 @@ private:
void HandleGatewayTypingStart(const GatewayMessage &msg);
void HandleGatewayGuildBanRemove(const GatewayMessage &msg);
void HandleGatewayGuildBanAdd(const GatewayMessage &msg);
void HandleGatewayInviteCreate(const GatewayMessage &msg);
void HandleGatewayInviteDelete(const GatewayMessage &msg);
void HandleGatewayReconnect(const GatewayMessage &msg);
void HeartbeatThread();
void SendIdentify();
@@ -231,7 +237,9 @@ public:
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_member_update; // guild id, user id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_ban_remove; // guild id, user id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_ban_add; // guild id, user id
typedef sigc::signal<void, bool> type_signal_disconnected; // bool true if reconnecting
typedef sigc::signal<void, InviteData> type_signal_invite_create;
typedef sigc::signal<void, InviteDeleteObject> type_signal_invite_delete;
typedef sigc::signal<void, bool> type_signal_disconnected; // bool true if reconnecting
typedef sigc::signal<void> type_signal_connected;
type_signal_gateway_ready signal_gateway_ready();
@@ -254,6 +262,8 @@ public:
type_signal_guild_member_update signal_guild_member_update();
type_signal_guild_ban_remove signal_guild_ban_remove();
type_signal_guild_ban_add signal_guild_ban_add();
type_signal_invite_create signal_invite_create();
type_signal_invite_delete signal_invite_delete(); // safe to assume guild id is set
type_signal_disconnected signal_disconnected();
type_signal_connected signal_connected();
@@ -278,6 +288,8 @@ protected:
type_signal_guild_member_update m_signal_guild_member_update;
type_signal_guild_ban_remove m_signal_guild_ban_remove;
type_signal_guild_ban_add m_signal_guild_ban_add;
type_signal_invite_create m_signal_invite_create;
type_signal_invite_delete m_signal_invite_delete;
type_signal_disconnected m_signal_disconnected;
type_signal_connected m_signal_connected;
};

View File

@@ -13,29 +13,29 @@ void from_json(const nlohmann::json &j, GuildData &m) {
JS_N("splash", m.Splash);
JS_ON("discovery_splash", m.DiscoverySplash);
JS_O("owner", m.IsOwner);
JS_D("owner_id", m.OwnerID);
JS_O("owner_id", m.OwnerID);
std::optional<std::string> tmp;
JS_O("permissions", tmp);
if (tmp.has_value())
m.Permissions = std::stoull(*tmp);
JS_D("region", m.VoiceRegion);
JS_N("afk_channel_id", m.AFKChannelID);
JS_D("afk_timeout", m.AFKTimeout);
JS_O("region", m.VoiceRegion);
JS_ON("afk_channel_id", m.AFKChannelID);
JS_O("afk_timeout", m.AFKTimeout);
JS_O("embed_enabled", m.IsEmbedEnabled);
JS_ON("embed_channel_id", m.EmbedChannelID);
JS_D("verification_level", m.VerificationLevel);
JS_D("default_message_notifications", m.DefaultMessageNotifications);
JS_D("explicit_content_filter", m.ExplicitContentFilter);
JS_D("roles", m.Roles);
JS_D("emojis", m.Emojis);
JS_D("features", m.Features);
JS_D("mfa_level", m.MFALevel);
JS_N("application_id", m.ApplicationID);
JS_O("verification_level", m.VerificationLevel);
JS_O("default_message_notifications", m.DefaultMessageNotifications);
JS_O("explicit_content_filter", m.ExplicitContentFilter);
JS_O("roles", m.Roles);
JS_O("emojis", m.Emojis);
JS_O("features", m.Features);
JS_O("mfa_level", m.MFALevel);
JS_ON("application_id", m.ApplicationID);
JS_O("widget_enabled", m.IsWidgetEnabled);
JS_ON("widget_channel_id", m.WidgetChannelID);
JS_N("system_channel_id", m.SystemChannelID);
JS_D("system_channel_flags", m.SystemChannelFlags);
JS_N("rules_channel_id", m.RulesChannelID);
JS_ON("system_channel_id", m.SystemChannelID);
JS_O("system_channel_flags", m.SystemChannelFlags);
JS_ON("rules_channel_id", m.RulesChannelID);
JS_O("joined_at", m.JoinedAt);
JS_O("large", m.IsLarge);
JS_O("unavailable", m.IsUnavailable);
@@ -46,13 +46,13 @@ void from_json(const nlohmann::json &j, GuildData &m) {
// JS_O("presences", m.Presences);
JS_ON("max_presences", m.MaxPresences);
JS_O("max_members", m.MaxMembers);
JS_N("vanity_url_code", m.VanityURL);
JS_N("description", m.Description);
JS_N("banner", m.BannerHash);
JS_D("premium_tier", m.PremiumTier);
JS_ON("vanity_url_code", m.VanityURL);
JS_ON("description", m.Description);
JS_ON("banner", m.BannerHash);
JS_O("premium_tier", m.PremiumTier);
JS_O("premium_subscription_count", m.PremiumSubscriptionCount);
JS_D("preferred_locale", m.PreferredLocale);
JS_N("public_updates_channel_id", m.PublicUpdatesChannelID);
JS_O("preferred_locale", m.PreferredLocale);
JS_ON("public_updates_channel_id", m.PublicUpdatesChannelID);
JS_O("max_video_channel_users", m.MaxVideoChannelUsers);
JS_O("approximate_member_count", tmp);
if (tmp.has_value())
@@ -119,7 +119,8 @@ void GuildData::update_from_json(const nlohmann::json &j) {
}
bool GuildData::HasFeature(const std::string &search_feature) {
for (const auto &feature : Features)
if (!Features.has_value()) return false;
for (const auto &feature : *Features)
if (search_feature == feature)
return true;
return false;

View File

@@ -9,6 +9,9 @@
// a bot is apparently only supposed to receive the `id` and `unavailable` as false
// but user tokens seem to get the full objects (minus users)
// everythings optional cuz of muh partial guild object
// anything not marked optional in https://discord.com/developers/docs/resources/guild#guild-object is guaranteed to be set when returned from Store::GetGuild
struct GuildData {
Snowflake ID;
std::string Name;
@@ -16,44 +19,44 @@ struct GuildData {
std::string Splash; // null
std::optional<std::string> DiscoverySplash; // null
std::optional<bool> IsOwner;
Snowflake OwnerID;
std::optional<Snowflake> OwnerID;
std::optional<uint64_t> Permissions;
std::optional<std::string> PermissionsNew;
std::optional<std::string> VoiceRegion;
Snowflake AFKChannelID; // null
int AFKTimeout;
std::optional<Snowflake> AFKChannelID; // null
std::optional<int> AFKTimeout;
std::optional<bool> IsEmbedEnabled; // deprecated
std::optional<Snowflake> EmbedChannelID; // null, deprecated
int VerificationLevel;
int DefaultMessageNotifications;
int ExplicitContentFilter;
std::vector<RoleData> Roles; // only access id
std::vector<EmojiData> Emojis; // only access id
std::vector<std::string> Features;
int MFALevel;
Snowflake ApplicationID; // null
std::optional<int> VerificationLevel;
std::optional<int> DefaultMessageNotifications;
std::optional<int> ExplicitContentFilter;
std::optional<std::vector<RoleData>> Roles; // only access id
std::optional<std::vector<EmojiData>> Emojis; // only access id
std::optional<std::vector<std::string>> Features;
std::optional<int> MFALevel;
std::optional<Snowflake> ApplicationID; // null
std::optional<bool> IsWidgetEnabled;
std::optional<Snowflake> WidgetChannelID; // null
Snowflake SystemChannelID; // null
int SystemChannelFlags;
Snowflake RulesChannelID; // null
std::optional<std::string> JoinedAt; // *
std::optional<bool> IsLarge; // *
std::optional<bool> IsUnavailable; // *
std::optional<int> MemberCount; // *
std::optional<Snowflake> SystemChannelID; // null
std::optional<int> SystemChannelFlags;
std::optional<Snowflake> RulesChannelID; // null
std::optional<std::string> JoinedAt; // *
std::optional<bool> IsLarge; // *
std::optional<bool> IsUnavailable; // *
std::optional<int> MemberCount; // *
// std::vector<VoiceStateData> VoiceStates; // opt*
// std::vector<MemberData> Members; // opt* - incomplete anyways
std::optional<std::vector<ChannelData>> Channels; // *
// std::vector<PresenceUpdateData> Presences; // opt*
std::optional<int> MaxPresences; // null
std::optional<int> MaxMembers;
std::string VanityURL; // null
std::string Description; // null
std::string BannerHash; // null
int PremiumTier;
std::optional<std::string> VanityURL; // null
std::optional<std::string> Description; // null
std::optional<std::string> BannerHash; // null
std::optional<int> PremiumTier;
std::optional<int> PremiumSubscriptionCount;
std::string PreferredLocale;
Snowflake PublicUpdatesChannelID; // null
std::optional<std::string> PreferredLocale;
std::optional<Snowflake> PublicUpdatesChannelID; // null
std::optional<int> MaxVideoChannelUsers;
std::optional<int> ApproximateMemberCount;
std::optional<int> ApproximatePresenceCount;

View File

@@ -1,14 +1,17 @@
#include "invite.hpp"
void from_json(const nlohmann::json &j, Invite &m) {
void from_json(const nlohmann::json &j, InviteData &m) {
JS_D("code", m.Code);
JS_O("guild", m.Guild);
JS_O("channel", m.Channel);
JS_O("inviter", m.Inviter);
JS_O("approximate_member_count", m.Members);
if (j.contains("guild")) {
auto x = j.at("guild");
x.at("id").get_to(m.Guild.ID);
x.at("name").get_to(m.Guild.Name);
}
JS_O("target_user", m.TargetUser);
JS_O("target_user_type", m.TargetUserType);
JS_O("approximate_presence_count", m.PresenceCount);
JS_O("approximate_member_count", m.MemberCount);
JS_O("uses", m.Uses);
JS_O("max_uses", m.MaxUses);
JS_O("max_age", m.MaxAge);
JS_O("temporary", m.IsTemporary);
JS_O("created_at", m.CreatedAt);
}

View File

@@ -3,13 +3,25 @@
#include "guild.hpp"
#include <string>
class Invite {
public:
std::string Code; //
GuildData Guild; // opt
ChannelData Channel; // opt
UserData Inviter; // opt
int Members = -1; // opt
friend void from_json(const nlohmann::json &j, Invite &m);
enum class ETargetUserType {
STREAM = 1
};
class InviteData {
public:
std::string Code;
std::optional<GuildData> Guild;
std::optional<ChannelData> Channel;
std::optional<UserData> Inviter;
std::optional<UserData> TargetUser;
std::optional<ETargetUserType> TargetUserType;
std::optional<int> PresenceCount;
std::optional<int> MemberCount;
std::optional<int> Uses;
std::optional<int> MaxUses;
std::optional<int> MaxAge;
std::optional<bool> IsTemporary;
std::optional<std::string> CreatedAt;
friend void from_json(const nlohmann::json &j, InviteData &m);
};

View File

@@ -262,3 +262,23 @@ void from_json(const nlohmann::json &j, GuildBanAddObject &m) {
JS_D("guild_id", m.GuildID);
JS_D("user", m.User);
}
void from_json(const nlohmann::json &j, InviteCreateObject &m) {
JS_D("channel_id", m.ChannelID);
JS_D("code", m.Code);
JS_D("created_at", m.CreatedAt);
JS_O("guild_id", m.GuildID);
JS_O("inviter", m.Inviter);
JS_D("max_age", m.MaxAge);
JS_D("max_uses", m.MaxUses);
JS_O("target_user", m.TargetUser);
JS_O("target_user_type", m.TargetUserType);
JS_D("temporary", m.IsTemporary);
JS_D("uses", m.Uses);
}
void from_json(const nlohmann::json &j, InviteDeleteObject &m) {
JS_D("channel_id", m.ChannelID);
JS_O("guild_id", m.GuildID);
JS_D("code", m.Code);
}

View File

@@ -57,6 +57,8 @@ enum class GatewayEvent : int {
TYPING_START,
GUILD_BAN_REMOVE,
GUILD_BAN_ADD,
INVITE_CREATE,
INVITE_DELETE,
};
struct GatewayMessage {
@@ -370,3 +372,27 @@ struct GuildBanAddObject {
friend void from_json(const nlohmann::json &j, GuildBanAddObject &m);
};
struct InviteCreateObject {
Snowflake ChannelID;
std::string Code;
std::string CreatedAt;
std::optional<Snowflake> GuildID;
std::optional<UserData> Inviter;
int MaxAge;
int MaxUses;
UserData TargetUser;
std::optional<ETargetUserType> TargetUserType;
bool IsTemporary;
int Uses;
friend void from_json(const nlohmann::json &j, InviteCreateObject &m);
};
struct InviteDeleteObject {
Snowflake ChannelID;
std::optional<Snowflake> GuildID;
std::string Code;
friend void from_json(const nlohmann::json &j, InviteDeleteObject &m);
};

View File

@@ -148,12 +148,23 @@ void Store::SetGuild(Snowflake id, const GuildData &guild) {
Bind(m_set_guild_stmt, 11, guild.VerificationLevel);
Bind(m_set_guild_stmt, 12, guild.DefaultMessageNotifications);
std::vector<Snowflake> snowflakes;
for (const auto &x : guild.Roles) snowflakes.push_back(x.ID);
Bind(m_set_guild_stmt, 13, nlohmann::json(snowflakes).dump());
if (guild.Roles.has_value()) {
for (const auto &x : *guild.Roles) snowflakes.push_back(x.ID);
Bind(m_set_guild_stmt, 13, nlohmann::json(snowflakes).dump());
} else {
Bind(m_set_guild_stmt, 13, "[]"s);
}
snowflakes.clear();
for (const auto &x : guild.Emojis) snowflakes.push_back(x.ID);
Bind(m_set_guild_stmt, 14, nlohmann::json(snowflakes).dump());
Bind(m_set_guild_stmt, 15, nlohmann::json(guild.Features).dump());
if (guild.Emojis.has_value()) {
for (const auto &x : *guild.Emojis) snowflakes.push_back(x.ID);
Bind(m_set_guild_stmt, 14, nlohmann::json(snowflakes).dump());
} else {
Bind(m_set_guild_stmt, 14, "[]"s);
}
if (guild.Features.has_value())
Bind(m_set_guild_stmt, 15, nlohmann::json(*guild.Features).dump());
else
Bind(m_set_guild_stmt, 15, "[]"s);
Bind(m_set_guild_stmt, 16, guild.MFALevel);
Bind(m_set_guild_stmt, 17, guild.ApplicationID);
Bind(m_set_guild_stmt, 18, guild.IsWidgetEnabled);
@@ -430,11 +441,13 @@ std::optional<GuildData> Store::GetGuild(Snowflake id) const {
Get(m_get_guild_stmt, 11, ret.DefaultMessageNotifications);
std::string tmp;
Get(m_get_guild_stmt, 12, tmp);
ret.Roles.emplace();
for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
ret.Roles.emplace_back().ID = id;
ret.Roles->emplace_back().ID = id;
Get(m_get_guild_stmt, 13, tmp);
ret.Emojis.emplace();
for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
ret.Emojis.emplace_back().ID = id;
ret.Emojis->emplace_back().ID = id;
Get(m_get_guild_stmt, 14, tmp);
ret.Features = nlohmann::json::parse(tmp).get<std::vector<std::string>>();
Get(m_get_guild_stmt, 15, ret.MFALevel);