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

@@ -178,15 +178,17 @@ void Completer::CompleteEmojis(const Glib::ustring &term) {
};
int i = 0;
for (const auto tmp : guild->Emojis) {
const auto emoji = discord.GetEmoji(tmp.ID);
if (!emoji.has_value()) continue;
if (emoji->IsAnimated.has_value() && *emoji->IsAnimated) continue;
if (term.size() > 0)
if (!StringContainsCaseless(emoji->Name, term)) continue;
if (i++ > MaxCompleterEntries) break;
if (guild->Emojis.has_value()) {
for (const auto tmp : *guild->Emojis) {
const auto emoji = discord.GetEmoji(tmp.ID);
if (!emoji.has_value()) continue;
if (emoji->IsAnimated.has_value() && *emoji->IsAnimated) continue;
if (term.size() > 0)
if (!StringContainsCaseless(emoji->Name, term)) continue;
if (i++ > MaxCompleterEntries) break;
const auto entry = make_entry(emoji->Name, "<:" + emoji->Name + ":" + std::to_string(emoji->ID) + ">", emoji->GetURL());
const auto entry = make_entry(emoji->Name, "<:" + emoji->Name + ":" + std::to_string(emoji->ID) + ">", emoji->GetURL());
}
}
// if <15 guild emojis match then load up stock

View File

@@ -56,25 +56,21 @@ void JoinGuildDialog::on_entry_changed() {
}
void JoinGuildDialog::CheckCode() {
// clang-format off
Abaddon::Get().GetDiscordClient().FetchInviteData(
m_code,
[this](Invite invite) {
m_ok.set_sensitive(true);
if (invite.Members != -1)
m_info.set_text(invite.Guild.Name + " (" + std::to_string(invite.Members) + " members)");
auto cb = [this](const std::optional<InviteData> &invite) {
if (invite.has_value()) {
m_ok.set_sensitive(true);
if (invite->Guild.has_value()) {
if (invite->MemberCount.has_value())
m_info.set_text(invite->Guild->Name + " (" + std::to_string(*invite->MemberCount) + " members)");
else
m_info.set_text(invite.Guild.Name);
},
[this](bool not_found) {
m_info.set_text(invite->Guild->Name);
}
} else {
m_ok.set_sensitive(false);
if (not_found)
m_info.set_text("Invalid invite");
else
m_info.set_text("HTTP error (try again)");
m_info.set_text("Invalid invite");
}
);
// clang-format on
};
Abaddon::Get().GetDiscordClient().FetchInvite(m_code, sigc::track_obj(cb, *this));
}
bool JoinGuildDialog::IsCode(std::string str) {

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);

View File

@@ -64,6 +64,41 @@ std::string HumanReadableBytes(uint64_t bytes) {
return std::to_string(bytes) + x[order];
}
int GetTimezoneOffset() {
std::time_t secs;
std::time(&secs);
std::tm *tptr = std::localtime(&secs);
std::time_t local_secs = std::mktime(tptr);
tptr = std::gmtime(&secs);
std::time_t gmt_secs = std::mktime(tptr);
return local_secs - gmt_secs;
}
std::string FormatISO8601(const std::string &in, int extra_offset, const std::string &fmt) {
int yr, mon, day, hr, min, sec, tzhr, tzmin;
float milli;
std::sscanf(in.c_str(), "%d-%d-%dT%d:%d:%d%f+%d:%d",
&yr, &mon, &day, &hr, &min, &sec, &milli, &tzhr, &tzmin);
std::tm tm;
tm.tm_year = yr - 1900;
tm.tm_mon = mon - 1;
tm.tm_mday = day;
tm.tm_hour = hr;
tm.tm_min = min;
tm.tm_sec = sec;
tm.tm_wday = 0;
tm.tm_yday = 0;
tm.tm_isdst = -1;
int offset = GetTimezoneOffset();
tm.tm_sec += offset + extra_offset;
mktime(&tm);
std::stringstream ss;
const static std::locale locale("");
ss.imbue(locale);
ss << std::put_time(&tm, fmt.c_str());
return ss.str();
}
void ScrollListBoxToSelected(Gtk::ListBox &list) {
auto cb = [&list]() -> bool {
const auto selected = list.get_selected_row();

View File

@@ -45,6 +45,7 @@ std::string GetExtension(std::string url);
bool IsURLViewableImage(const std::string &url);
std::vector<uint8_t> ReadWholeFile(std::string path);
std::string HumanReadableBytes(uint64_t bytes);
std::string FormatISO8601(const std::string &in, int extra_offset = 0, const std::string &fmt = "%x %X");
template<typename T>
struct Bitwise {

View File

@@ -0,0 +1,127 @@
#include "invitespane.hpp"
#include "../../abaddon.hpp"
GuildSettingsInvitesPane::GuildSettingsInvitesPane(Snowflake id)
: GuildID(id)
, m_model(Gtk::ListStore::create(m_columns))
, m_menu_delete("Delete") {
set_name("guild-invites-pane");
set_hexpand(true);
set_vexpand(true);
m_view.signal_button_press_event().connect(sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnTreeButtonPress), false);
m_menu_delete.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnMenuDelete));
m_menu.append(m_menu_delete);
m_menu.show_all();
auto &discord = Abaddon::Get().GetDiscordClient();
const auto self_id = discord.GetUserData().ID;
if (discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_GUILD))
discord.FetchGuildInvites(GuildID, sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnInvitesFetch));
discord.signal_invite_create().connect(sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnInviteCreate));
discord.signal_invite_delete().connect(sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnInviteDelete));
m_view.show();
add(m_view);
m_view.set_model(m_model);
m_view.append_column("Code", m_columns.m_col_code);
m_view.append_column("Expires", m_columns.m_col_expires);
m_view.append_column("Created by", m_columns.m_col_inviter);
m_view.append_column("Uses", m_columns.m_col_uses);
m_view.append_column("Max uses", m_columns.m_col_max_uses);
m_view.append_column("Grants temporary membership", m_columns.m_col_temporary);
for (const auto column : m_view.get_columns())
column->set_resizable(true);
}
void GuildSettingsInvitesPane::AppendInvite(const InviteData &invite) {
auto &discord = Abaddon::Get().GetDiscordClient();
auto &row = *m_model->append();
row[m_columns.m_col_code] = invite.Code;
if (invite.Inviter.has_value())
row[m_columns.m_col_inviter] = invite.Inviter->Username + "#" + invite.Inviter->Discriminator;
if (invite.MaxAge.has_value())
if (*invite.MaxAge == 0)
row[m_columns.m_col_expires] = "Never";
else
row[m_columns.m_col_expires] = FormatISO8601(*invite.CreatedAt, *invite.MaxAge);
row[m_columns.m_col_uses] = *invite.Uses;
if (*invite.MaxUses == 0)
row[m_columns.m_col_max_uses] = "Unlimited";
else
row[m_columns.m_col_max_uses] = std::to_string(*invite.MaxUses);
row[m_columns.m_col_temporary] = *invite.IsTemporary ? "Yes" : "No";
}
void GuildSettingsInvitesPane::OnInviteFetch(const std::optional<InviteData> &invite) {
if (!invite.has_value()) return;
AppendInvite(*invite);
}
void GuildSettingsInvitesPane::OnInvitesFetch(const std::vector<InviteData> &invites) {
auto &discord = Abaddon::Get().GetDiscordClient();
for (const auto &invite : invites)
AppendInvite(invite);
}
void GuildSettingsInvitesPane::OnInviteCreate(const InviteData &invite) {
if (invite.Guild->ID == GuildID)
OnInviteFetch(std::make_optional(invite));
}
void GuildSettingsInvitesPane::OnInviteDelete(const InviteDeleteObject &data) {
if (*data.GuildID == GuildID)
for (auto &row : m_model->children())
if (row[m_columns.m_col_code] == data.Code)
m_model->erase(row);
}
void GuildSettingsInvitesPane::OnMenuDelete() {
auto selected_row = *m_view.get_selection()->get_selected();
if (selected_row) {
auto code = static_cast<Glib::ustring>(selected_row[m_columns.m_col_code]);
auto cb = [this](const bool success) {
if (!success) {
Gtk::MessageDialog dlg("Failed to delete invite", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
dlg.run();
}
};
Abaddon::Get().GetDiscordClient().DeleteInvite(code, sigc::track_obj(cb, *this));
}
}
bool GuildSettingsInvitesPane::OnTreeButtonPress(GdkEventButton *event) {
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) {
auto &discord = Abaddon::Get().GetDiscordClient();
const auto self_id = discord.GetUserData().ID;
const auto can_manage = discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_GUILD);
m_menu_delete.set_sensitive(can_manage);
auto selection = m_view.get_selection();
Gtk::TreeModel::Path path;
if (m_view.get_path_at_pos(event->x, event->y, path)) {
m_view.get_selection()->select(path);
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
}
return true;
}
return false;
}
GuildSettingsInvitesPane::ModelColumns::ModelColumns() {
add(m_col_code);
add(m_col_expires);
add(m_col_inviter);
add(m_col_temporary);
add(m_col_uses);
add(m_col_max_uses);
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <gtkmm.h>
#include "../../discord/objects.hpp"
class GuildSettingsInvitesPane : public Gtk::ScrolledWindow {
public:
GuildSettingsInvitesPane(Snowflake id);
private:
void AppendInvite(const InviteData &invite);
void OnInviteFetch(const std::optional<InviteData> &invite);
void OnInvitesFetch(const std::vector<InviteData> &invites);
void OnInviteCreate(const InviteData &invite);
void OnInviteDelete(const InviteDeleteObject &data);
void OnMenuDelete();
bool OnTreeButtonPress(GdkEventButton *event);
Gtk::TreeView m_view;
Snowflake GuildID;
class ModelColumns : public Gtk::TreeModel::ColumnRecord {
public:
ModelColumns();
Gtk::TreeModelColumn<Glib::ustring> m_col_code;
Gtk::TreeModelColumn<Glib::ustring> m_col_expires;
Gtk::TreeModelColumn<Glib::ustring> m_col_inviter;
Gtk::TreeModelColumn<Glib::ustring> m_col_temporary;
Gtk::TreeModelColumn<int> m_col_uses;
Gtk::TreeModelColumn<Glib::ustring> m_col_max_uses;
};
ModelColumns m_columns;
Glib::RefPtr<Gtk::ListStore> m_model;
Gtk::Menu m_menu;
Gtk::MenuItem m_menu_delete;
};

View File

@@ -5,7 +5,8 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
: m_main(Gtk::ORIENTATION_VERTICAL)
, GuildID(id)
, m_pane_info(id)
, m_pane_bans(id) {
, m_pane_bans(id)
, m_pane_invites(id) {
auto &discord = Abaddon::Get().GetDiscordClient();
const auto guild = *discord.GetGuild(id);
@@ -36,7 +37,10 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
m_pane_info.show();
m_pane_bans.show();
m_pane_invites.show();
m_stack.set_transition_duration(100);
m_stack.set_transition_type(Gtk::STACK_TRANSITION_TYPE_CROSSFADE);
m_stack.set_margin_top(10);
m_stack.set_margin_bottom(10);
m_stack.set_margin_left(10);
@@ -44,6 +48,7 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
m_stack.add(m_pane_info, "info", "Info");
m_stack.add(m_pane_bans, "bans", "Bans");
m_stack.add(m_pane_invites, "invites", "Invites");
m_stack.show();
m_main.add(m_switcher);

View File

@@ -3,6 +3,7 @@
#include "../discord/snowflake.hpp"
#include "guildsettings/infopane.hpp"
#include "guildsettings/banspane.hpp"
#include "guildsettings/invitespane.hpp"
class GuildSettingsWindow : public Gtk::Window {
public:
@@ -15,6 +16,7 @@ private:
GuildSettingsInfoPane m_pane_info;
GuildSettingsBansPane m_pane_bans;
GuildSettingsInvitesPane m_pane_invites;
Snowflake GuildID;
};