diff --git a/discord/channel.py b/discord/channel.py index 1dc82025f..c53a96cff 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -30,6 +30,7 @@ from .enums import ChannelType from collections import namedtuple from .mixins import Hashable from .role import Role +from .user import User from .member import Member Overwrites = namedtuple('Overwrites', 'id allow deny type') @@ -298,24 +299,68 @@ class PrivateChannel(Hashable): Attributes ---------- - user : :class:`User` - The user you are participating with in the private channel. - id : str + recipients: list of :class:`User` + The users you are participating with in the private channel. + id: str The private channel ID. - is_private : bool + is_private: bool ``True`` if the channel is a private channel (i.e. PM). ``True`` in this case. + type: :class:`ChannelType` + The type of private channel. + owner: Optional[:class:`User`] + The user that owns the private channel. If the channel type is not + :attr:`ChannelType.group` then this is always ``None``. + icon: Optional[str] + The private channel's icon hash. If the channel type is not + :attr:`ChannelType.group` then this is always ``None``. + name: Optional[str] + The private channel's name. If the channel type is not + :attr:`ChannelType.group` then this is always ``None``. """ - __slots__ = ['user', 'id', 'is_private'] + __slots__ = ['id', 'is_private', 'recipients', 'type', 'owner', 'icon', 'name'] - def __init__(self, user, id, **kwargs): - self.user = user - self.id = id + def __init__(self, me, **kwargs): + self.recipients = [User(**u) for u in kwargs['recipients']] + self.id = kwargs['id'] self.is_private = True + self.type = ChannelType(kwargs['type']) + + owner_id = kwargs.get('owner_id') + self.owner = None + self.icon = kwargs.get('icon') + self.name = kwargs.get('name') + + self.recipients = [] + for data in kwargs['recipients']: + to_add = User(**data) + if to_add.id == owner_id: + self.owner = to_add + self.recipients.append(to_add) + + if owner_id == me.id: + self.owner = me def __str__(self): return 'Direct Message with {0.name}'.format(self.user) + @property + def user(self): + """A property that returns the first recipient of the private channel. + + This is mainly for compatibility and ease of use with old style private + channels that had a single recipient. + """ + return self.recipients[0] + + @property + def icon_url(self): + """Returns the channel's icon URL if available or an empty string otherwise.""" + if self.icon is None: + return '' + + return 'https://cdn.discordapp.com/channel-icons/{0.id}/{0.icon}.jpg'.format(self) + @property def created_at(self): """Returns the private channel's creation time in UTC.""" @@ -332,7 +377,9 @@ class PrivateChannel(Hashable): - send_tts_messages: You cannot send TTS messages in a PM. - manage_messages: You cannot delete others messages in a PM. - - mention_everyone: There is no one to mention in a PM. + + This also handles permissions for :attr:`ChannelType.group` channels + such as kicking or mentioning everyone. Parameters ----------- @@ -348,7 +395,11 @@ class PrivateChannel(Hashable): base = Permissions.text() base.send_tts_messages = False base.manage_messages = False - base.mention_everyone = False + base.mention_everyone = self.type is ChannelType.group + + if user == self.owner: + base.kick_members = True + return base diff --git a/discord/enums.py b/discord/enums.py index 4666c52f5..10a7f60b5 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -27,11 +27,13 @@ DEALINGS IN THE SOFTWARE. from enum import Enum class ChannelType(Enum): - text = 'text' - voice = 'voice' + text = 0 + private = 1 + voice = 2 + group = 3 def __str__(self): - return self.value + return self.name class ServerRegion(Enum): us_west = 'us-west' diff --git a/discord/gateway.py b/discord/gateway.py index 7ba78b077..56e4ba6fa 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -98,7 +98,7 @@ class VoiceKeepAliveHandler(KeepAliveHandler): } class DiscordWebSocket(websockets.client.WebSocketClientProtocol): - """Implements a WebSocket for Discord's gateway v4. + """Implements a WebSocket for Discord's gateway v6. This is created through :func:`create_main_websocket`. Library users should never create this manually. diff --git a/discord/http.py b/discord/http.py index 192337fe8..fabcafd67 100644 --- a/discord/http.py +++ b/discord/http.py @@ -53,7 +53,7 @@ class HTTPClient: """Represents an HTTP client sending HTTP requests to the Discord API.""" BASE = 'https://discordapp.com' - API_BASE = BASE + '/api' + API_BASE = BASE + '/api/v6' GATEWAY = API_BASE + '/gateway' USERS = API_BASE + '/users' ME = USERS + '/@me' @@ -500,4 +500,4 @@ class HTTPClient: data = yield from self.get(self.GATEWAY, bucket=_func_()) except HTTPException as e: raise GatewayNotFound() from e - return data.get('url') + '?encoding=json&v=5' + return data.get('url') + '?encoding=json&v=6' diff --git a/discord/state.py b/discord/state.py index 7fc8debc8..25988a623 100644 --- a/discord/state.py +++ b/discord/state.py @@ -205,8 +205,7 @@ class ConnectionState: servers.append(server) for pm in data.get('private_channels'): - self._add_private_channel(PrivateChannel(id=pm['id'], - user=User(**pm['recipient']))) + self._add_private_channel(PrivateChannel(self.user, **pm)) compat.create_task(self._delay_ready(), loop=self.loop) @@ -303,9 +302,7 @@ class ConnectionState: is_private = data.get('is_private', False) channel = None if is_private: - recipient = User(**data.get('recipient')) - pm_id = data.get('id') - channel = PrivateChannel(id=pm_id, user=recipient) + channel = PrivateChannel(self.user, **data) self._add_private_channel(channel) else: server = self._get_server(data.get('guild_id')) diff --git a/docs/api.rst b/docs/api.rst index 7d015b634..32014d6b9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -391,6 +391,12 @@ All enumerations are subclasses of `enum`_. .. attribute:: voice A voice channel. + .. attribute:: private + + A private text channel. Also called a direct message. + .. attribute:: group + + A private group text channel. .. class:: ServerRegion