From 8085ac84f4feec7e0a250bbbbdf07de7ddaecd27 Mon Sep 17 00:00:00 2001 From: Kae Bartlett Date: Wed, 24 Apr 2024 21:38:08 +0100 Subject: [PATCH] Add guildmember permission calculations --- novus/models/channel.py | 32 +++++++++++++++++++ novus/models/guild_member.py | 59 +++++++++++++++++++++++++++++++++--- novus/payloads/channel.py | 2 +- 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/novus/models/channel.py b/novus/models/channel.py index b7688072..7e87c7b4 100644 --- a/novus/models/channel.py +++ b/novus/models/channel.py @@ -443,6 +443,38 @@ def _add_channel(self, channel: Channel) -> None: def _remove_channel(self, id: int | str) -> None: self._channels.pop(try_snowflake(id), None) + def permissions_for(self, user: GuildMember) -> Permissions: + """ + Get the permissions for a given user in this channel. + + .. note:: + + Permissions are only properly calculated when the guild and its + roles are cached (ie when the bot is connected to the gateway). + + Parameters + ---------- + user : novus.GuildMember + The user whose permissions you want to get. + + Returns + ------- + novus.Permissions + The calculated permissions for that user in this channel. + """ + + all = Permissions.all().value + permissions: int = user.permissions.value + for overwrite in self.overwrites or []: + if overwrite.type == PermissionOverwriteType.ROLE: + if overwrite.id in user.role_ids or overwrite.id == self.guild_id: + permissions |= overwrite.allow.value + permissions &= (all ^ overwrite.deny.value) + elif overwrite.type == PermissionOverwriteType.MEMBER: + if overwrite.id == user.id: + permissions |= overwrite.allow.value + permissions &= (all ^ overwrite.deny.value) + return Permissions(permissions) # API methods diff --git a/novus/models/guild_member.py b/novus/models/guild_member.py index c33ad4a1..1a348b0e 100644 --- a/novus/models/guild_member.py +++ b/novus/models/guild_member.py @@ -162,7 +162,7 @@ class GuildMember(Hashable, Messageable): 'deaf', 'mute', 'pending', - 'permissions', + '_permissions', # calculated 'timeout_until', 'guild', '_cs_guild_avatar', @@ -195,7 +195,6 @@ class GuildMember(Hashable, Messageable): deaf: bool mute: bool pending: bool - permissions: Permissions timeout_until: DiscordDatetime | None guild: BaseGuild @@ -249,9 +248,9 @@ def __init__( self.deaf = data.get('deaf', False) self.mute = data.get('mute', False) self.pending = data.get('pending', False) - self.permissions = Permissions.none() + self._permissions = None if "permissions" in data: - self.permissions = Permissions(int(data["permissions"])) + self._permissions = Permissions(int(data["permissions"])) self.timeout_until = None if "communication_disabled_until" in data: self.timeout_until = parse_timestamp(data["communication_disabled_until"]) @@ -318,6 +317,58 @@ def _update(self, data: payloads.GuildMember) -> Self: self.timeout_until = parse_timestamp(data.get("communication_disabled_until")) return self + @property + def permissions(self) -> Permissions: + """ + The calculated permissions for the user based on their roles and the + cached guild. + If permissions were provided (ie this member was created as part of an + interaction payload) then they will not be re-calculated. + + .. note:: + + Permissions are only properly calculated when the guild and its + roles are cached (ie when the bot is connected to the gateway). + """ + + if self._permissions is not None: + return self._permissions + + permissions = Permissions() + + from .guild import Guild + if not isinstance(self.guild, Guild): + return permissions + + for role_id in [self.guild.id] + self.role_ids: + role = self.guild.get_role(role_id) + if role is None: + continue + permissions = Permissions(permissions.value | role.permissions.value) + return permissions + + def permissions_in(self, channel: Channel) -> Permissions: + """ + Get the permissions for this guild member inside of a channel. + + .. note:: + + Permissions are only properly calculated when the guild and its + roles are cached (ie when the bot is connected to the gateway). + + Parameters + ---------- + channel : novus.Channel + The channel that you want to get the user's permissions for. + + Returns + ------- + novus.Permissions + The calculated permissions for this user in that channel. + """ + + return channel.permissions_for(self) + # API methods @classmethod diff --git a/novus/payloads/channel.py b/novus/payloads/channel.py index 612e9f76..6c66fb92 100644 --- a/novus/payloads/channel.py +++ b/novus/payloads/channel.py @@ -34,7 +34,7 @@ class ChannelOverwrite(TypedDict): id: Snowflake - type: Literal[0, 1, "role", "channel"] + type: int allow: str deny: str