From 2770137bd6d53dbfbeb325f74f302cf1ffd26f99 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sat, 11 Jan 2020 19:50:49 -0500 Subject: [PATCH] Redesign permissions to allow aliases to be used. This adds manage_permissions, view_channel, and use_external_emojis aliases to Permissions. It should be noted that to prevent breaking changes and for sake of usefulness, aliases are not included in the `__iter__` for either Permissions or PermissionOverwrite. Fixes #2496 --- discord/permissions.py | 397 ++++++++++++++++------------------------- 1 file changed, 151 insertions(+), 246 deletions(-) diff --git a/discord/permissions.py b/discord/permissions.py index 1c814b0e1..c573573da 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -24,7 +24,27 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -class Permissions: +from .flags import BaseFlags, flag_value, fill_with_flags + +__all__ = ( + 'Permissions', + 'PermissionOverwrite', +) + +# A permission alias works like a regular flag but is marked +# So the PermissionOverwrite knows to work with it +class permission_alias(flag_value): + pass + +def make_permission_alias(alias): + def decorator(func): + ret = permission_alias(func) + ret.alias = alias + return ret + return decorator + +@fill_with_flags() +class Permissions(BaseFlags): """Wraps up the Discord permission value. The properties provided are two way. You can set and retrieve individual @@ -58,6 +78,7 @@ class Permissions: Returns an iterator of ``(perm, value)`` pairs. This allows it to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. Attributes ----------- @@ -67,34 +88,17 @@ class Permissions: permissions via the properties rather than using this raw value. """ - __slots__ = ('value',) - def __init__(self, permissions=0): + __slots__ = () + + def __init__(self, permissions=0, **kwargs): if not isinstance(permissions, int): raise TypeError('Expected int parameter, received %s instead.' % permissions.__class__.__name__) self.value = permissions - - def __eq__(self, other): - return isinstance(other, Permissions) and self.value == other.value - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - return hash(self.value) - - def __repr__(self): - return '' % self.value - - def _perm_iterator(self): - for attr in dir(self): - # check if it's a property, because if so it's a permission - is_property = isinstance(getattr(self.__class__, attr), property) - if is_property: - yield (attr, getattr(self, attr)) - - def __iter__(self): - return self._perm_iterator() + for key, value in kwargs.items(): + if key not in self.VALID_FLAGS: + raise TypeError('%r is not a valid permission name.' % key) + setattr(self, key, value) def is_subset(self, other): """Returns ``True`` if self has the same or fewer permissions as other.""" @@ -123,6 +127,14 @@ class Permissions: __lt__ = is_strict_subset __gt__ = is_strict_superset + def __iter__(self): + for name, value in self.__class__.__dict__.items(): + if isinstance(value, permission_alias): + continue + + if isinstance(value, flag_value): + yield (name, self._has_flag(value.flag)) + @classmethod def none(cls): """A factory method that creates a :class:`Permissions` with all @@ -182,25 +194,9 @@ class Permissions: A list of key/value pairs to bulk update permissions with. """ for key, value in kwargs.items(): - try: - is_property = isinstance(getattr(self.__class__, key), property) - except AttributeError: - continue - - if is_property: + if key in self.VALID_FLAGS: setattr(self, key, value) - def _bit(self, index): - return bool((self.value >> index) & 1) - - def _set(self, index, value): - if value is True: - self.value |= (1 << index) - elif value is False: - self.value &= ~(1 << index) - else: - raise TypeError('Value to set for Permissions must be a bool.') - def handle_overwrite(self, allow, deny): # Basically this is what's happening here. # We have an original bit array, e.g. 1010 @@ -216,129 +212,85 @@ class Permissions: # The OP2 is base | allowed. self.value = (self.value & ~deny) | allow - @property + @flag_value def create_instant_invite(self): """:class:`bool`: Returns ``True`` if the user can create instant invites.""" - return self._bit(0) - - @create_instant_invite.setter - def create_instant_invite(self, value): - self._set(0, value) + return 1 << 0 - @property + @flag_value def kick_members(self): """:class:`bool`: Returns ``True`` if the user can kick users from the guild.""" - return self._bit(1) + return 1 << 1 - @kick_members.setter - def kick_members(self, value): - self._set(1, value) - - @property + @flag_value def ban_members(self): """:class:`bool`: Returns ``True`` if a user can ban users from the guild.""" - return self._bit(2) - - @ban_members.setter - def ban_members(self, value): - self._set(2, value) + return 1 << 2 - @property + @flag_value def administrator(self): """:class:`bool`: Returns ``True`` if a user is an administrator. This role overrides all other permissions. This also bypasses all channel-specific overrides. """ - return self._bit(3) + return 1 << 3 - @administrator.setter - def administrator(self, value): - self._set(3, value) - - @property + @flag_value def manage_channels(self): """:class:`bool`: Returns ``True`` if a user can edit, delete, or create channels in the guild. This also corresponds to the "Manage Channel" channel-specific override.""" - return self._bit(4) - - @manage_channels.setter - def manage_channels(self, value): - self._set(4, value) + return 1 << 4 - @property + @flag_value def manage_guild(self): """:class:`bool`: Returns ``True`` if a user can edit guild properties.""" - return self._bit(5) + return 1 << 5 - @manage_guild.setter - def manage_guild(self, value): - self._set(5, value) - - @property + @flag_value def add_reactions(self): """:class:`bool`: Returns ``True`` if a user can add reactions to messages.""" - return self._bit(6) - - @add_reactions.setter - def add_reactions(self, value): - self._set(6, value) + return 1 << 6 - @property + @flag_value def view_audit_log(self): """:class:`bool`: Returns ``True`` if a user can view the guild's audit log.""" - return self._bit(7) + return 1 << 7 - @view_audit_log.setter - def view_audit_log(self, value): - self._set(7, value) - - @property + @flag_value def priority_speaker(self): """:class:`bool`: Returns ``True`` if a user can be more easily heard while talking.""" - return self._bit(8) - - @priority_speaker.setter - def priority_speaker(self, value): - self._set(8, value) + return 1 << 8 - @property + @flag_value def stream(self): """:class:`bool`: Returns ``True`` if a user can stream in a voice channel.""" - return self._bit(9) + return 1 << 9 - @stream.setter - def stream(self, value): - self._set(9, value) - - @property + @flag_value def read_messages(self): """:class:`bool`: Returns ``True`` if a user can read messages from all or specific text channels.""" - return self._bit(10) + return 1 << 10 + + @make_permission_alias('read_messages') + def view_channel(self): + """:class:`bool`: An alias for :attr:`read_messages`. - @read_messages.setter - def read_messages(self, value): - self._set(10, value) + .. versionadded:: 1.3 + """ + return 1 << 10 - @property + @flag_value def send_messages(self): """:class:`bool`: Returns ``True`` if a user can send messages from all or specific text channels.""" - return self._bit(11) - - @send_messages.setter - def send_messages(self, value): - self._set(11, value) + return 1 << 11 - @property + @flag_value def send_tts_messages(self): """:class:`bool`: Returns ``True`` if a user can send TTS messages from all or specific text channels.""" - return self._bit(12) - - @send_tts_messages.setter - def send_tts_messages(self, value): - self._set(12, value) + return 1 << 12 - @property + @flag_value def manage_messages(self): """:class:`bool`: Returns ``True`` if a user can delete or pin messages in a text channel. @@ -346,189 +298,143 @@ class Permissions: Note that there are currently no ways to edit other people's messages. """ - return self._bit(13) + return 1 << 13 - @manage_messages.setter - def manage_messages(self, value): - self._set(13, value) - - @property + @flag_value def embed_links(self): """:class:`bool`: Returns ``True`` if a user's messages will automatically be embedded by Discord.""" - return self._bit(14) - - @embed_links.setter - def embed_links(self, value): - self._set(14, value) + return 1 << 14 - @property + @flag_value def attach_files(self): """:class:`bool`: Returns ``True`` if a user can send files in their messages.""" - return self._bit(15) + return 1 << 15 - @attach_files.setter - def attach_files(self, value): - self._set(15, value) - - @property + @flag_value def read_message_history(self): """:class:`bool`: Returns ``True`` if a user can read a text channel's previous messages.""" - return self._bit(16) - - @read_message_history.setter - def read_message_history(self, value): - self._set(16, value) + return 1 << 16 - @property + @flag_value def mention_everyone(self): """:class:`bool`: Returns ``True`` if a user's @everyone or @here will mention everyone in the text channel.""" - return self._bit(17) + return 1 << 17 - @mention_everyone.setter - def mention_everyone(self, value): - self._set(17, value) - - @property + @flag_value def external_emojis(self): """:class:`bool`: Returns ``True`` if a user can use emojis from other guilds.""" - return self._bit(18) + return 1 << 18 - @external_emojis.setter - def external_emojis(self, value): - self._set(18, value) + @make_permission_alias('external_emojis') + def use_external_emojis(self): + """:class:`bool`: An alias for :attr:`external_emojis`. - @property + .. versionadded:: 1.3 + """ + return 1 << 18 + + @flag_value def view_guild_insights(self): """:class:`bool`: Returns ``True`` if a user can view the guild's insights. - + .. versionadded:: 1.3.0 """ - return self._bit(19) + return 1 << 19 - @view_guild_insights.setter - def view_guild_insights(self, value): - self._set(19, value) - - @property + @flag_value def connect(self): """:class:`bool`: Returns ``True`` if a user can connect to a voice channel.""" - return self._bit(20) - - @connect.setter - def connect(self, value): - self._set(20, value) + return 1 << 20 - @property + @flag_value def speak(self): """:class:`bool`: Returns ``True`` if a user can speak in a voice channel.""" - return self._bit(21) + return 1 << 21 - @speak.setter - def speak(self, value): - self._set(21, value) - - @property + @flag_value def mute_members(self): """:class:`bool`: Returns ``True`` if a user can mute other users.""" - return self._bit(22) - - @mute_members.setter - def mute_members(self, value): - self._set(22, value) + return 1 << 22 - @property + @flag_value def deafen_members(self): """:class:`bool`: Returns ``True`` if a user can deafen other users.""" - return self._bit(23) + return 1 << 23 - @deafen_members.setter - def deafen_members(self, value): - self._set(23, value) - - @property + @flag_value def move_members(self): """:class:`bool`: Returns ``True`` if a user can move users between other voice channels.""" - return self._bit(24) - - @move_members.setter - def move_members(self, value): - self._set(24, value) + return 1 << 24 - @property + @flag_value def use_voice_activation(self): """:class:`bool`: Returns ``True`` if a user can use voice activation in voice channels.""" - return self._bit(25) + return 1 << 25 - @use_voice_activation.setter - def use_voice_activation(self, value): - self._set(25, value) - - @property + @flag_value def change_nickname(self): """:class:`bool`: Returns ``True`` if a user can change their nickname in the guild.""" - return self._bit(26) - - @change_nickname.setter - def change_nickname(self, value): - self._set(26, value) + return 1 << 26 - @property + @flag_value def manage_nicknames(self): """:class:`bool`: Returns ``True`` if a user can change other user's nickname in the guild.""" - return self._bit(27) + return 1 << 27 - @manage_nicknames.setter - def manage_nicknames(self, value): - self._set(27, value) - - @property + @flag_value def manage_roles(self): """:class:`bool`: Returns ``True`` if a user can create or edit roles less than their role's position. This also corresponds to the "Manage Permissions" channel-specific override. """ - return self._bit(28) + return 1 << 28 + + @make_permission_alias('manage_roles') + def manage_permissions(self): + """:class:`bool`: An alias for :attr:`manage_roles`. - @manage_roles.setter - def manage_roles(self, value): - self._set(28, value) + .. versionadded:: 1.3 + """ + return 1 << 28 - @property + @flag_value def manage_webhooks(self): """:class:`bool`: Returns ``True`` if a user can create, edit, or delete webhooks.""" - return self._bit(29) - - @manage_webhooks.setter - def manage_webhooks(self, value): - self._set(29, value) + return 1 << 29 - @property + @flag_value def manage_emojis(self): """:class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis.""" - return self._bit(30) - - @manage_emojis.setter - def manage_emojis(self, value): - self._set(30, value) + return 1 << 30 # 1 unused # after these 32 bits, there's 21 more unused ones technically def augment_from_permissions(cls): - cls.VALID_NAMES = {name for name in dir(Permissions) if isinstance(getattr(Permissions, name), property)} + cls.VALID_NAMES = set(Permissions.VALID_FLAGS) + aliases = set() + + # make descriptors for all the valid names and aliases + for name, value in Permissions.__dict__.items(): + if isinstance(value, permission_alias): + key = value.alias + aliases.add(name) + elif isinstance(value, flag_value): + key = name + else: + continue - # make descriptors for all the valid names - for name in cls.VALID_NAMES: # god bless Python - def getter(self, x=name): + def getter(self, x=key): return self._values.get(x) - def setter(self, value, x=name): + def setter(self, value, x=key): self._set(x, value) prop = property(getter, setter) setattr(cls, name, prop) + cls.PURE_FLAGS = cls.VALID_NAMES - aliases return cls @augment_from_permissions @@ -544,20 +450,19 @@ class PermissionOverwrite: The values supported by this are the same as :class:`Permissions` with the added possibility of it being set to ``None``. - Supported operations: - - +-----------+------------------------------------------+ - | Operation | Description | - +===========+==========================================+ - | x == y | Checks if two overwrites are equal. | - +-----------+------------------------------------------+ - | x != y | Checks if two overwrites are not equal. | - +-----------+------------------------------------------+ - | iter(x) | Returns an iterator of (perm, value) | - | | pairs. This allows this class to be used | - | | as an iterable in e.g. set/list/dict | - | | constructions. | - +-----------+------------------------------------------+ + .. container:: operations + + .. describe:: x == y + + Checks if two overwrites are equal. + .. describe:: x != y + + Checks if two overwrites are not equal. + .. describe:: iter(x) + + Returns an iterator of ``(perm, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. Parameters ----------- @@ -643,5 +548,5 @@ class PermissionOverwrite: setattr(self, key, value) def __iter__(self): - for key in self.VALID_NAMES: + for key in self.PURE_FLAGS: yield key, self._values.get(key)