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
This commit is contained in:
		| @@ -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 '<Permissions value=%s>' % 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) | ||||
|         return 1 << 0 | ||||
|  | ||||
|     @create_instant_invite.setter | ||||
|     def create_instant_invite(self, value): | ||||
|         self._set(0, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 2 | ||||
|  | ||||
|     @ban_members.setter | ||||
|     def ban_members(self, value): | ||||
|         self._set(2, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 4 | ||||
|  | ||||
|     @manage_channels.setter | ||||
|     def manage_channels(self, value): | ||||
|         self._set(4, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 6 | ||||
|  | ||||
|     @add_reactions.setter | ||||
|     def add_reactions(self, value): | ||||
|         self._set(6, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 8 | ||||
|  | ||||
|     @priority_speaker.setter | ||||
|     def priority_speaker(self, value): | ||||
|         self._set(8, value) | ||||
|  | ||||
|     @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 | ||||
|  | ||||
|     @read_messages.setter | ||||
|     def read_messages(self, value): | ||||
|         self._set(10, value) | ||||
|     @make_permission_alias('read_messages') | ||||
|     def view_channel(self): | ||||
|         """:class:`bool`: An alias for :attr:`read_messages`. | ||||
|  | ||||
|     @property | ||||
|         .. versionadded:: 1.3 | ||||
|         """ | ||||
|         return 1 << 10 | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 11 | ||||
|  | ||||
|     @send_messages.setter | ||||
|     def send_messages(self, value): | ||||
|         self._set(11, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 12 | ||||
|  | ||||
|     @send_tts_messages.setter | ||||
|     def send_tts_messages(self, value): | ||||
|         self._set(12, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 14 | ||||
|  | ||||
|     @embed_links.setter | ||||
|     def embed_links(self, value): | ||||
|         self._set(14, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 16 | ||||
|  | ||||
|     @read_message_history.setter | ||||
|     def read_message_history(self, value): | ||||
|         self._set(16, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 20 | ||||
|  | ||||
|     @connect.setter | ||||
|     def connect(self, value): | ||||
|         self._set(20, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 22 | ||||
|  | ||||
|     @mute_members.setter | ||||
|     def mute_members(self, value): | ||||
|         self._set(22, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 24 | ||||
|  | ||||
|     @move_members.setter | ||||
|     def move_members(self, value): | ||||
|         self._set(24, value) | ||||
|  | ||||
|     @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) | ||||
|         return 1 << 26 | ||||
|  | ||||
|     @change_nickname.setter | ||||
|     def change_nickname(self, value): | ||||
|         self._set(26, value) | ||||
|  | ||||
|     @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 | ||||
|  | ||||
|     @manage_roles.setter | ||||
|     def manage_roles(self, value): | ||||
|         self._set(28, value) | ||||
|     @make_permission_alias('manage_roles') | ||||
|     def manage_permissions(self): | ||||
|         """:class:`bool`: An alias for :attr:`manage_roles`. | ||||
|  | ||||
|     @property | ||||
|         .. versionadded:: 1.3 | ||||
|         """ | ||||
|         return 1 << 28 | ||||
|  | ||||
|     @flag_value | ||||
|     def manage_webhooks(self): | ||||
|         """:class:`bool`: Returns ``True`` if a user can create, edit, or delete webhooks.""" | ||||
|         return self._bit(29) | ||||
|         return 1 << 29 | ||||
|  | ||||
|     @manage_webhooks.setter | ||||
|     def manage_webhooks(self, value): | ||||
|         self._set(29, value) | ||||
|  | ||||
|     @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: | ||||
|     .. container:: 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.                           | | ||||
|     +-----------+------------------------------------------+ | ||||
|         .. 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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user