aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRapptz <[email protected]>2020-01-11 19:50:49 -0500
committerRapptz <[email protected]>2020-01-11 19:52:25 -0500
commit2770137bd6d53dbfbeb325f74f302cf1ffd26f99 (patch)
tree187af635835106f2fee4011d6b3336aaaa47aead
parent[commands] Document guild permission checks (diff)
downloaddiscord.py-2770137bd6d53dbfbeb325f74f302cf1ffd26f99.tar.xz
discord.py-2770137bd6d53dbfbeb325f74f302cf1ffd26f99.zip
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
-rw-r--r--discord/permissions.py397
1 files changed, 151 insertions, 246 deletions
diff --git a/discord/permissions.py b/discord/permissions.py
index 1c814b0e..c573573d 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 '<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)
-
- @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)