Browse Source

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
pull/2504/head
Rapptz 5 years ago
parent
commit
2770137bd6
  1. 397
      discord/permissions.py

397
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)

Loading…
Cancel
Save