aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--discord/abc.py301
-rw-r--r--discord/channel.py267
2 files changed, 266 insertions, 302 deletions
diff --git a/discord/abc.py b/discord/abc.py
index 81bb4105..e2ae12e2 100644
--- a/discord/abc.py
+++ b/discord/abc.py
@@ -29,16 +29,13 @@ import io
import os
import asyncio
+from collections import namedtuple
+
from .message import Message
from .iterators import LogsFromIterator
from .context_managers import Typing
from .errors import ClientException, NoMoreMessages
-import discord.message
-import discord.iterators
-import discord.context_managers
-import discord.errors
-
class Snowflake(metaclass=abc.ABCMeta):
__slots__ = ()
@@ -89,38 +86,6 @@ class User(metaclass=abc.ABCMeta):
return True
return NotImplemented
-class GuildChannel(metaclass=abc.ABCMeta):
- __slots__ = ()
-
- @property
- @abc.abstractmethod
- def mention(self):
- raise NotImplementedError
-
- @abc.abstractmethod
- def overwrites_for(self, obj):
- raise NotImplementedError
-
- @abc.abstractmethod
- def permissions_for(self, user):
- raise NotImplementedError
-
- @classmethod
- def __subclasshook__(cls, C):
- if cls is GuildChannel:
- if Snowflake.__subclasshook__(C) is NotImplemented:
- return NotImplemented
-
- mro = C.__mro__
- for attr in ('name', 'guild', 'overwrites_for', 'permissions_for', 'mention'):
- for base in mro:
- if attr in base.__dict__:
- break
- else:
- return NotImplemented
- return True
- return NotImplemented
-
class PrivateChannel(metaclass=abc.ABCMeta):
__slots__ = ()
@@ -137,6 +102,268 @@ class PrivateChannel(metaclass=abc.ABCMeta):
return NotImplemented
return NotImplemented
+_Overwrites = namedtuple('_Overwrites', 'id allow deny type')
+
+class GuildChannel:
+ __slots__ = ()
+
+ def __str__(self):
+ return self.name
+
+ @asyncio.coroutine
+ def _move(self, position):
+ if position < 0:
+ raise InvalidArgument('Channel position cannot be less than 0.')
+
+ http = self._state.http
+ url = '{0}/{1.guild.id}/channels'.format(http.GUILDS, self)
+ channels = [c for c in self.guild.channels if isinstance(c, type(self))]
+
+ if position >= len(channels):
+ raise InvalidArgument('Channel position cannot be greater than {}'.format(len(channels) - 1))
+
+ channels.sort(key=lambda c: c.position)
+
+ try:
+ # remove ourselves from the channel list
+ channels.remove(self)
+ except ValueError:
+ # not there somehow lol
+ return
+ else:
+ # add ourselves at our designated position
+ channels.insert(position, self)
+
+ payload = [{'id': c.id, 'position': index } for index, c in enumerate(channels)]
+ yield from http.patch(url, json=payload, bucket='move_channel')
+
+ def _fill_overwrites(self, data):
+ self._overwrites = []
+ everyone_index = 0
+ everyone_id = self.guild.id
+
+ for index, overridden in enumerate(data.get('permission_overwrites', [])):
+ overridden_id = int(overridden.pop('id'))
+ self._overwrites.append(_Overwrites(id=overridden_id, **overridden))
+
+ if overridden['type'] == 'member':
+ continue
+
+ if overridden_id == everyone_id:
+ # the @everyone role is not guaranteed to be the first one
+ # in the list of permission overwrites, however the permission
+ # resolution code kind of requires that it is the first one in
+ # the list since it is special. So we need the index so we can
+ # swap it to be the first one.
+ everyone_index = index
+
+ # do the swap
+ tmp = self._overwrites
+ if tmp:
+ tmp[everyone_index], tmp[0] = tmp[0], tmp[everyone_index]
+
+ @property
+ def changed_roles(self):
+ """Returns a list of :class:`Roles` that have been overridden from
+ their default values in the :attr:`Guild.roles` attribute."""
+ ret = []
+ for overwrite in filter(lambda o: o.type == 'role', self._overwrites):
+ role = discord.utils.get(self.guild.roles, id=overwrite.id)
+ if role is None:
+ continue
+
+ role = copy.copy(role)
+ role.permissions.handle_overwrite(overwrite.allow, overwrite.deny)
+ ret.append(role)
+ return ret
+
+ @property
+ def is_default(self):
+ """bool : Indicates if this is the default channel for the :class:`Guild` it belongs to."""
+ return self.guild.id == self.id
+
+ @property
+ def mention(self):
+ """str : The string that allows you to mention the channel."""
+ return '<#{0.id}>'.format(self)
+
+ @property
+ def created_at(self):
+ """Returns the channel's creation time in UTC."""
+ return discord.utils.snowflake_time(self.id)
+
+ def overwrites_for(self, obj):
+ """Returns the channel-specific overwrites for a member or a role.
+
+ Parameters
+ -----------
+ obj
+ The :class:`Role` or :class:`Member` or :class:`Object` denoting
+ whose overwrite to get.
+
+ Returns
+ ---------
+ :class:`PermissionOverwrite`
+ The permission overwrites for this object.
+ """
+
+ if isinstance(obj, Member):
+ predicate = lambda p: p.type == 'member'
+ elif isinstance(obj, Role):
+ predicate = lambda p: p.type == 'role'
+ else:
+ predicate = lambda p: True
+
+ for overwrite in filter(predicate, self._overwrites):
+ if overwrite.id == obj.id:
+ allow = Permissions(overwrite.allow)
+ deny = Permissions(overwrite.deny)
+ return PermissionOverwrite.from_pair(allow, deny)
+
+ return PermissionOverwrite()
+
+ @property
+ def overwrites(self):
+ """Returns all of the channel's overwrites.
+
+ This is returned as a list of two-element tuples containing the target,
+ which can be either a :class:`Role` or a :class:`Member` and the overwrite
+ as the second element as a :class:`PermissionOverwrite`.
+
+ Returns
+ --------
+ List[Tuple[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`]]:
+ The channel's permission overwrites.
+ """
+ ret = []
+ for ow in self._permission_overwrites:
+ allow = Permissions(ow.allow)
+ deny = Permissions(ow.deny)
+ overwrite = PermissionOverwrite.from_pair(allow, deny)
+
+ if ow.type == 'role':
+ # accidentally quadratic
+ target = discord.utils.find(lambda r: r.id == ow.id, self.server.roles)
+ elif ow.type == 'member':
+ target = self.server.get_member(ow.id)
+
+ ret.append((target, overwrite))
+ return ret
+
+ def permissions_for(self, member):
+ """Handles permission resolution for the current :class:`Member`.
+
+ This function takes into consideration the following cases:
+
+ - Guild owner
+ - Guild roles
+ - Channel overrides
+ - Member overrides
+ - Whether the channel is the default channel.
+
+ Parameters
+ ----------
+ member : :class:`Member`
+ The member to resolve permissions for.
+
+ Returns
+ -------
+ :class:`Permissions`
+ The resolved permissions for the member.
+ """
+
+ # The current cases can be explained as:
+ # Guild 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.guild.owner.id:
+ return Permissions.all()
+
+ default = self.guild.default_role
+ base = Permissions(default.permissions.value)
+
+ # Apply guild roles that the member has.
+ for role in member.roles:
+ base.value |= role.permissions.value
+
+ # Guild-wide Administrator -> True for everything
+ # Bypass all channel-specific overrides
+ if base.administrator:
+ return Permissions.all()
+
+ member_role_ids = set(map(lambda r: r.id, member.roles))
+ denies = 0
+ allows = 0
+
+ # Apply channel specific role permission overwrites
+ for overwrite in self._overwrites:
+ if overwrite.type == 'role' and overwrite.id in member_role_ids:
+ denies |= overwrite.deny
+ allows |= overwrite.allow
+
+ base.handle_overwrite(allow=allows, deny=denies)
+
+ # Apply member specific permission overwrites
+ for overwrite in self._overwrites:
+ if overwrite.type == 'member' and overwrite.id == member.id:
+ base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny)
+ break
+
+ # default channels can always be read
+ if self.is_default:
+ base.read_messages = True
+
+ # if you can't send a message in a channel then you can't have certain
+ # permissions as well
+ if not base.send_messages:
+ base.send_tts_messages = False
+ base.mention_everyone = False
+ base.embed_links = False
+ base.attach_files = False
+
+ # if you can't read a channel then you have no permissions there
+ if not base.read_messages:
+ denied = Permissions.all_channel()
+ base.value &= ~denied.value
+
+ # text channels do not have voice related permissions
+ if isinstance(self, TextChannel):
+ denied = Permissions.voice()
+ base.value &= ~denied.value
+
+ return base
+
+ @asyncio.coroutine
+ def delete(self):
+ """|coro|
+
+ Deletes the channel.
+
+ You must have Manage Channel permission to use this.
+
+ Raises
+ -------
+ Forbidden
+ You do not have proper permissions to delete the channel.
+ NotFound
+ The channel was not found or was already deleted.
+ HTTPException
+ Deleting the channel failed.
+ """
+ yield from self._state.http.delete_channel(self.id)
+
class MessageChannel(metaclass=abc.ABCMeta):
__slots__ = ()
diff --git a/discord/channel.py b/discord/channel.py
index 75d801ef..80ecdaab 100644
--- a/discord/channel.py
+++ b/discord/channel.py
@@ -25,7 +25,6 @@ DEALINGS IN THE SOFTWARE.
from .permissions import Permissions, PermissionOverwrite
from .enums import ChannelType, try_enum
-from collections import namedtuple
from .mixins import Hashable
from .role import Role
from .user import User
@@ -39,269 +38,7 @@ import asyncio
__all__ = ('TextChannel', 'VoiceChannel', 'DMChannel', 'GroupChannel', '_channel_factory')
-Overwrites = namedtuple('Overwrites', 'id allow deny type')
-
-class CommonGuildChannel(Hashable):
- __slots__ = ()
-
- def __str__(self):
- return self.name
-
- @asyncio.coroutine
- def _move(self, position):
- if position < 0:
- raise InvalidArgument('Channel position cannot be less than 0.')
-
- http = self._state.http
- url = '{0}/{1.guild.id}/channels'.format(http.GUILDS, self)
- channels = [c for c in self.guild.channels if isinstance(c, type(self))]
-
- if position >= len(channels):
- raise InvalidArgument('Channel position cannot be greater than {}'.format(len(channels) - 1))
-
- channels.sort(key=lambda c: c.position)
-
- try:
- # remove ourselves from the channel list
- channels.remove(self)
- except ValueError:
- # not there somehow lol
- return
- else:
- # add ourselves at our designated position
- channels.insert(position, self)
-
- payload = [{'id': c.id, 'position': index } for index, c in enumerate(channels)]
- yield from http.patch(url, json=payload, bucket='move_channel')
-
- def _fill_overwrites(self, data):
- self._overwrites = []
- everyone_index = 0
- everyone_id = self.guild.id
-
- for index, overridden in enumerate(data.get('permission_overwrites', [])):
- overridden_id = int(overridden.pop('id'))
- self._overwrites.append(Overwrites(id=overridden_id, **overridden))
-
- if overridden['type'] == 'member':
- continue
-
- if overridden_id == everyone_id:
- # the @everyone role is not guaranteed to be the first one
- # in the list of permission overwrites, however the permission
- # resolution code kind of requires that it is the first one in
- # the list since it is special. So we need the index so we can
- # swap it to be the first one.
- everyone_index = index
-
- # do the swap
- tmp = self._overwrites
- if tmp:
- tmp[everyone_index], tmp[0] = tmp[0], tmp[everyone_index]
-
- @property
- def changed_roles(self):
- """Returns a list of :class:`Roles` that have been overridden from
- their default values in the :attr:`Guild.roles` attribute."""
- ret = []
- for overwrite in filter(lambda o: o.type == 'role', self._overwrites):
- role = discord.utils.get(self.guild.roles, id=overwrite.id)
- if role is None:
- continue
-
- role = copy.copy(role)
- role.permissions.handle_overwrite(overwrite.allow, overwrite.deny)
- ret.append(role)
- return ret
-
- @property
- def is_default(self):
- """bool : Indicates if this is the default channel for the :class:`Guild` it belongs to."""
- return self.guild.id == self.id
-
- @property
- def mention(self):
- """str : The string that allows you to mention the channel."""
- return '<#{0.id}>'.format(self)
-
- @property
- def created_at(self):
- """Returns the channel's creation time in UTC."""
- return discord.utils.snowflake_time(self.id)
-
- def overwrites_for(self, obj):
- """Returns the channel-specific overwrites for a member or a role.
-
- Parameters
- -----------
- obj
- The :class:`Role` or :class:`Member` or :class:`Object` denoting
- whose overwrite to get.
-
- Returns
- ---------
- :class:`PermissionOverwrite`
- The permission overwrites for this object.
- """
-
- if isinstance(obj, Member):
- predicate = lambda p: p.type == 'member'
- elif isinstance(obj, Role):
- predicate = lambda p: p.type == 'role'
- else:
- predicate = lambda p: True
-
- for overwrite in filter(predicate, self._overwrites):
- if overwrite.id == obj.id:
- allow = Permissions(overwrite.allow)
- deny = Permissions(overwrite.deny)
- return PermissionOverwrite.from_pair(allow, deny)
-
- return PermissionOverwrite()
-
- @property
- def overwrites(self):
- """Returns all of the channel's overwrites.
-
- This is returned as a list of two-element tuples containing the target,
- which can be either a :class:`Role` or a :class:`Member` and the overwrite
- as the second element as a :class:`PermissionOverwrite`.
-
- Returns
- --------
- List[Tuple[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`]]:
- The channel's permission overwrites.
- """
- ret = []
- for ow in self._permission_overwrites:
- allow = Permissions(ow.allow)
- deny = Permissions(ow.deny)
- overwrite = PermissionOverwrite.from_pair(allow, deny)
-
- if ow.type == 'role':
- # accidentally quadratic
- target = discord.utils.find(lambda r: r.id == ow.id, self.server.roles)
- elif ow.type == 'member':
- target = self.server.get_member(ow.id)
-
- ret.append((target, overwrite))
- return ret
-
- def permissions_for(self, member):
- """Handles permission resolution for the current :class:`Member`.
-
- This function takes into consideration the following cases:
-
- - Guild owner
- - Guild roles
- - Channel overrides
- - Member overrides
- - Whether the channel is the default channel.
-
- Parameters
- ----------
- member : :class:`Member`
- The member to resolve permissions for.
-
- Returns
- -------
- :class:`Permissions`
- The resolved permissions for the member.
- """
-
- # The current cases can be explained as:
- # Guild 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.guild.owner.id:
- return Permissions.all()
-
- default = self.guild.default_role
- base = Permissions(default.permissions.value)
-
- # Apply guild roles that the member has.
- for role in member.roles:
- base.value |= role.permissions.value
-
- # Guild-wide Administrator -> True for everything
- # Bypass all channel-specific overrides
- if base.administrator:
- return Permissions.all()
-
- member_role_ids = set(map(lambda r: r.id, member.roles))
- denies = 0
- allows = 0
-
- # Apply channel specific role permission overwrites
- for overwrite in self._overwrites:
- if overwrite.type == 'role' and overwrite.id in member_role_ids:
- denies |= overwrite.deny
- allows |= overwrite.allow
-
- base.handle_overwrite(allow=allows, deny=denies)
-
- # Apply member specific permission overwrites
- for overwrite in self._overwrites:
- if overwrite.type == 'member' and overwrite.id == member.id:
- base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny)
- break
-
- # default channels can always be read
- if self.is_default:
- base.read_messages = True
-
- # if you can't send a message in a channel then you can't have certain
- # permissions as well
- if not base.send_messages:
- base.send_tts_messages = False
- base.mention_everyone = False
- base.embed_links = False
- base.attach_files = False
-
- # if you can't read a channel then you have no permissions there
- if not base.read_messages:
- denied = Permissions.all_channel()
- base.value &= ~denied.value
-
- # text channels do not have voice related permissions
- if isinstance(self, TextChannel):
- denied = Permissions.voice()
- base.value &= ~denied.value
-
- return base
-
- @asyncio.coroutine
- def delete(self):
- """|coro|
-
- Deletes the channel.
-
- You must have Manage Channel permission to use this.
-
- Raises
- -------
- Forbidden
- You do not have proper permissions to delete the channel.
- NotFound
- The channel was not found or was already deleted.
- HTTPException
- Deleting the channel failed.
- """
- yield from self._state.http.delete_channel(self.id)
-
-class TextChannel(discord.abc.MessageChannel, CommonGuildChannel):
+class TextChannel(discord.abc.MessageChannel, discord.abc.GuildChannel, Hashable):
"""Represents a Discord guild text channel.
Supported Operations:
@@ -393,7 +130,7 @@ class TextChannel(discord.abc.MessageChannel, CommonGuildChannel):
data = yield from self._state.http.edit_channel(self.id, **options)
self._update(self.guild, data)
-class VoiceChannel(CommonGuildChannel):
+class VoiceChannel(discord.abc.GuildChannel, Hashable):
"""Represents a Discord guild voice channel.
Supported Operations: