Source code for curious.dataclasses.permissions

# This file is part of curious.
#
# curious is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# curious is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with curious.  If not, see <http://www.gnu.org/licenses/>.

"""
Wrappers for Permission objects.

This class uses some automatic generation to create the objects.

.. currentmodule:: curious.dataclasses.permissions
"""
import typing

from curious.dataclasses import channel as dt_channel, member as dt_member, role as dt_role
from curious.exc import PermissionsError


# I'm far too lazy to type out each permission bit manually.
# So here's a helper method.
[docs]def build_permissions_class(name: str = "Permissions") -> type: """ Builds the permissions class automagically. This should ***not*** be used by normal user code - it is designed for internal usage by curious. :param name: The name of the class. :return: A new type representing the permissions class. """ # Closure methods. __doc__ = """ Represents the permissions a user can have. This type is automatically generated based upon a set of constant permission bits. Every permission is accessible via a property getter and setter. The raw permissions value is accessible via ``bitfield``. """ def __init__(self, value: int = 0, **kwargs): """ Creates a new Permissions object. :param value: The bitfield value of the permissions object. """ self.bitfield = value for perm, value in kwargs.items(): if perm not in permissions: raise ValueError("Unknown permission", perm) setattr(self, perm, value) def __new__(cls, value: int = 0, **kwargs): if isinstance(value, cls): return value return super(Permissions, cls).__new__(cls) def _get_bit(self, bit: int) -> bool: """ Gets a bit from the internal bitfield of the permissions. """ return bool((self.bitfield >> bit) & 1) def _set_bit(self, bit: int, value: bool): if value: self.bitfield |= (1 << bit) else: self.bitfield &= ~(1 << bit) # Operator overloads. def __eq__(self, other): return self.bitfield == other.bitfield # This is a dict because discord skips some permissions. permissions = { "create_instant_invite": 0, "kick_members": 1, "ban_members": 2, "administrator": 3, "manage_channels": 4, "manage_server": 5, "add_reactions": 6, "view_audit_log": 7, "read_messages": 10, "send_messages": 11, "send_tts_messages": 12, "manage_messages": 13, "embed_links": 14, "attach_files": 15, "read_message_history": 16, "mention_everyone": 17, "use_external_emojis": 18, "voice_connect": 20, "voice_speak": 21, "voice_mute_members": 22, "voice_deafen_members": 23, "voice_move_members": 24, "voice_use_voice_activation": 25, "change_nickname": 26, "manage_nicknames": 27, "manage_roles": 28, "manage_webhooks": 29, "manage_emojis": 30, # rest are unused } # Create a bunch of property objects for each permission. def _get_permission_getter(name: str, bit: int): def _junk_function(self) -> bool: return self._get_bit(bit) _junk_function.__name__ = name return _junk_function def _get_permission_setter(name: str, bit: int): def _junk_function(self, value: bool): return self._set_bit(bit, value) _junk_function.__name__ = name return _junk_function _doc_base = ":return: If this member has the {} permission (bit {})." properties = { name: property(fget=_get_permission_getter(name, bit), fset=_get_permission_setter(name, bit), doc=_doc_base.format(name, bit)) for (name, bit) in permissions.items() } def raise_for_permission(self, permission: typing.Union[str]) -> None: """ Raises :class:`.PermissionsError` if this permission does not have the required bit. """ if not getattr(self, permission): raise PermissionsError(permission) # Create some useful classmethods. @classmethod def all(cls): """ :return: A new Permissions object with all permissions. """ return cls(9007199254740991) @classmethod def none(cls): """ :return: A new permissions object with no permissions. """ return cls(0) # Create the namespace dict to use in the type declaration. namespace = { "__init__": __init__, "__new__": __new__, "_set_bit": _set_bit, "_get_bit": _get_bit, "__eq__": __eq__, "__repr__": lambda self: "<Permissions value={}>".format(self.bitfield), "all": all, "none": none, "raise_for_permission": raise_for_permission, "__slots__": ("bitfield",), **properties } new_class = type(name, (object,), namespace) new_class.__doc__ = __doc__ return new_class
Permissions = build_permissions_class("Permissions")
[docs]class Overwrite(object): """ Represents a permission overwrite. This has all properties that the base Permissions object, but it takes into accounts the overwrites for the channels. It is always recommended to use this over the server permissions, as it will fall back to the default permissions for the role if it can't find specific overwrites. The overwrite has a permission marked as ``True`` if the object has a) an overwrite on the channel OR b) the object has that permission and no overwrite. The overwrite is marked as ``False`` if the object has a) an overwrite on the channel OR b) the object does not have that permission and no overwrite/a deny overwrite. You can set an attribute to None to clear the overwrite, True to set an allow overwrite, and False to set a deny overwrite. """ __slots__ = "target", "channel_id", "allow", "deny" def __init__(self, allow: typing.Union[int, Permissions], deny: typing.Union[int, Permissions], obb: 'typing.Union[dt_member.Member, dt_role.Role]' = None, channel_id: int = None): """ :param allow: A :class:`.Permissions` that this overwrite allows. :param deny: A :class:`.Permissions` that this overwrite denies. :param obb: Optional: The :class:`.Member` or :class:`.Role` that this overwrite is for. :param channel_id: Optional: The channel ID this overwrite is in. """ self.target = obb self.channel_id = channel_id if isinstance(allow, Permissions): allow = allow.bitfield self.allow = Permissions(value=allow if allow is not None else 0) if isinstance(deny, Permissions): deny = deny.bitfield self.deny = Permissions(value=deny if deny is not None else 0) @property def channel(self) -> 'typing.Union[dt_channel.Channel, None]': """ :return: The :class:`.Channel` this overwrite represents. """ def __repr__(self) -> str: return "<Overwrites for object={} channel={} allow={} deny={}>".format(self.target, self.channel, self.allow, self.deny) def __getattr__(self, item) -> bool: """ Attribute getter helper. This will check allow first, the deny, then finally the role permissions. """ if isinstance(self.target, dt_member.Member): permissions = self.target.guild_permissions elif isinstance(self.target, dt_role.Role): permissions = self.target.permissions else: raise TypeError("Target must be a member or a role") if permissions.administrator: # short-circuit to always return True if they have administrator # this is because those overrides are useless # if the user wants to get the override, they can access `allow/deny` directly. return True if not hasattr(self.allow, item): raise AttributeError(item) if getattr(self.allow, item, None) is True: return True if getattr(self.deny, item, None) is True: # Return False because it's denied. return False return getattr(permissions, item, False) def __setattr__(self, key: str, value: object) -> object: """ Attribute setter helper. """ if not hasattr(Permissions, key): super().__setattr__(key, value) return if value is False: setattr(self.deny, key, True) elif value is True: setattr(self.allow, key, True) elif value is None: setattr(self.allow, key, False) setattr(self.deny, key, False)