From 3923acd0dd7adef1710bfa039eb22454d9d551dd Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 24 Jan 2021 22:24:03 -0500 Subject: add viewing audit log --- css/main.css | 12 +- discord/auditlog.cpp | 34 +++ discord/auditlog.hpp | 111 ++++++++ discord/discord.cpp | 16 ++ discord/discord.hpp | 2 + discord/objects.hpp | 1 + discord/user.cpp | 4 + discord/user.hpp | 1 + discord/webhook.cpp | 13 + discord/webhook.hpp | 24 ++ windows/guildsettings/auditlogpane.cpp | 456 +++++++++++++++++++++++++++++++++ windows/guildsettings/auditlogpane.hpp | 15 ++ windows/guildsettingswindow.cpp | 6 +- windows/guildsettingswindow.hpp | 2 + 14 files changed, 690 insertions(+), 7 deletions(-) create mode 100644 discord/auditlog.cpp create mode 100644 discord/auditlog.hpp create mode 100644 discord/webhook.cpp create mode 100644 discord/webhook.hpp create mode 100644 windows/guildsettings/auditlogpane.cpp create mode 100644 windows/guildsettings/auditlogpane.hpp diff --git a/css/main.css b/css/main.css index 409488c..bc0370c 100644 --- a/css/main.css +++ b/css/main.css @@ -144,16 +144,16 @@ } .completer { - background-color: @secondary_color; - padding: 5px; + background-color: @secondary_color; + padding: 5px; } .completer-entry { - color: @text_color; + color: @text_color; } .completer-entry-image { - margin-right: 6px; + margin-right: 6px; } .typing-indicator { @@ -200,8 +200,8 @@ background: @secondary_color; } -.app-window listbox { - background: @background_color; +.app-popup list { + background: @secondary_color; } .app-window paned separator { diff --git a/discord/auditlog.cpp b/discord/auditlog.cpp new file mode 100644 index 0000000..5f3cde5 --- /dev/null +++ b/discord/auditlog.cpp @@ -0,0 +1,34 @@ +#include "auditlog.hpp" + +void from_json(const nlohmann::json &j, AuditLogChange &m) { + JS_D("key", m.Key); + JS_O("old_value", m.OldValue); + JS_O("new_value", m.NewValue); +} + +void from_json(const nlohmann::json &j, AuditLogOptions &m) { + JS_O("delete_member_days", m.DeleteMemberDays); + JS_O("members_removed", m.MembersRemoved); + JS_O("channel_id", m.ChannelID); + JS_O("message_id", m.MessageID); + JS_O("count", m.Count); + JS_O("id", m.ID); + JS_O("type", m.Type); + JS_O("role_name", m.RoleName); +} + +void from_json(const nlohmann::json &j, AuditLogEntry &m) { + JS_N("target_id", m.TargetID); + JS_O("changes", m.Changes); + JS_D("user_id", m.UserID); + JS_D("id", m.ID); + JS_D("action_type", m.Type); + JS_O("options", m.Options); + JS_O("reason", m.Reason); +} + +void from_json(const nlohmann::json &j, AuditLogData &m) { + JS_D("audit_log_entries", m.Entries); + JS_D("users", m.Users); + JS_D("webhooks", m.Webhooks); +} diff --git a/discord/auditlog.hpp b/discord/auditlog.hpp new file mode 100644 index 0000000..c6ce0cc --- /dev/null +++ b/discord/auditlog.hpp @@ -0,0 +1,111 @@ +#pragma once +#include "snowflake.hpp" +#include "user.hpp" +#include "json.hpp" +#include "webhook.hpp" + +enum class AuditLogActionType { + GUILD_UPDATE = 1, + CHANNEL_CREATE = 10, + CHANNEL_UPDATE = 11, + CHANNEL_DELETE = 12, + CHANNEL_OVERWRITE_CREATE = 13, + CHANNEL_OVERWRITE_UPDATE = 14, + CHANNEL_OVERWRITE_DELETE = 15, + MEMBER_KICK = 20, + MEMBER_PRUNE = 21, + MEMBER_BAN_ADD = 22, + MEMBER_BAN_REMOVE = 23, + MEMBER_UPDATE = 24, + MEMBER_ROLE_UPDATE = 25, + MEMBER_MOVE = 26, + MEMBER_DISCONNECT = 27, + BOT_ADD = 28, + ROLE_CREATE = 30, + ROLE_UPDATE = 31, + ROLE_DELETE = 32, + INVITE_CREATE = 40, + INVITE_UPDATE = 41, + INVITE_DELETE = 42, + WEBHOOK_CREATE = 50, + WEBHOOK_UPDATE = 51, + WEBHOOK_DELETE = 52, + EMOJI_CREATE = 60, + EMOJI_UPDATE = 61, + EMOJI_DELETE = 62, + MESSAGE_DELETE = 72, + MESSAGE_BULK_DELETE = 73, + MESSAGE_PIN = 74, + MESSAGE_UNPIN = 75, + INTEGRATION_CREATE = 80, + INTEGRATION_UPDATE = 81, + INTEGRATION_DELETE = 82, +}; + +struct AuditLogChange { + std::string Key; + std::optional OldValue; + std::optional NewValue; + + friend void from_json(const nlohmann::json &j, AuditLogChange &m); +}; + +struct AuditLogOptions { + std::optional DeleteMemberDays; // MEMBER_PRUNE + std::optional MembersRemoved; // MEMBER_PRUNE + std::optional ChannelID; // MEMBER_MOVE, MESSAGE_PIN, MESSAGE_UNPIN, MESSAGE_DELETE + std::optional MessageID; // MESSAGE_PIN, MESSAGE_UNPIN, + std::optional Count; // MESSAGE_DELETE, MESSAGE_BULK_DELETE, MEMBER_DISCONNECT, MEMBER_MOVE + std::optional ID; // CHANNEL_OVERWRITE_CREATE, CHANNEL_OVERWRITE_UPDATE, CHANNEL_OVERWRITE_DELETE + std::optional Type; // CHANNEL_OVERWRITE_CREATE, CHANNEL_OVERWRITE_UPDATE, CHANNEL_OVERWRITE_DELETE + std::optional RoleName; // CHANNEL_OVERWRITE_CREATE, CHANNEL_OVERWRITE_UPDATE, CHANNEL_OVERWRITE_DELETE + + friend void from_json(const nlohmann::json &j, AuditLogOptions &m); +}; + +struct AuditLogEntry { + Snowflake ID; + std::string TargetID; // null + Snowflake UserID; + AuditLogActionType Type; + std::optional Reason; + std::optional> Changes; + std::optional Options; + + friend void from_json(const nlohmann::json &j, AuditLogEntry &m); + + template + std::optional GetOldFromKey(const std::string &key) const; + + template + std::optional GetNewFromKey(const std::string &key) const; +}; + +struct AuditLogData { + std::vector Entries; + std::vector Users; + std::vector Webhooks; + // std::vector Integrations; + + friend void from_json(const nlohmann::json &j, AuditLogData &m); +}; + +template +inline std::optional AuditLogEntry::GetOldFromKey(const std::string &key) const { + if (!Changes.has_value()) return std::nullopt; + for (const auto &change : *Changes) + if (change.Key == key && change.OldValue.has_value()) + return change.OldValue->get(); + + return std::nullopt; +} + +template +inline std::optional AuditLogEntry::GetNewFromKey(const std::string &key) const { + if (!Changes.has_value()) return std::nullopt; + for (const auto &change : *Changes) + if (change.Key == key && change.NewValue.has_value()) + return change.NewValue->get(); + + return std::nullopt; +} diff --git a/discord/discord.cpp b/discord/discord.cpp index 75be116..9d1dd04 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -553,6 +553,22 @@ void DiscordClient::FetchGuildInvites(Snowflake guild_id, sigc::slot callback) { + sigc::signal signal; + signal.connect(callback); + m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/audit-logs", [this, callback](const http::response &response) { + if (!CheckCode(response)) return; + auto data = nlohmann::json::parse(response.text).get(); + + m_store.BeginTransaction(); + for (const auto &user : data.Users) + m_store.SetUser(user.ID, user); + m_store.EndTransaction(); + + callback(data); + }); +} + void DiscordClient::UpdateToken(std::string token) { if (!IsStarted()) { m_token = token; diff --git a/discord/discord.hpp b/discord/discord.hpp index a6f5d99..025684a 100644 --- a/discord/discord.hpp +++ b/discord/discord.hpp @@ -127,6 +127,8 @@ public: void FetchInvite(std::string code, sigc::slot)> callback); void FetchGuildInvites(Snowflake guild_id, sigc::slot)> callback); + void FetchAuditLog(Snowflake guild_id, sigc::slot callback); + void UpdateToken(std::string token); void SetUserAgent(std::string agent); diff --git a/discord/objects.hpp b/discord/objects.hpp index 1f37ce9..575ee85 100644 --- a/discord/objects.hpp +++ b/discord/objects.hpp @@ -17,6 +17,7 @@ #include "activity.hpp" #include "sticker.hpp" #include "ban.hpp" +#include "auditlog.hpp" // most stuff below should just be objects that get processed and thrown away immediately diff --git a/discord/user.cpp b/discord/user.cpp index edaa701..443365d 100644 --- a/discord/user.cpp +++ b/discord/user.cpp @@ -21,6 +21,10 @@ std::string UserData::GetMention() const { return "<@" + std::to_string(ID) + ">"; } +std::string UserData::GetEscapedString() const { + return Glib::Markup::escape_text(Username) + "#" + Discriminator; +} + void from_json(const nlohmann::json &j, UserData &m) { JS_D("id", m.ID); JS_D("username", m.Username); diff --git a/discord/user.hpp b/discord/user.hpp index 4672ea8..b82a07b 100644 --- a/discord/user.hpp +++ b/discord/user.hpp @@ -33,4 +33,5 @@ struct UserData { std::string GetAvatarURL(std::string ext = "png", std::string size = "32") const; Snowflake GetHoistedRole(Snowflake guild_id, bool with_color = false) const; std::string GetMention() const; + std::string GetEscapedString() const; }; diff --git a/discord/webhook.cpp b/discord/webhook.cpp new file mode 100644 index 0000000..4e8b422 --- /dev/null +++ b/discord/webhook.cpp @@ -0,0 +1,13 @@ +#include "webhook.hpp" + +void from_json(const nlohmann::json &j, WebhookData &m) { + JS_D("id", m.ID); + JS_D("type", m.Type); + JS_O("guild_id", m.GuildID); + JS_D("channel_id", m.ChannelID); + JS_O("user", m.User); + JS_N("name", m.Name); + JS_N("avatar", m.Avatar); + JS_O("token", m.Token); + JS_N("application_id", m.ApplicationID); +} diff --git a/discord/webhook.hpp b/discord/webhook.hpp new file mode 100644 index 0000000..f0214df --- /dev/null +++ b/discord/webhook.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include "json.hpp" +#include "snowflake.hpp" +#include "user.hpp" + +enum class WebhookType { + Incoming = 1, + ChannelFollower = 2, +}; + +struct WebhookData { + Snowflake ID; + WebhookType Type; + std::optional GuildID; + Snowflake ChannelID; + std::optional User; + std::string Name; // null + std::string Avatar; // null + std::optional Token; + Snowflake ApplicationID; // null + + friend void from_json(const nlohmann::json &j, WebhookData &m); +}; diff --git a/windows/guildsettings/auditlogpane.cpp b/windows/guildsettings/auditlogpane.cpp new file mode 100644 index 0000000..4637838 --- /dev/null +++ b/windows/guildsettings/auditlogpane.cpp @@ -0,0 +1,456 @@ +#include "auditlogpane.hpp" +#include "../../abaddon.hpp" + +using namespace std::string_literals; + +GuildSettingsAuditLogPane::GuildSettingsAuditLogPane(Snowflake id) + : GuildID(id) { + set_name("guild-audit-log-pane"); + set_hexpand(true); + set_vexpand(true); + + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto self_id = discord.GetUserData().ID; + + if (discord.HasGuildPermission(self_id, GuildID, Permission::VIEW_AUDIT_LOG)) + discord.FetchAuditLog(GuildID, sigc::mem_fun(*this, &GuildSettingsAuditLogPane::OnAuditLogFetch)); + + m_list.set_selection_mode(Gtk::SELECTION_NONE); + m_list.show(); + add(m_list); +} + +void GuildSettingsAuditLogPane::OnAuditLogFetch(const AuditLogData &data) { + auto &discord = Abaddon::Get().GetDiscordClient(); + auto guild = *discord.GetGuild(GuildID); + for (const auto &entry : data.Entries) { + auto expander = Gtk::manage(new Gtk::Expander); + auto label = Gtk::manage(new Gtk::Label); + label->set_ellipsize(Pango::ELLIPSIZE_END); + + auto user = *discord.GetUser(entry.UserID); + + // spaghetti moment + Glib::ustring markup; + std::vector extra_markup; + switch (entry.Type) { + case AuditLogActionType::GUILD_UPDATE: { + markup = + "" + user.GetEscapedString() + " " + + "made changes to " + + Glib::Markup::escape_text(guild.Name) + + ""; + + if (entry.Changes.has_value()) + for (const auto &change : *entry.Changes) { + if (change.Key == "icon_hash") { + extra_markup.push_back("Set the server icon"); + } else if (change.Key == "name") { + auto new_name = change.NewValue; + if (new_name.has_value()) + extra_markup.push_back("Set the server name to " + + Glib::Markup::escape_text(new_name->get()) + + ""); + else + extra_markup.push_back("Set the server name"); + } + } + } break; + case AuditLogActionType::CHANNEL_CREATE: { + const auto type = *entry.GetNewFromKey("type"); + markup = "" + user.GetEscapedString() + " " + + "created a " + (type == ChannelType::GUILD_VOICE ? "voice" : "text") + + " channel #" + + Glib::Markup::escape_text(*entry.GetNewFromKey("name")) + + ""; + if (entry.Changes.has_value()) + for (const auto &change : *entry.Changes) { + if (change.Key == "name" && change.NewValue.has_value()) + extra_markup.push_back("Set the name to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + else if (change.Key == "nsfw" && change.NewValue.has_value()) + extra_markup.push_back((*change.NewValue ? "Marked" : "Unmarked") + + " the channel as NSFW"s); + } + + } break; + case AuditLogActionType::CHANNEL_UPDATE: { + const auto target_channel = discord.GetChannel(entry.TargetID); + if (target_channel.has_value()) { + markup = "" + user.GetEscapedString() + " " + + "made changes to #" + + Glib::Markup::escape_text(*target_channel->Name) + + ""; + } else { + markup = "" + user.GetEscapedString() + " " + + "made changes to <#" + + entry.TargetID + + ">"; + } + if (entry.Changes.has_value()) + for (const auto &change : *entry.Changes) { + if (change.Key == "name" && change.NewValue.has_value()) { + if (change.OldValue.has_value()) + extra_markup.push_back("Changed the name from " + + Glib::Markup::escape_text(change.OldValue->get()) + + " to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + else + extra_markup.push_back("Changed the name to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } else if (change.Key == "topic") { + if (change.NewValue.has_value()) + extra_markup.push_back("Changed the topic to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + else + extra_markup.push_back("Cleared the topic"); + } else if (change.Key == "nsfw" && change.NewValue.has_value()) { + extra_markup.push_back((*change.NewValue ? "Marked" : "Unmarked") + " the channel as NSFW"s); + } else if (change.Key == "rate_limit_per_user" && change.NewValue.has_value()) { + const int secs = change.NewValue->get(); + if (secs == 0) + extra_markup.push_back("Disabled slowmode"); + else + extra_markup.push_back("Set slowmode to " + + std::to_string(secs) + " seconds"); + } + } + } break; + case AuditLogActionType::CHANNEL_DELETE: { + markup = "" + user.GetEscapedString() + " " + + "removed #" + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + ""; + } break; + case AuditLogActionType::CHANNEL_OVERWRITE_CREATE: { + const auto channel = discord.GetChannel(entry.TargetID); + if (channel.has_value()) { + markup = "" + user.GetEscapedString() + " " + + "created channel overrides for #" + + Glib::Markup::escape_text(*channel->Name) + ""; + } else { + markup = "" + user.GetEscapedString() + " " + + "created channel overrides for <#" + + entry.TargetID + ">"; + } + } break; + case AuditLogActionType::CHANNEL_OVERWRITE_UPDATE: { + const auto channel = discord.GetChannel(entry.TargetID); + if (channel.has_value()) { + markup = "" + user.GetEscapedString() + " " + + "updated channel overrides for #" + + Glib::Markup::escape_text(*channel->Name) + ""; + } else { + markup = "" + user.GetEscapedString() + " " + + "updated channel overrides for <#" + + entry.TargetID + ">"; + } + } break; + case AuditLogActionType::CHANNEL_OVERWRITE_DELETE: { + const auto channel = discord.GetChannel(entry.TargetID); + if (channel.has_value()) { + markup = "" + user.GetEscapedString() + " " + + "removed channel overrides for #" + + Glib::Markup::escape_text(*channel->Name) + ""; + } else { + markup = "" + user.GetEscapedString() + " " + + "removed channel overrides for <#" + + entry.TargetID + ">"; + } + } break; + case AuditLogActionType::MEMBER_KICK: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = "" + user.GetEscapedString() + " " + + "kicked " + + target_user->GetEscapedString() + + ""; + } break; + case AuditLogActionType::MEMBER_PRUNE: { + markup = "" + user.GetEscapedString() + " " + + "pruned " + + *entry.Options->MembersRemoved + + " members"; + extra_markup.push_back("For " + + *entry.Options->DeleteMemberDays + + " days of inactivity"); + } break; + case AuditLogActionType::MEMBER_BAN_ADD: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = "" + user.GetEscapedString() + " " + + "banned " + + target_user->GetEscapedString() + + ""; + } break; + case AuditLogActionType::MEMBER_BAN_REMOVE: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = "" + user.GetEscapedString() + " " + + "removed the ban for " + + target_user->GetEscapedString() + + ""; + } break; + case AuditLogActionType::MEMBER_UPDATE: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = "" + user.GetEscapedString() + " " + + "updated " + + target_user->GetEscapedString() + + ""; + if (entry.Changes.has_value()) + for (const auto &change : *entry.Changes) { + if (change.Key == "deaf" && change.NewValue.has_value()) + extra_markup.push_back( + (change.NewValue->get() ? "Deafened"s : "Undeafened"s) + + " them"); + else if (change.Key == "mute" && change.NewValue.has_value()) + extra_markup.push_back( + (change.NewValue->get() ? "Muted"s : "Unmuted"s) + + " them"); + else if (change.Key == "nick" && change.NewValue.has_value()) + extra_markup.push_back("Set their nickname to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } + } break; + case AuditLogActionType::MEMBER_ROLE_UPDATE: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = "" + user.GetEscapedString() + " " + + "updated roles for " + + target_user->GetEscapedString() + ""; + if (entry.Changes.has_value()) + for (const auto &change : *entry.Changes) { + if (change.Key == "$remove" && change.NewValue.has_value()) { + extra_markup.push_back("Removed a role " + + Glib::Markup::escape_text(change.NewValue.value()[0].at("name").get()) + + ""); + } else if (change.Key == "$add" && change.NewValue.has_value()) { + extra_markup.push_back("Added a role " + + Glib::Markup::escape_text(change.NewValue.value()[0].at("name").get()) + + ""); + } + } + } break; + case AuditLogActionType::MEMBER_MOVE: { + const auto channel = discord.GetChannel(*entry.Options->ChannelID); + markup = "" + user.GetEscapedString() + " " + + "moved " + + *entry.Options->Count + + " user" + + (*entry.Options->Count == "1" ? ""s : "s"s) + + " to " + + Glib::Markup::escape_text(*channel->Name) + + ""; + } break; + case AuditLogActionType::MEMBER_DISCONNECT: { + markup = "" + user.GetEscapedString() + " " + + "disconnected " + + *entry.Options->Count + + " users from voice"; + } break; + case AuditLogActionType::BOT_ADD: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = "" + user.GetEscapedString() + " " + + "added " + + target_user->GetEscapedString() + + " to the server"; + } break; + case AuditLogActionType::ROLE_CREATE: { + markup = "" + user.GetEscapedString() + " " + + "created the role " + + *entry.GetNewFromKey("name") + + ""; + } break; + case AuditLogActionType::ROLE_UPDATE: { + const auto role = discord.GetRole(entry.TargetID); + markup = "" + user.GetEscapedString() + " " + + "updated the role " + + (role.has_value() ? Glib::Markup::escape_text(role->Name) : entry.TargetID) + + ""; + if (entry.Changes.has_value()) + for (const auto &change : *entry.Changes) { + if (change.Key == "name" && change.NewValue.has_value()) { + extra_markup.push_back("Changed the name to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } else if (change.Key == "color" && change.NewValue.has_value()) { + const auto col = change.NewValue->get(); + if (col == 0) + extra_markup.push_back("Removed the color"); + else + extra_markup.push_back("Set the color to " + + IntToCSSColor(col) + + ""); + } else if (change.Key == "permissions") { + extra_markup.push_back("Updated the permissions"); + } else if (change.Key == "mentionable" && change.NewValue.has_value()) { + extra_markup.push_back(change.NewValue->get() ? "Mentionable" : "Not mentionable"); + } else if (change.Key == "hoist" && change.NewValue.has_value()) { + extra_markup.push_back(change.NewValue->get() ? "Not hoisted" : "Hoisted"); + } + } + } break; + case AuditLogActionType::ROLE_DELETE: { + markup = "" + user.GetEscapedString() + " " + + "deleted the role " + + *entry.GetOldFromKey("name") + + ""; + } break; + case AuditLogActionType::INVITE_CREATE: { + const auto code = *entry.GetNewFromKey("code"); + markup = "" + user.GetEscapedString() + " " + + "created an invite " + code + ""; + if (entry.Changes.has_value()) + for (const auto &change : *entry.Changes) { + if (change.Key == "channel_id" && change.NewValue.has_value()) { + const auto channel = discord.GetChannel(change.NewValue->get()); + if (!channel.has_value()) continue; + extra_markup.push_back("For channel #" + + Glib::Markup::escape_text(*channel->Name) + + ""); + } else if (change.Key == "max_uses" && change.NewValue.has_value()) { + const auto uses = change.NewValue->get(); + if (uses == 0) + extra_markup.push_back("Which has unlimited uses"); + else + extra_markup.push_back("Which has " + std::to_string(uses) + " uses"); + } else if (change.Key == "temporary" && change.NewValue.has_value()) { + extra_markup.push_back("With temporary "s + + (change.NewValue->get() ? "on" : "off") + + ""); + } // no max_age cuz fuck time + } + } break; + case AuditLogActionType::INVITE_DELETE: { + markup = "" + user.GetEscapedString() + " " + + "deleted an invite " + + *entry.GetOldFromKey("code") + + ""; + } break; + case AuditLogActionType::WEBHOOK_CREATE: { + markup = "" + user.GetEscapedString() + " " + + "created the webhook " + + Glib::Markup::escape_text(*entry.GetNewFromKey("name")) + + ""; + for (const auto &change : *entry.Changes) { + if (change.Key == "channel_id" && change.NewValue.has_value()) { + const auto channel = discord.GetChannel(change.NewValue->get()); + if (channel.has_value()) { + extra_markup.push_back("With channel #" + + Glib::Markup::escape_text(*channel->Name) + + ""); + } + } + } + } break; + case AuditLogActionType::WEBHOOK_UPDATE: { + const WebhookData *webhookptr = nullptr; + for (const auto &webhook : data.Webhooks) { + if (webhook.ID == entry.TargetID) + webhookptr = &webhook; + } + if (webhookptr != nullptr) { + markup = "" + user.GetEscapedString() + " " + + "updated the webhook " + + Glib::Markup::escape_text(webhookptr->Name) + + ""; + } else { + markup = "" + user.GetEscapedString() + " " + + "updated a webhook"; + } + if (entry.Changes.has_value()) + for (const auto &change : *entry.Changes) { + if (change.Key == "name" && change.NewValue.has_value()) { + extra_markup.push_back("Changed the name to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } else if (change.Key == "avatar_hash") { + extra_markup.push_back("Changed the avatar"); + } else if (change.Key == "channel_id" && change.NewValue.has_value()) { + const auto channel = discord.GetChannel(change.NewValue->get()); + if (channel.has_value()) { + extra_markup.push_back("Changed the channel to #" + + Glib::Markup::escape_text(*channel->Name) + + ""); + } else { + extra_markup.push_back("Changed the channel"); + } + } + } + } break; + case AuditLogActionType::WEBHOOK_DELETE: { + markup = "" + user.GetEscapedString() + " " + + "deleted the webhook " + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + ""; + } break; + case AuditLogActionType::EMOJI_CREATE: { + markup = "" + user.GetEscapedString() + " " + + "created the emoji " + + Glib::Markup::escape_text(*entry.GetNewFromKey("name")) + + ""; + } break; + case AuditLogActionType::EMOJI_UPDATE: { + markup = "" + user.GetEscapedString() + " " + + "updated the emoji " + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + ""; + extra_markup.push_back("Changed the name from " + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + " to " + + Glib::Markup::escape_text(*entry.GetNewFromKey("name")) + + ""); + } break; + case AuditLogActionType::EMOJI_DELETE: { + markup = "" + user.GetEscapedString() + " " + + "deleted the emoji " + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + ""; + } break; + case AuditLogActionType::MESSAGE_PIN: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = "" + user.GetEscapedString() + " " + + "pinned a message by " + + target_user->GetEscapedString() + + ""; + } break; + case AuditLogActionType::MESSAGE_UNPIN: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = "" + user.GetEscapedString() + " " + + "unpinned a message by " + + target_user->GetEscapedString() + + ""; + } break; + default: + markup = "Unknown action"; + break; + } + + label->set_markup(markup); + expander->set_label_widget(*label); + + if (entry.Reason.has_value()) { + extra_markup.push_back("With reason " + + Glib::Markup::escape_text(*entry.Reason) + + ""); + } + + if (extra_markup.size() == 1) + expander->set_expanded(true); + + auto contents = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + for (const auto &extra : extra_markup) { + auto extra_label = Gtk::manage(new Gtk::Label); + extra_label->set_markup(extra); + extra_label->set_halign(Gtk::ALIGN_START); + extra_label->set_margin_start(25); + extra_label->set_ellipsize(Pango::ELLIPSIZE_END); + contents->add(*extra_label); + } + expander->add(*contents); + expander->set_margin_bottom(5); + expander->show_all(); + m_list.add(*expander); + } +} diff --git a/windows/guildsettings/auditlogpane.hpp b/windows/guildsettings/auditlogpane.hpp new file mode 100644 index 0000000..caa5af6 --- /dev/null +++ b/windows/guildsettings/auditlogpane.hpp @@ -0,0 +1,15 @@ +#pragma once +#include +#include "../../discord/objects.hpp" + +class GuildSettingsAuditLogPane : public Gtk::ScrolledWindow { +public: + GuildSettingsAuditLogPane(Snowflake id); + +private: + Gtk::ListBox m_list; + + void OnAuditLogFetch(const AuditLogData &data); + + Snowflake GuildID; +}; diff --git a/windows/guildsettingswindow.cpp b/windows/guildsettingswindow.cpp index 2124a0e..fc3b567 100644 --- a/windows/guildsettingswindow.cpp +++ b/windows/guildsettingswindow.cpp @@ -6,7 +6,8 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id) , GuildID(id) , m_pane_info(id) , m_pane_bans(id) - , m_pane_invites(id) { + , m_pane_invites(id) + , m_pane_audit_log(id) { auto &discord = Abaddon::Get().GetDiscordClient(); const auto guild = *discord.GetGuild(id); @@ -24,6 +25,7 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id) set_title(guild.Name); set_position(Gtk::WIN_POS_CENTER); get_style_context()->add_class("app-window"); + get_style_context()->add_class("app-popup"); if (guild.HasIcon()) { Abaddon::Get().GetImageManager().LoadFromURL(guild.GetIconURL(), sigc::mem_fun(*this, &GuildSettingsWindow::set_icon)); @@ -38,6 +40,7 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id) m_pane_info.show(); m_pane_bans.show(); m_pane_invites.show(); + m_pane_audit_log.show(); m_stack.set_transition_duration(100); m_stack.set_transition_type(Gtk::STACK_TRANSITION_TYPE_CROSSFADE); @@ -49,6 +52,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.add(m_pane_audit_log, "audit-log", "Audit Log"); m_stack.show(); m_main.add(m_switcher); diff --git a/windows/guildsettingswindow.hpp b/windows/guildsettingswindow.hpp index 811c937..a88a730 100644 --- a/windows/guildsettingswindow.hpp +++ b/windows/guildsettingswindow.hpp @@ -4,6 +4,7 @@ #include "guildsettings/infopane.hpp" #include "guildsettings/banspane.hpp" #include "guildsettings/invitespane.hpp" +#include "guildsettings/auditlogpane.hpp" class GuildSettingsWindow : public Gtk::Window { public: @@ -17,6 +18,7 @@ private: GuildSettingsInfoPane m_pane_info; GuildSettingsBansPane m_pane_bans; GuildSettingsInvitesPane m_pane_invites; + GuildSettingsAuditLogPane m_pane_audit_log; Snowflake GuildID; }; -- cgit v1.2.3