summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--css/main.css20
-rw-r--r--discord/ban.cpp6
-rw-r--r--discord/ban.hpp10
-rw-r--r--discord/discord.cpp82
-rw-r--r--discord/discord.hpp20
-rw-r--r--discord/objects.cpp10
-rw-r--r--discord/objects.hpp17
-rw-r--r--discord/store.cpp115
-rw-r--r--discord/store.hpp8
-rw-r--r--windows/guildsettings/banspane.cpp136
-rw-r--r--windows/guildsettings/banspane.hpp38
-rw-r--r--windows/guildsettings/infopane.cpp4
-rw-r--r--windows/guildsettingswindow.cpp10
-rw-r--r--windows/guildsettingswindow.hpp2
14 files changed, 469 insertions, 9 deletions
diff --git a/css/main.css b/css/main.css
index b1f8705..409488c 100644
--- a/css/main.css
+++ b/css/main.css
@@ -175,13 +175,31 @@
.app-window button {
background: @secondary_color;
color: @text_color;
- border: 1px solid #1c2e40;
+ text-shadow: none;
+ box-shadow: none;
+}
+
+.app-window button:checked {
+ border-top: 0px;
+ border-left: 0px;
+ border-right: 0px;
+ border-bottom: 3px solid #39a2ed;
+ color: #ffffff;
+}
+
+.app-window button:not(:checked) {
+ border: 3px #0000ff;
}
.app-window.background {
background: @background_color;
}
+.app-window treeview {
+ color: @text_color;
+ background: @secondary_color;
+}
+
.app-window listbox {
background: @background_color;
}
diff --git a/discord/ban.cpp b/discord/ban.cpp
new file mode 100644
index 0000000..a354c15
--- /dev/null
+++ b/discord/ban.cpp
@@ -0,0 +1,6 @@
+#include "ban.hpp"
+
+void from_json(const nlohmann::json &j, BanData &m) {
+ JS_N("reason", m.Reason);
+ JS_D("user", m.User);
+}
diff --git a/discord/ban.hpp b/discord/ban.hpp
new file mode 100644
index 0000000..d417ce3
--- /dev/null
+++ b/discord/ban.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include <string>
+#include "user.hpp"
+
+struct BanData {
+ std::string Reason; // null
+ UserData User; // access id
+
+ friend void from_json(const nlohmann::json &j, BanData &m);
+};
diff --git a/discord/discord.cpp b/discord/discord.cpp
index 73c7614..d37e21d 100644
--- a/discord/discord.cpp
+++ b/discord/discord.cpp
@@ -179,6 +179,10 @@ std::optional<GuildMember> DiscordClient::GetMember(Snowflake user_id, Snowflake
return m_store.GetGuildMember(guild_id, user_id);
}
+std::optional<BanData> DiscordClient::GetBan(Snowflake guild_id, Snowflake user_id) const {
+ return m_store.GetBan(guild_id, user_id);
+}
+
std::optional<PermissionOverwrite> DiscordClient::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const {
return m_store.GetPermissionOverwrite(channel_id, id);
}
@@ -467,6 +471,49 @@ void DiscordClient::SetGuildIcon(Snowflake id, const std::string &data, sigc::sl
});
}
+void DiscordClient::UnbanUser(Snowflake guild_id, Snowflake user_id) {
+ UnbanUser(guild_id, user_id, [](const auto) {});
+}
+
+void DiscordClient::UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(bool success)> callback) {
+ sigc::signal<void, bool> signal;
+ signal.connect(callback);
+ m_http.MakeDELETE("/guilds/" + std::to_string(guild_id) + "/bans/" + std::to_string(user_id), [this, callback](const cpr::Response &response) {
+ callback(response.status_code == 204);
+ });
+}
+
+std::vector<BanData> DiscordClient::GetBansInGuild(Snowflake guild_id) {
+ return m_store.GetBans(guild_id);
+}
+
+void DiscordClient::FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::slot<void(BanData)> callback) {
+ 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;
+ 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);
+ callback(ban);
+ });
+}
+
+void DiscordClient::FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vector<BanData>)> callback) {
+ 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) {
+ auto bans = nlohmann::json::parse(response.text).get<std::vector<BanData>>();
+ m_store.BeginTransaction();
+ for (const auto &ban : bans) {
+ m_store.SetBan(guild_id, ban.User.ID, ban);
+ m_store.SetUser(ban.User.ID, ban.User);
+ }
+ m_store.EndTransaction();
+ callback(bans);
+ });
+}
+
void DiscordClient::UpdateToken(std::string token) {
if (!IsStarted()) {
m_token = token;
@@ -623,6 +670,12 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
case GatewayEvent::TYPING_START: {
HandleGatewayTypingStart(m);
} break;
+ case GatewayEvent::GUILD_BAN_REMOVE: {
+ HandleGatewayGuildBanRemove(m);
+ } break;
+ case GatewayEvent::GUILD_BAN_ADD: {
+ HandleGatewayGuildBanAdd(m);
+ } break;
}
} break;
default:
@@ -724,7 +777,7 @@ void DiscordClient::HandleGatewayMessageDeleteBulk(const GatewayMessage &msg) {
for (const auto &id : data.IDs) {
auto cur = m_store.GetMessage(id);
if (!cur.has_value())
- return;
+ continue;
cur->SetDeleted();
m_store.SetMessage(id, *cur);
@@ -930,6 +983,23 @@ void DiscordClient::HandleGatewayTypingStart(const GatewayMessage &msg) {
m_signal_typing_start.emit(data.UserID, data.ChannelID);
}
+void DiscordClient::HandleGatewayGuildBanRemove(const GatewayMessage &msg) {
+ GuildBanRemoveObject data = msg.Data;
+ m_store.SetUser(data.User.ID, data.User);
+ m_store.ClearBan(data.GuildID, data.User.ID);
+ m_signal_guild_ban_remove.emit(data.GuildID, data.User.ID);
+}
+
+void DiscordClient::HandleGatewayGuildBanAdd(const GatewayMessage &msg) {
+ GuildBanAddObject data = msg.Data;
+ BanData ban;
+ ban.Reason = "";
+ ban.User = data.User;
+ m_store.SetUser(data.User.ID, data.User);
+ m_store.SetBan(data.GuildID, data.User.ID, ban);
+ m_signal_guild_ban_add.emit(data.GuildID, data.User.ID);
+}
+
void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) {
m_signal_disconnected.emit(true);
inflateEnd(&m_zstream);
@@ -1151,6 +1221,8 @@ void DiscordClient::LoadEventMap() {
m_event_map["CHANNEL_RECIPIENT_ADD"] = GatewayEvent::CHANNEL_RECIPIENT_ADD;
m_event_map["CHANNEL_RECIPIENT_REMOVE"] = GatewayEvent::CHANNEL_RECIPIENT_REMOVE;
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;
}
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
@@ -1232,3 +1304,11 @@ DiscordClient::type_signal_typing_start DiscordClient::signal_typing_start() {
DiscordClient::type_signal_guild_member_update DiscordClient::signal_guild_member_update() {
return m_signal_guild_member_update;
}
+
+DiscordClient::type_signal_guild_ban_remove DiscordClient::signal_guild_ban_remove() {
+ return m_signal_guild_ban_remove;
+}
+
+DiscordClient::type_signal_guild_ban_add DiscordClient::signal_guild_ban_add() {
+ return m_signal_guild_ban_add;
+}
diff --git a/discord/discord.hpp b/discord/discord.hpp
index e1cfcde..27df1fa 100644
--- a/discord/discord.hpp
+++ b/discord/discord.hpp
@@ -86,6 +86,7 @@ public:
std::optional<RoleData> GetRole(Snowflake id) const;
std::optional<GuildData> GetGuild(Snowflake id) const;
std::optional<GuildMember> GetMember(Snowflake user_id, Snowflake guild_id) const;
+ std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const;
Snowflake GetMemberHighestRole(Snowflake guild_id, Snowflake user_id) const;
std::unordered_set<Snowflake> GetUsersInGuild(Snowflake id) const;
@@ -114,6 +115,13 @@ public:
void SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
void SetGuildIcon(Snowflake id, const std::string &data);
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);
+
+ // 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 UpdateToken(std::string token);
void SetUserAgent(std::string agent);
@@ -151,6 +159,8 @@ private:
void HandleGatewayChannelRecipientAdd(const GatewayMessage &msg);
void HandleGatewayChannelRecipientRemove(const GatewayMessage &msg);
void HandleGatewayTypingStart(const GatewayMessage &msg);
+ void HandleGatewayGuildBanRemove(const GatewayMessage &msg);
+ void HandleGatewayGuildBanAdd(const GatewayMessage &msg);
void HandleGatewayReconnect(const GatewayMessage &msg);
void HeartbeatThread();
void SendIdentify();
@@ -217,9 +227,11 @@ public:
typedef sigc::signal<void, Snowflake> type_signal_role_delete;
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_reaction_add;
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_reaction_remove;
- typedef sigc::signal<void, Snowflake, Snowflake> type_signal_typing_start; // user id, channel id
+ typedef sigc::signal<void, Snowflake, Snowflake> type_signal_typing_start; // user id, channel id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_member_update; // guild id, user id
- typedef sigc::signal<void, bool> type_signal_disconnected; // bool true if reconnecting
+ 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> type_signal_connected;
type_signal_gateway_ready signal_gateway_ready();
@@ -240,6 +252,8 @@ public:
type_signal_reaction_remove signal_reaction_remove();
type_signal_typing_start signal_typing_start();
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_disconnected signal_disconnected();
type_signal_connected signal_connected();
@@ -262,6 +276,8 @@ protected:
type_signal_reaction_remove m_signal_reaction_remove;
type_signal_typing_start m_signal_typing_start;
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_disconnected m_signal_disconnected;
type_signal_connected m_signal_connected;
};
diff --git a/discord/objects.cpp b/discord/objects.cpp
index 07e616b..70fbd57 100644
--- a/discord/objects.cpp
+++ b/discord/objects.cpp
@@ -252,3 +252,13 @@ void to_json(nlohmann::json &j, const ModifyGuildObject &m) {
JS_IF("name", m.Name);
JS_IF("icon", m.IconData);
}
+
+void from_json(const nlohmann::json &j, GuildBanRemoveObject &m) {
+ JS_D("guild_id", m.GuildID);
+ JS_D("user", m.User);
+}
+
+void from_json(const nlohmann::json &j, GuildBanAddObject &m) {
+ JS_D("guild_id", m.GuildID);
+ JS_D("user", m.User);
+}
diff --git a/discord/objects.hpp b/discord/objects.hpp
index 89770a5..5aa92ec 100644
--- a/discord/objects.hpp
+++ b/discord/objects.hpp
@@ -16,6 +16,7 @@
#include "emoji.hpp"
#include "activity.hpp"
#include "sticker.hpp"
+#include "ban.hpp"
// most stuff below should just be objects that get processed and thrown away immediately
@@ -54,6 +55,8 @@ enum class GatewayEvent : int {
CHANNEL_RECIPIENT_ADD,
CHANNEL_RECIPIENT_REMOVE,
TYPING_START,
+ GUILD_BAN_REMOVE,
+ GUILD_BAN_ADD,
};
struct GatewayMessage {
@@ -353,3 +356,17 @@ struct ModifyGuildObject {
friend void to_json(nlohmann::json &j, const ModifyGuildObject &m);
};
+
+struct GuildBanRemoveObject {
+ Snowflake GuildID;
+ UserData User;
+
+ friend void from_json(const nlohmann::json &j, GuildBanRemoveObject &m);
+};
+
+struct GuildBanAddObject {
+ Snowflake GuildID;
+ UserData User;
+
+ friend void from_json(const nlohmann::json &j, GuildBanAddObject &m);
+};
diff --git a/discord/store.cpp b/discord/store.cpp
index 9b4ea50..2957f47 100644
--- a/discord/store.cpp
+++ b/discord/store.cpp
@@ -51,6 +51,15 @@ bool Store::IsValid() const {
return m_db_err == SQLITE_OK;
}
+void Store::SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban) {
+ Bind(m_set_ban_stmt, 1, guild_id);
+ Bind(m_set_ban_stmt, 2, user_id);
+ Bind(m_set_ban_stmt, 3, ban.Reason);
+
+ if (!RunInsert(m_set_ban_stmt))
+ fprintf(stderr, "ban insert failed: %s\n", sqlite3_errstr(m_db_err));
+}
+
void Store::SetChannel(Snowflake id, const ChannelData &chan) {
Bind(m_set_chan_stmt, 1, id);
Bind(m_set_chan_stmt, 2, static_cast<int>(chan.Type));
@@ -273,6 +282,41 @@ void Store::SetUser(Snowflake id, const UserData &user) {
}
}
+std::optional<BanData> Store::GetBan(Snowflake guild_id, Snowflake user_id) const {
+ Bind(m_get_ban_stmt, 1, guild_id);
+ Bind(m_get_ban_stmt, 2, user_id);
+ if (!FetchOne(m_get_ban_stmt)) {
+ if (m_db_err != SQLITE_DONE)
+ fprintf(stderr, "error while fetching ban: %s\n", sqlite3_errstr(m_db_err));
+ Reset(m_get_ban_stmt);
+ return std::nullopt;
+ }
+
+ BanData ret;
+ ret.User.ID = user_id;
+ Get(m_get_ban_stmt, 2, ret.Reason);
+
+ Reset(m_get_ban_stmt);
+ return ret;
+}
+
+std::vector<BanData> Store::GetBans(Snowflake guild_id) const {
+ Bind(m_get_bans_stmt, 1, guild_id);
+
+ std::vector<BanData> ret;
+ while (FetchOne(m_get_bans_stmt)) {
+ auto &ban = ret.emplace_back();
+ Get(m_get_bans_stmt, 1, ban.User.ID);
+ Get(m_get_bans_stmt, 2, ban.Reason);
+ }
+
+ Reset(m_get_bans_stmt);
+
+ if (m_db_err != SQLITE_DONE)
+ fprintf(stderr, "error while fetching bans: %s\n", sqlite3_errstr(m_db_err));
+ return ret;
+}
+
std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
Bind(m_get_chan_stmt, 1, id);
if (!FetchOne(m_get_chan_stmt)) {
@@ -595,6 +639,16 @@ void Store::ClearChannel(Snowflake id) {
m_channels.erase(id);
}
+void Store::ClearBan(Snowflake guild_id, Snowflake user_id) {
+ Bind(m_clear_ban_stmt, 1, guild_id);
+ Bind(m_clear_ban_stmt, 2, user_id);
+
+ if ((m_db_err = sqlite3_step(m_clear_ban_stmt)) != SQLITE_DONE)
+ printf("clearing ban failed: %s\n", sqlite3_errstr(m_db_err));
+
+ Reset(m_clear_ban_stmt);
+}
+
const std::unordered_set<Snowflake> &Store::GetChannels() const {
return m_channels;
}
@@ -779,6 +833,15 @@ bool Store::CreateTables() {
)
)";
+ constexpr 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)
+ )
+ )";
+
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: %s\n", sqlite3_errstr(m_db_err));
@@ -827,6 +890,12 @@ bool Store::CreateTables() {
return false;
}
+ m_db_err = sqlite3_exec(m_db, create_bans, nullptr, nullptr, nullptr);
+ if (m_db_err != SQLITE_OK) {
+ fprintf(stderr, "failed to create bans table: %s\n", sqlite3_errstr(m_db_err));
+ return false;
+ }
+
return true;
}
@@ -911,6 +980,24 @@ bool Store::CreateStatements() {
SELECT * FROM channels WHERE id = ?
)";
+ constexpr const char *set_ban = R"(
+ REPLACE INTO bans VALUES (
+ ?, ?, ?
+ )
+ )";
+
+ constexpr const char *get_ban = R"(
+ SELECT * FROM bans WHERE guild_id = ? AND user_id = ?
+ )";
+
+ constexpr const char *clear_ban = R"(
+ DELETE FROM bans WHERE guild_id = ? AND user_id = ?
+ )";
+
+ constexpr const char *get_bans = R"(
+ SELECT * FROM bans WHERE guild_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: %s\n", sqlite3_errstr(m_db_err));
@@ -1007,6 +1094,30 @@ bool Store::CreateStatements() {
return false;
}
+ m_db_err = sqlite3_prepare_v2(m_db, set_ban, -1, &m_set_ban_stmt, nullptr);
+ if (m_db_err != SQLITE_OK) {
+ fprintf(stderr, "failed to prepare set ban statement: %s\n", sqlite3_errstr(m_db_err));
+ return false;
+ }
+
+ m_db_err = sqlite3_prepare_v2(m_db, get_ban, -1, &m_get_ban_stmt, nullptr);
+ if (m_db_err != SQLITE_OK) {
+ fprintf(stderr, "failed to prepare get ban statement: %s\n", sqlite3_errstr(m_db_err));
+ return false;
+ }
+
+ m_db_err = sqlite3_prepare_v2(m_db, clear_ban, -1, &m_clear_ban_stmt, nullptr);
+ if (m_db_err != SQLITE_OK) {
+ fprintf(stderr, "failed to prepare clear ban statement: %s\n", sqlite3_errstr(m_db_err));
+ return false;
+ }
+
+ m_db_err = sqlite3_prepare_v2(m_db, get_bans, -1, &m_get_bans_stmt, nullptr);
+ if (m_db_err != SQLITE_OK) {
+ fprintf(stderr, "failed to prepare get bans statement: %s\n", sqlite3_errstr(m_db_err));
+ return false;
+ }
+
return true;
}
@@ -1027,6 +1138,10 @@ void Store::Cleanup() {
sqlite3_finalize(m_get_guild_stmt);
sqlite3_finalize(m_set_chan_stmt);
sqlite3_finalize(m_get_chan_stmt);
+ sqlite3_finalize(m_set_ban_stmt);
+ sqlite3_finalize(m_get_ban_stmt);
+ sqlite3_finalize(m_clear_ban_stmt);
+ sqlite3_finalize(m_get_bans_stmt);
}
void Store::Bind(sqlite3_stmt *stmt, int index, int num) const {
diff --git a/discord/store.hpp b/discord/store.hpp
index c25cb7c..7559f23 100644
--- a/discord/store.hpp
+++ b/discord/store.hpp
@@ -26,6 +26,7 @@ public:
void SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data);
void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm);
void SetEmoji(Snowflake id, const EmojiData &emoji);
+ void SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban);
// slap const on everything even tho its not *really* const
@@ -37,9 +38,12 @@ public:
std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const;
std::optional<RoleData> GetRole(Snowflake id) const;
std::optional<UserData> GetUser(Snowflake id) const;
+ std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
+ std::vector<BanData> GetBans(Snowflake guild_id) const;
void ClearGuild(Snowflake id);
void ClearChannel(Snowflake id);
+ void ClearBan(Snowflake guild_id, Snowflake user_id);
using users_type = std::unordered_map<Snowflake, UserData>;
using channels_type = std::unordered_map<Snowflake, ChannelData>;
@@ -104,6 +108,10 @@ private:
mutable sqlite3_stmt *m_get_guild_stmt;
mutable sqlite3_stmt *m_set_chan_stmt;
mutable sqlite3_stmt *m_get_chan_stmt;
+ mutable sqlite3_stmt *m_set_ban_stmt;
+ mutable sqlite3_stmt *m_get_ban_stmt;
+ mutable sqlite3_stmt *m_clear_ban_stmt;
+ mutable sqlite3_stmt *m_get_bans_stmt;
};
template<typename T>
diff --git a/windows/guildsettings/banspane.cpp b/windows/guildsettings/banspane.cpp
new file mode 100644
index 0000000..fb4b42b
--- /dev/null
+++ b/windows/guildsettings/banspane.cpp
@@ -0,0 +1,136 @@
+#include "banspane.hpp"
+#include "../../abaddon.hpp"
+
+// gtk_list_store_set_value: assertion 'column >= 0 && column < priv->n_columns' failed
+// dont care to figure out why this happens cuz it doesnt seem to break anything
+
+GuildSettingsBansPane::GuildSettingsBansPane(Snowflake id)
+ : GuildID(id)
+ , m_menu_unban("Unban")
+ , m_menu_copy_id("Copy ID")
+ , m_model(Gtk::ListStore::create(m_columns)) {
+ set_name("guild-bans-pane");
+ set_hexpand(true);
+ set_vexpand(true);
+
+ auto &discord = Abaddon::Get().GetDiscordClient();
+
+ discord.signal_guild_ban_add().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnBanAdd));
+ discord.signal_guild_ban_remove().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnBanRemove));
+
+ const auto self_id = discord.GetUserData().ID;
+ const auto can_ban = discord.HasGuildPermission(self_id, GuildID, Permission::BAN_MEMBERS);
+
+ if (can_ban) {
+ discord.FetchGuildBans(id, sigc::mem_fun(*this, &GuildSettingsBansPane::OnGuildBansFetch));
+ } else {
+ for (const auto &ban : discord.GetBansInGuild(id))
+ OnGuildBanFetch(ban);
+ }
+
+ m_menu_unban.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnMenuUnban));
+ m_menu_copy_id.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnMenuCopyID));
+ m_menu_unban.show();
+ m_menu_copy_id.show();
+ m_menu.append(m_menu_unban);
+ m_menu.append(m_menu_copy_id);
+
+ m_view.signal_button_press_event().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnTreeButtonPress), false);
+ m_view.show();
+ add(m_view);
+
+ m_view.set_model(m_model);
+ m_view.append_column("User", m_columns.m_col_user);
+ m_view.append_column("Reason", m_columns.m_col_reason);
+}
+
+void GuildSettingsBansPane::OnGuildBanFetch(const BanData &ban) {
+ const auto user = Abaddon::Get().GetDiscordClient().GetUser(ban.User.ID);
+ auto &row = *m_model->append();
+ row[m_columns.m_col_id] = ban.User.ID;
+ if (user.has_value())
+ row[m_columns.m_col_user] = user->Username + "#" + user->Discriminator;
+ else
+ row[m_columns.m_col_user] = "<@" + std::to_string(ban.User.ID) + ">";
+
+ row[m_columns.m_col_reason] = ban.Reason;
+}
+
+void GuildSettingsBansPane::OnGuildBansFetch(const std::vector<BanData> &bans) {
+ for (const auto &ban : bans) {
+ const auto user = Abaddon::Get().GetDiscordClient().GetUser(ban.User.ID);
+ auto &row = *m_model->append();
+ row[m_columns.m_col_id] = user->ID;
+ row[m_columns.m_col_user] = user->Username + "#" + user->Discriminator;
+ row[m_columns.m_col_reason] = ban.Reason;
+ }
+}
+
+void GuildSettingsBansPane::OnMenuUnban() {
+ auto selected_row = *m_view.get_selection()->get_selected();
+ if (selected_row) {
+ Snowflake id = selected_row[m_columns.m_col_id];
+ auto cb = [this](bool success) {
+ if (!success) {
+ Gtk::MessageDialog dlg("Failed to unban user", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
+ dlg.run();
+ }
+ };
+ Abaddon::Get().GetDiscordClient().UnbanUser(GuildID, id, sigc::track_obj(cb, *this));
+ }
+}
+
+void GuildSettingsBansPane::OnMenuCopyID() {
+ auto selected_row = *m_view.get_selection()->get_selected();
+ if (selected_row)
+ Gtk::Clipboard::get()->set_text(std::to_string(static_cast<Snowflake>(selected_row[m_columns.m_col_id])));
+}
+
+bool GuildSettingsBansPane::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_ban = discord.HasGuildPermission(self_id, GuildID, Permission::BAN_MEMBERS);
+ m_menu_unban.set_sensitive(can_ban);
+ 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;
+}
+
+void GuildSettingsBansPane::OnBanRemove(Snowflake guild_id, Snowflake user_id) {
+ if (guild_id != GuildID) return;
+ for (auto &child : m_model->children()) {
+ if (static_cast<Snowflake>(child[m_columns.m_col_id]) == user_id) {
+ m_model->erase(child);
+ break;
+ }
+ }
+}
+
+void GuildSettingsBansPane::OnBanAdd(Snowflake guild_id, Snowflake user_id) {
+ if (guild_id != GuildID) return;
+ auto &discord = Abaddon::Get().GetDiscordClient();
+ if (discord.HasGuildPermission(discord.GetUserData().ID, guild_id, Permission::BAN_MEMBERS)) {
+ discord.FetchGuildBan(guild_id, user_id, sigc::mem_fun(*this, &GuildSettingsBansPane::OnGuildBanFetch));
+ } else {
+ auto user = *discord.GetUser(user_id);
+ auto &row = *m_model->append();
+ row[m_columns.m_col_id] = user_id;
+ row[m_columns.m_col_user] = user.Username + "#" + user.Discriminator;
+ row[m_columns.m_col_reason] = "";
+ }
+}
+
+GuildSettingsBansPane::ModelColumns::ModelColumns() {
+ add(m_col_id);
+ add(m_col_user);
+ add(m_col_reason);
+}
diff --git a/windows/guildsettings/banspane.hpp b/windows/guildsettings/banspane.hpp
new file mode 100644
index 0000000..c3e1933
--- /dev/null
+++ b/windows/guildsettings/banspane.hpp
@@ -0,0 +1,38 @@
+#pragma once
+#include <gtkmm.h>
+#include "../../discord/snowflake.hpp"
+#include "../../discord/ban.hpp"
+
+class GuildSettingsBansPane : public Gtk::ScrolledWindow {
+public:
+ GuildSettingsBansPane(Snowflake id);
+
+private:
+ void OnGuildBanFetch(const BanData &ban);
+ void OnGuildBansFetch(const std::vector<BanData> &bans);
+ void OnMenuUnban();
+ void OnMenuCopyID();
+ bool OnTreeButtonPress(GdkEventButton *event);
+ void OnBanRemove(Snowflake guild_id, Snowflake user_id);
+ void OnBanAdd(Snowflake guild_id, Snowflake user_id);
+
+ Gtk::TreeView m_view;
+
+ Snowflake GuildID;
+
+ class ModelColumns : public Gtk::TreeModel::ColumnRecord {
+ public:
+ ModelColumns();
+
+ Gtk::TreeModelColumn<Glib::ustring> m_col_user;
+ Gtk::TreeModelColumn<Glib::ustring> m_col_reason;
+ Gtk::TreeModelColumn<Snowflake> m_col_id;
+ };
+
+ ModelColumns m_columns;
+ Glib::RefPtr<Gtk::ListStore> m_model;
+
+ Gtk::Menu m_menu;
+ Gtk::MenuItem m_menu_unban;
+ Gtk::MenuItem m_menu_copy_id;
+};
diff --git a/windows/guildsettings/infopane.cpp b/windows/guildsettings/infopane.cpp
index 862c082..7ab672d 100644
--- a/windows/guildsettings/infopane.cpp
+++ b/windows/guildsettings/infopane.cpp
@@ -12,10 +12,6 @@ GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id)
const auto can_modify = discord.HasGuildPermission(self_id, id, Permission::MANAGE_GUILD);
set_name("guild-info-pane");
- set_margin_top(10);
- set_margin_bottom(10);
- set_margin_left(10);
- set_margin_top(10);
m_guild_name.set_sensitive(can_modify);
m_guild_name.set_text(guild.Name);
diff --git a/windows/guildsettingswindow.cpp b/windows/guildsettingswindow.cpp
index 618bcf5..4672729 100644
--- a/windows/guildsettingswindow.cpp
+++ b/windows/guildsettingswindow.cpp
@@ -4,7 +4,8 @@
GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
: m_main(Gtk::ORIENTATION_VERTICAL)
, GuildID(id)
- , m_pane_info(id) {
+ , m_pane_info(id)
+ , m_pane_bans(id) {
auto &discord = Abaddon::Get().GetDiscordClient();
const auto guild = *discord.GetGuild(id);
@@ -34,8 +35,15 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
m_switcher.show();
m_pane_info.show();
+ m_pane_bans.show();
+
+ m_stack.set_margin_top(10);
+ m_stack.set_margin_bottom(10);
+ m_stack.set_margin_left(10);
+ m_stack.set_margin_right(10);
m_stack.add(m_pane_info, "info", "Info");
+ m_stack.add(m_pane_bans, "bans", "Bans");
m_stack.show();
m_main.add(m_switcher);
diff --git a/windows/guildsettingswindow.hpp b/windows/guildsettingswindow.hpp
index 1904216..dc189fb 100644
--- a/windows/guildsettingswindow.hpp
+++ b/windows/guildsettingswindow.hpp
@@ -2,6 +2,7 @@
#include <gtkmm.h>
#include "../discord/snowflake.hpp"
#include "guildsettings/infopane.hpp"
+#include "guildsettings/banspane.hpp"
class GuildSettingsWindow : public Gtk::Window {
public:
@@ -13,6 +14,7 @@ private:
Gtk::StackSwitcher m_switcher;
GuildSettingsInfoPane m_pane_info;
+ GuildSettingsBansPane m_pane_bans;
Snowflake GuildID;
};