aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRapptz <[email protected]>2015-10-17 06:21:51 -0400
committerRapptz <[email protected]>2015-10-17 06:21:51 -0400
commit61f62c146808838e0e721c898888fbab179bb2fb (patch)
tree57b4cdfdac8e354e3db79c888392feccb5164ceb
parentFix bug with member voice state update. (diff)
downloaddiscord.py-61f62c146808838e0e721c898888fbab179bb2fb.tar.xz
discord.py-61f62c146808838e0e721c898888fbab179bb2fb.zip
Add Channel.permissions_for and PrivateChannel.permissions_for.
These functions handle permission resolution for a specific member. Aids with #18.
-rw-r--r--discord/channel.py117
-rw-r--r--discord/permissions.py25
2 files changed, 128 insertions, 14 deletions
diff --git a/discord/channel.py b/discord/channel.py
index ee276f58..3f8b566b 100644
--- a/discord/channel.py
+++ b/discord/channel.py
@@ -25,6 +25,10 @@ DEALINGS IN THE SOFTWARE.
from copy import deepcopy
from . import utils
+from .permissions import Permissions
+from collections import namedtuple
+
+MemberOverwrite = namedtuple('MemberOverwrite', ['id', 'allow', 'deny'])
class Channel(object):
"""Represents a Discord server channel.
@@ -75,7 +79,13 @@ class Channel(object):
self.position = kwargs.get('position')
self.type = kwargs.get('type')
self.changed_roles = []
+ self._user_permissions = []
for overridden in kwargs.get('permission_overwrites', []):
+ if overridden.get('type') == 'member':
+ del overridden['type']
+ self._user_permissions.append(MemberOverwrite(**overridden))
+ continue
+
# this is pretty inefficient due to the deep nested loops unfortunately
role = utils.find(lambda r: r.id == overridden['id'], self.server.roles)
if role is None:
@@ -84,22 +94,77 @@ class Channel(object):
denied = overridden.get('deny', 0)
allowed = overridden.get('allow', 0)
override = deepcopy(role)
-
- # Basically this is what's happening here.
- # We have an original bit array, e.g. 1010
- # Then we have another bit array that is 'denied', e.g. 1111
- # And then we have the last one which is 'allowed', e.g. 0101
- # We want original OP denied to end up resulting in
- # whatever is in denied to be set to 0.
- # So 1010 OP 1111 -> 0000
- # Then we take this value and look at the allowed values.
- # And whatever is allowed is set to 1.
- # So 0000 OP2 0101 -> 0101
- # The OP is (base ^ denied) & ~denied.
- # The OP2 is base | allowed.
- override.permissions.value = ((override.permissions.value ^ denied) & ~denied) | allowed
+ override.permissions.handle_overwrite(allowed, denied)
self.changed_roles.append(override)
+ def is_default_channel(self):
+ """Checks if this is the default channel for the :class:`Server` it belongs to."""
+ return self.server.id == self.id
+
+ def permissions_for(self, member):
+ """Handles permission resolution for the current :class:`Member`.
+
+ This function takes into consideration the following cases:
+
+ - Server owner
+ - Server roles
+ - Channel overrides
+ - Member overrides
+ - Whether the channel is the default channel.
+
+ :param member: The :class:`Member` to resolve permissions for.
+ :return: The resolved :class:`Permissions` for the :class:`Member`.
+ """
+
+ # The current cases can be explained as:
+ # Server owner get all permissions -- no questions asked. Otherwise...
+ # The @everyone role gets the first application.
+ # After that, the applied roles that the user has in the channel
+ # (or otherwise) are then OR'd together.
+ # After the role permissions are resolved, the member permissions
+ # have to take into effect.
+ # After all that is done.. you have to do the following:
+
+ # If manage permissions is True, then all permissions are set to
+ # True. If the channel is the default channel then everyone gets
+ # read permissions regardless.
+
+ # The operation first takes into consideration the denied
+ # and then the allowed.
+
+ if member.id == self.server.owner.id:
+ return Permissions.ALL
+
+ base = self.server.get_default_role().permissions
+
+ # Apply server roles that the member has.
+ for role in member.roles:
+ denied = ~role.permissions.value
+ base.handle_overwrite(allow=role.permissions.value, deny=denied)
+
+ # Server-wide Manage Roles -> True for everything
+ if base.can_manage_roles:
+ base = Permissions.ALL
+
+ # Apply channel specific permission overwrites
+ for role in self.changed_roles:
+ denied = ~role.permissions.value
+ base.handle_overwrite(allow=role.permissions.value, deny=denied)
+
+ # Apply member specific permission overwrites
+ for overwrite in self._user_permissions:
+ if overwrite.id == member.id:
+ base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny)
+
+ if base.can_manage_roles:
+ # This point is essentially Channel-specific Manage Roles.
+ base.value |= Permissions.ALL_CHANNEL.value
+
+ if self.is_default_channel():
+ base.can_read_messages = True
+
+ return base
+
class PrivateChannel(object):
"""Represents a Discord private channel.
@@ -121,3 +186,27 @@ class PrivateChannel(object):
self.id = id
self.is_private = True
+ def permissions_for(user):
+ """Handles permission resolution for a :class:`User`.
+
+ This function is there for compatibility with :class:`Channel`.
+
+ Actual private messages do not really have the concept of permissions.
+
+ This returns all the Text related permissions set to true except:
+
+ - can_send_tts_messages: You cannot send TTS messages in a PM.
+ - can_manage_messages: You cannot delete others messages in a PM.
+ - can_mention_everyone: There is no one to mention in a PM.
+
+ :param user: The :class:`User` to check permissions for.
+ :return: A :class:`Permission` with the resolved permission value.
+ """
+
+ base = Permissions.TEXT
+ base.can_send_tts_messages = False
+ base.can_manage_messages = False
+ base.can_mention_everyone = False
+ return base
+
+
diff --git a/discord/permissions.py b/discord/permissions.py
index 43cfcef3..1b58bf54 100644
--- a/discord/permissions.py
+++ b/discord/permissions.py
@@ -24,6 +24,16 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
+def create_permission_masks(cls):
+ cls.NONE = cls(0)
+ cls.ALL = cls(0b00000011111100111111110000111111)
+ cls.ALL_CHANNEL = cls(0b00000011111100111111110000011001)
+ cls.GENERAL = cls(0b00000000000000000000000000111111)
+ cls.TEXT = cls(0b00000000000000111111110000000000)
+ cls.VOICE = cls(0b00000011111100000000000000000000)
+ return cls
+
+@create_permission_masks
class Permissions(object):
"""Wraps up the Discord permission value.
@@ -53,6 +63,21 @@ class Permissions(object):
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
+ # Then we have another bit array that is 'denied', e.g. 1111
+ # And then we have the last one which is 'allowed', e.g. 0101
+ # We want original OP denied to end up resulting in
+ # whatever is in denied to be set to 0.
+ # So 1010 OP 1111 -> 0000
+ # Then we take this value and look at the allowed values.
+ # And whatever is allowed is set to 1.
+ # So 0000 OP2 0101 -> 0101
+ # The OP is (base ^ denied) & ~denied.
+ # The OP2 is base | allowed.
+ self.value = ((self.value ^ deny) & ~deny) | allow
+
@property
def can_create_instant_invite(self):
"""Returns True if the user can create instant invites."""