summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--components/chatmessage.cpp11
-rw-r--r--discord/channel.cpp9
-rw-r--r--discord/channel.hpp40
-rw-r--r--discord/discord.cpp89
-rw-r--r--discord/discord.hpp7
-rw-r--r--discord/objects.hpp1
-rw-r--r--discord/permissions.cpp12
-rw-r--r--discord/permissions.hpp60
-rw-r--r--discord/role.cpp2
-rw-r--r--discord/role.hpp3
-rw-r--r--discord/store.cpp4
-rw-r--r--discord/store.hpp1
-rw-r--r--util.hpp36
14 files changed, 251 insertions, 26 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b1c9f29..aeaec9b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -173,6 +173,8 @@ add_executable(abaddon
discord/message.cpp
discord/invite.hpp
discord/invite.cpp
+ discord/permissions.hpp
+ discord/permissions.cpp
windows/mainwindow.hpp
windows/mainwindow.cpp
)
diff --git a/components/chatmessage.cpp b/components/chatmessage.cpp
index c846e20..6d1fb91 100644
--- a/components/chatmessage.cpp
+++ b/components/chatmessage.cpp
@@ -163,11 +163,12 @@ void ChatMessageItem::AttachMenuHandler(Gtk::Widget *widget) {
// clang-format on
void ChatMessageItem::ShowMenu(const GdkEvent *event) {
- auto &client = Abaddon::Get().GetDiscordClient();
- auto *data = client.GetMessage(ID);
- bool can_manage = client.GetUserData().ID == data->Author.ID;
- m_menu_delete_message->set_sensitive(can_manage);
- m_menu_edit_message->set_sensitive(can_manage);
+ const auto &client = Abaddon::Get().GetDiscordClient();
+ const auto *data = client.GetMessage(ID);
+ const bool can_edit = client.GetUserData().ID == data->Author.ID;
+ const bool can_delete = can_edit || client.HasChannelPermission(client.GetUserData().ID, ChannelID, Permission::MANAGE_MESSAGES);
+ m_menu_delete_message->set_sensitive(can_delete);
+ m_menu_edit_message->set_sensitive(can_edit);
m_menu.popup_at_pointer(event);
}
diff --git a/discord/channel.cpp b/discord/channel.cpp
index fe1a6a6..482d731 100644
--- a/discord/channel.cpp
+++ b/discord/channel.cpp
@@ -5,7 +5,7 @@ void from_json(const nlohmann::json &j, Channel &m) {
JS_D("type", m.Type);
JS_O("guild_id", m.GuildID);
JS_O("position", m.Position);
- // JS_O("permission_overwrites", m.PermissionOverwrites);
+ JS_O("permission_overwrites", m.PermissionOverwrites);
JS_ON("name", m.Name);
JS_ON("topic", m.Topic);
JS_O("nsfw", m.IsNSFW);
@@ -20,3 +20,10 @@ void from_json(const nlohmann::json &j, Channel &m) {
JS_ON("parent_id", m.ParentID);
JS_ON("last_pin_timestamp", m.LastPinTimestamp);
}
+
+std::optional<PermissionOverwrite> Channel::GetOverwrite(Snowflake id) const {
+ auto ret = std::find_if(PermissionOverwrites.begin(), PermissionOverwrites.end(), [id](const auto x) { return x.ID == id; });
+ if (ret != PermissionOverwrites.end())
+ return *ret;
+ return std::nullopt;
+}
diff --git a/discord/channel.hpp b/discord/channel.hpp
index 78246f6..9d876e3 100644
--- a/discord/channel.hpp
+++ b/discord/channel.hpp
@@ -2,6 +2,8 @@
#include "snowflake.hpp"
#include "json.hpp"
#include "user.hpp"
+#include "permissions.hpp"
+#include <optional>
#include <string>
#include <vector>
@@ -16,24 +18,26 @@ enum class ChannelType : int {
};
struct Channel {
- Snowflake ID; //
- ChannelType Type; //
- Snowflake GuildID; // opt
- int Position = -1; // opt
- // std::vector<PermissionOverwriteData> PermissionOverwrites; // opt
- std::string Name; // opt, null (null for dm's)
- std::string Topic; // opt, null
- bool IsNSFW = false; // opt
- Snowflake LastMessageID; // opt, null
- int Bitrate = 0; // opt
- int UserLimit = 0; // opt
- int RateLimitPerUser = 0; // opt
- std::vector<User> Recipients; // opt
- std::string Icon; // opt, null
- Snowflake OwnerID; // opt
- Snowflake ApplicationID; // opt
- Snowflake ParentID; // opt, null
- std::string LastPinTimestamp; // opt, can be null even tho docs say otherwise
+ Snowflake ID; //
+ ChannelType Type; //
+ Snowflake GuildID; // opt
+ int Position = -1; // opt
+ std::vector<PermissionOverwrite> PermissionOverwrites; // opt
+ std::string Name; // opt, null (null for dm's)
+ std::string Topic; // opt, null
+ bool IsNSFW = false; // opt
+ Snowflake LastMessageID; // opt, null
+ int Bitrate = 0; // opt
+ int UserLimit = 0; // opt
+ int RateLimitPerUser = 0; // opt
+ std::vector<User> Recipients; // opt
+ std::string Icon; // opt, null
+ Snowflake OwnerID; // opt
+ Snowflake ApplicationID; // opt
+ Snowflake ParentID; // opt, null
+ std::string LastPinTimestamp; // opt, can be null even tho docs say otherwise
friend void from_json(const nlohmann::json &j, Channel &m);
+
+ std::optional<PermissionOverwrite> GetOverwrite(Snowflake id) const;
};
diff --git a/discord/discord.cpp b/discord/discord.cpp
index 9d1842a..6ef00a7 100644
--- a/discord/discord.cpp
+++ b/discord/discord.cpp
@@ -194,6 +194,10 @@ const Guild *DiscordClient::GetGuild(Snowflake id) const {
return m_store.GetGuild(id);
}
+const GuildMember *DiscordClient::GetMember(Snowflake user_id, Snowflake guild_id) const {
+ return m_store.GetGuildMemberData(guild_id, user_id);
+}
+
Snowflake DiscordClient::GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color) const {
auto *data = m_store.GetGuildMemberData(guild_id, user_id);
if (data == nullptr) return Snowflake::Invalid;
@@ -224,6 +228,91 @@ std::unordered_set<Snowflake> DiscordClient::GetUsersInGuild(Snowflake id) const
return std::unordered_set<Snowflake>();
}
+std::unordered_set<Snowflake> DiscordClient::GetRolesInGuild(Snowflake id) const {
+ std::unordered_set<Snowflake> ret;
+ const auto &roles = m_store.GetRoles();
+ for (const auto &[rid, rdata] : roles)
+ ret.insert(rid);
+ return ret;
+}
+
+bool DiscordClient::HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const {
+ const auto base = ComputePermissions(user_id, guild_id);
+ return (base & perm) == perm;
+}
+
+bool DiscordClient::HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const {
+ const auto *channel = m_store.GetChannel(channel_id);
+ if (channel == nullptr) return false;
+ const auto base = ComputePermissions(user_id, channel->GuildID);
+ const auto overwrites = ComputeOverwrites(base, user_id, channel_id);
+ return (overwrites & perm) == perm;
+}
+
+Permission DiscordClient::ComputePermissions(Snowflake member_id, Snowflake guild_id) const {
+ const auto *member = GetMember(member_id, guild_id);
+ const auto *guild = GetGuild(guild_id);
+ if (member == nullptr || guild == nullptr)
+ return Permission::NONE;
+
+ if (guild->OwnerID == member_id)
+ return Permission::ALL;
+
+ const auto *everyone = GetRole(guild_id);
+ if (everyone == nullptr)
+ return Permission::NONE;
+
+ Permission perms = everyone->Permissions;
+ for (const auto role_id : member->Roles) {
+ const auto *role = GetRole(role_id);
+ if (role != nullptr)
+ perms |= role->Permissions;
+ }
+
+ if ((perms & Permission::ADMINISTRATOR) == Permission::ADMINISTRATOR)
+ return Permission::ALL;
+
+ return perms;
+}
+
+Permission DiscordClient::ComputeOverwrites(Permission base, Snowflake member_id, Snowflake channel_id) const {
+ if ((base & Permission::ADMINISTRATOR) == Permission::ADMINISTRATOR)
+ return Permission::ALL;
+
+ const auto *channel = GetChannel(channel_id);
+ const auto *member = GetMember(member_id, channel->GuildID);
+ if (member == nullptr || channel == nullptr)
+ return Permission::NONE;
+
+ Permission perms = base;
+ auto overwrite_everyone = channel->GetOverwrite(channel->GuildID);
+ if (overwrite_everyone.has_value()) {
+ perms &= ~overwrite_everyone->Deny;
+ perms |= overwrite_everyone->Allow;
+ }
+
+ Permission allow = Permission::NONE;
+ Permission deny = Permission::NONE;
+ for (const auto role_id : member->Roles) {
+ const auto overwrite = channel->GetOverwrite(role_id);
+ if (overwrite.has_value()) {
+ allow |= overwrite->Allow;
+ deny |= overwrite->Deny;
+ }
+ }
+
+ perms &= ~deny;
+ perms |= allow;
+
+ const auto member_overwrite = channel->GetOverwrite(member_id);
+ if (member_overwrite.has_value()) {
+ perms &= ~member_overwrite->Deny;
+ perms |= member_overwrite->Allow;
+ }
+
+ return perms;
+}
+
void DiscordClient::SendChatMessage(std::string content, Snowflake channel) {
// @([^@#]{1,32})#(\\d{4})
CreateMessageObject obj;
diff --git a/discord/discord.hpp b/discord/discord.hpp
index cda3854..f9af928 100644
--- a/discord/discord.hpp
+++ b/discord/discord.hpp
@@ -77,8 +77,15 @@ public:
const User *GetUser(Snowflake id) const;
const Role *GetRole(Snowflake id) const;
const Guild *GetGuild(Snowflake id) const;
+ const GuildMember *GetMember(Snowflake user_id, Snowflake guild_id) const;
Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const;
std::unordered_set<Snowflake> GetUsersInGuild(Snowflake id) const;
+ std::unordered_set<Snowflake> GetRolesInGuild(Snowflake id) const;
+
+ bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const;
+ bool HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const;
+ Permission ComputePermissions(Snowflake member_id, Snowflake guild_id) const;
+ Permission ComputeOverwrites(Permission base, Snowflake member_id, Snowflake channel_id) const;
void SendChatMessage(std::string content, Snowflake channel);
void DeleteMessage(Snowflake channel_id, Snowflake id);
diff --git a/discord/objects.hpp b/discord/objects.hpp
index e2526fe..37a79b7 100644
--- a/discord/objects.hpp
+++ b/discord/objects.hpp
@@ -12,6 +12,7 @@
#include "usersettings.hpp"
#include "message.hpp"
#include "invite.hpp"
+#include "permissions.hpp"
// most stuff below should just be objects that get processed and thrown away immediately
diff --git a/discord/permissions.cpp b/discord/permissions.cpp
new file mode 100644
index 0000000..3f269a0
--- /dev/null
+++ b/discord/permissions.cpp
@@ -0,0 +1,12 @@
+#include "permissions.hpp"
+
+void from_json(const nlohmann::json &j, PermissionOverwrite &m) {
+ JS_D("id", m.ID);
+ std::string tmp;
+ JS_D("type", tmp);
+ m.ID = tmp == "role" ? PermissionOverwrite::ROLE : PermissionOverwrite::MEMBER;
+ JS_D("allow_new", tmp);
+ m.Allow = static_cast<Permission>(std::stoull(tmp));
+ JS_D("deny_new", tmp);
+ m.Deny = static_cast<Permission>(std::stoull(tmp));
+}
diff --git a/discord/permissions.hpp b/discord/permissions.hpp
new file mode 100644
index 0000000..65a0daa
--- /dev/null
+++ b/discord/permissions.hpp
@@ -0,0 +1,60 @@
+#pragma once
+#include <cstdint>
+#include "snowflake.hpp"
+#include "json.hpp"
+#include "../util.hpp"
+
+enum class Permission {
+ NONE = 0,
+ CREATE_INSTANT_INVITE = (1 << 0),
+ KICK_MEMBERS = (1 << 1),
+ BAN_MEMBERS = (1 << 2),
+ ADMINISTRATOR = (1 << 3),
+ MANAGE_CHANNELS = (1 << 4),
+ MANAGE_GUILD = (1 << 5),
+ ADD_REACTIONS = (1 << 6),
+ VIEW_AUDIT_LOG = (1 << 7),
+ PRIORITY_SPEAKER = (1 << 8),
+ STREAM = (1 << 9),
+ VIEW_CHANNEL = (1 << 10),
+ SEND_MESSAGES = (1 << 11),
+ SEND_TTS_MESSAGES = (1 << 12),
+ MANAGE_MESSAGES = (1 << 13),
+ EMBED_LINKS = (1 << 14),
+ ATTACH_FILES = (1 << 15),
+ READ_MESSAGE_HISTORY = (1 << 16),
+ MENTION_EVERYONE = (1 << 17),
+ USE_EXTERNAL_EMOJIS = (1 << 18),
+ VIEW_GUILD_INSIGHTS = (1 << 19),
+ CONNECT = (1 << 20),
+ SPEAK = (1 << 21),
+ MUTE_MEMBERS = (1 << 22),
+ DEAFEN_MEMBERS = (1 << 23),
+ MOVE_MEMBERS = (1 << 24),
+ USE_VAD = (1 << 25), // voice activity detection
+ CHANGE_NICKNAME = (1 << 26),
+ MANAGE_NICKNAMES = (1 << 27),
+ MANAGE_ROLES = (1 << 28),
+ MANAGE_WEBHOOKS = (1 << 29),
+ MANAGE_EMOJIS = (1 << 30),
+
+ ALL = 0x7FFFFFFF,
+};
+template<>
+struct Bitwise<Permission> {
+ static const bool enable = true;
+};
+
+struct PermissionOverwrite {
+ enum OverwriteType {
+ ROLE,
+ MEMBER,
+ };
+
+ Snowflake ID;
+ OverwriteType Type;
+ Permission Allow;
+ Permission Deny;
+
+ friend void from_json(const nlohmann::json &j, PermissionOverwrite &m);
+};
diff --git a/discord/role.cpp b/discord/role.cpp
index 9e77f2b..d752f72 100644
--- a/discord/role.cpp
+++ b/discord/role.cpp
@@ -9,7 +9,7 @@ void from_json(const nlohmann::json &j, Role &m) {
JS_D("permissions", m.PermissionsLegacy);
std::string tmp;
JS_D("permissions_new", tmp);
- m.Permissions = std::stoull(tmp);
+ m.Permissions = static_cast<Permission>(std::stoull(tmp));
JS_D("managed", m.IsManaged);
JS_D("mentionable", m.IsMentionable);
}
diff --git a/discord/role.hpp b/discord/role.hpp
index 79f1038..078ac76 100644
--- a/discord/role.hpp
+++ b/discord/role.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "snowflake.hpp"
#include "json.hpp"
+#include "permissions.hpp"
#include <string>
#include <cstdint>
@@ -11,7 +12,7 @@ struct Role {
bool IsHoisted;
int Position;
int PermissionsLegacy;
- uint64_t Permissions;
+ Permission Permissions;
bool IsManaged;
bool IsMentionable;
diff --git a/discord/store.cpp b/discord/store.cpp
index 92ccaa0..b9fcbdb 100644
--- a/discord/store.cpp
+++ b/discord/store.cpp
@@ -130,6 +130,10 @@ const Store::guilds_type &Store::GetGuilds() const {
return m_guilds;
}
+const Store::roles_type &Store::GetRoles() const {
+ return m_roles;
+}
+
void Store::ClearAll() {
m_channels.clear();
m_guilds.clear();
diff --git a/discord/store.hpp b/discord/store.hpp
index 6477b50..1d49e09 100644
--- a/discord/store.hpp
+++ b/discord/store.hpp
@@ -41,6 +41,7 @@ public:
const channels_type &GetChannels() const;
const guilds_type &GetGuilds() const;
+ const roles_type &GetRoles() const;
void ClearAll();
diff --git a/util.hpp b/util.hpp
index 5caa49b..925ace9 100644
--- a/util.hpp
+++ b/util.hpp
@@ -10,6 +10,42 @@
#include <iomanip>
template<typename T>
+struct Bitwise {
+ static const bool enable = false;
+};
+
+template<typename T>
+typename std::enable_if<Bitwise<T>::enable, T>::type operator|(T a, T b) {
+ using x = typename std::underlying_type<T>::type;
+ return static_cast<T>(static_cast<x>(a) | static_cast<x>(b));
+}
+
+template<typename T>
+typename std::enable_if<Bitwise<T>::enable, T>::type operator|=(T &a, T b) {
+ using x = typename std::underlying_type<T>::type;
+ a = static_cast<T>(static_cast<x>(a) | static_cast<x>(b));
+ return a;
+}
+
+template<typename T>
+typename std::enable_if<Bitwise<T>::enable, T>::type operator&(T a, T b) {
+ using x = typename std::underlying_type<T>::type;
+ return static_cast<T>(static_cast<x>(a) & static_cast<x>(b));
+}
+
+template<typename T>
+typename std::enable_if<Bitwise<T>::enable, T>::type operator&=(T &a, T b) {
+ using x = typename std::underlying_type<T>::type;
+ a = static_cast<T>(static_cast<x>(a) & static_cast<x>(b));
+ return a;
+}
+
+template<typename T>
+typename std::enable_if<Bitwise<T>::enable, T>::type operator~(T a) {
+ return static_cast<T>(~static_cast<std::underlying_type<T>::type>(a));
+}
+
+template<typename T>
inline void AlphabeticalSort(T start, T end, std::function<std::string(const typename std::iterator_traits<T>::value_type &)> get_string) {
std::sort(start, end, [&](const auto &a, const auto &b) -> bool {
const std::string &s1 = get_string(a);