Browse Source

Begin working on the rewrite.

pull/447/head
Rapptz 9 years ago
parent
commit
044b0824e6
  1. 84
      discord/channel.py
  2. 42
      discord/client.py
  3. 16
      discord/emoji.py
  4. 35
      discord/invite.py
  5. 20
      discord/iterators.py
  6. 130
      discord/member.py
  7. 51
      discord/message.py
  8. 29
      discord/role.py
  9. 46
      discord/server.py
  10. 93
      discord/state.py
  11. 17
      discord/user.py

84
discord/channel.py

@ -26,7 +26,7 @@ DEALINGS IN THE SOFTWARE.
import copy import copy
from . import utils from . import utils
from .permissions import Permissions, PermissionOverwrite from .permissions import Permissions, PermissionOverwrite
from .enums import ChannelType from .enums import ChannelType, try_enum
from collections import namedtuple from collections import namedtuple
from .mixins import Hashable from .mixins import Hashable
from .role import Role from .role import Role
@ -54,68 +54,63 @@ class Channel(Hashable):
Attributes Attributes
----------- -----------
name : str name: str
The channel name. The channel name.
server : :class:`Server` server: :class:`Server`
The server the channel belongs to. The server the channel belongs to.
id : str id: str
The channel ID. The channel ID.
topic : Optional[str] topic: Optional[str]
The channel's topic. None if it doesn't exist. The channel's topic. None if it doesn't exist.
is_private : bool is_private: bool
``True`` if the channel is a private channel (i.e. PM). ``False`` in this case. ``True`` if the channel is a private channel (i.e. PM). ``False`` in this case.
position : int position: int
The position in the channel list. This is a number that starts at 0. e.g. the The position in the channel list. This is a number that starts at 0. e.g. the
top channel is position 0. The position varies depending on being a voice channel top channel is position 0. The position varies depending on being a voice channel
or a text channel, so a 0 position voice channel is on top of the voice channel or a text channel, so a 0 position voice channel is on top of the voice channel
list. list.
type : :class:`ChannelType` type: :class:`ChannelType`
The channel type. There is a chance that the type will be ``str`` if The channel type. There is a chance that the type will be ``str`` if
the channel type is not within the ones recognised by the enumerator. the channel type is not within the ones recognised by the enumerator.
bitrate : int bitrate: int
The channel's preferred audio bitrate in bits per second. The channel's preferred audio bitrate in bits per second.
voice_members voice_members
A list of :class:`Members` that are currently inside this voice channel. A list of :class:`Members` that are currently inside this voice channel.
If :attr:`type` is not :attr:`ChannelType.voice` then this is always an empty array. If :attr:`type` is not :attr:`ChannelType.voice` then this is always an empty array.
user_limit : int user_limit: int
The channel's limit for number of members that can be in a voice channel. The channel's limit for number of members that can be in a voice channel.
""" """
__slots__ = [ 'voice_members', 'name', 'id', 'server', 'topic', 'position', __slots__ = ( 'voice_members', 'name', 'id', 'server', 'topic',
'is_private', 'type', 'bitrate', 'user_limit', 'type', 'bitrate', 'user_limit', '_state', 'position',
'_permission_overwrites' ] '_permission_overwrites' )
def __init__(self, **kwargs): def __init__(self, *, state, server, data):
self._update(**kwargs) self._state = state
self._update(server, data)
self.voice_members = [] self.voice_members = []
def __str__(self): def __str__(self):
return self.name return self.name
def _update(self, **kwargs): def _update(self, server, data):
self.name = kwargs.get('name') self.server = server
self.server = kwargs.get('server') self.name = data['name']
self.id = kwargs.get('id') self.id = data['id']
self.topic = kwargs.get('topic') self.topic = data.get('topic')
self.is_private = False self.position = data['position']
self.position = kwargs.get('position') self.bitrate = data.get('bitrate')
self.bitrate = kwargs.get('bitrate') self.type = data['type']
self.type = kwargs.get('type') self.user_limit = data.get('user_limit')
self.user_limit = kwargs.get('user_limit')
try:
self.type = ChannelType(self.type)
except:
pass
self._permission_overwrites = [] self._permission_overwrites = []
everyone_index = 0 everyone_index = 0
everyone_id = self.server.id everyone_id = self.server.id
for index, overridden in enumerate(kwargs.get('permission_overwrites', [])): for index, overridden in enumerate(data.get('permission_overwrites', [])):
overridden_id = overridden['id'] overridden_id = overridden['id']
self._permission_overwrites.append(Overwrites(**overridden)) self._permission_overwrites.append(Overwrites(**overridden))
if overridden.get('type') == 'member': if overridden['type'] == 'member':
continue continue
if overridden_id == everyone_id: if overridden_id == everyone_id:
@ -151,6 +146,10 @@ class Channel(Hashable):
"""bool : Indicates if this is the default channel for the :class:`Server` it belongs to.""" """bool : Indicates if this is the default channel for the :class:`Server` it belongs to."""
return self.server.id == self.id return self.server.id == self.id
@property
def is_private(self):
return False
@property @property
def mention(self): def mention(self):
"""str : The string that allows you to mention the channel.""" """str : The string that allows you to mention the channel."""
@ -354,19 +353,20 @@ class PrivateChannel(Hashable):
:attr:`ChannelType.group` then this is always ``None``. :attr:`ChannelType.group` then this is always ``None``.
""" """
__slots__ = ['id', 'recipients', 'type', 'owner', 'icon', 'name', 'me'] __slots__ = ['id', 'recipients', 'type', 'owner', 'icon', 'name', 'me', '_state']
def __init__(self, me, **kwargs): def __init__(self, *, me, state, data):
self.recipients = [User(**u) for u in kwargs['recipients']] self._state = state
self.id = kwargs['id'] self.recipients = [state.try_insert_user(u) for u in data['recipients']]
self.id = data['id']
self.me = me self.me = me
self.type = ChannelType(kwargs['type']) self.type = ChannelType(data['type'])
self._update_group(**kwargs) self._update_group(data)
def _update_group(self, **kwargs): def _update_group(self, data):
owner_id = kwargs.get('owner_id') owner_id = data.get('owner_id')
self.icon = kwargs.get('icon') self.icon = data.get('icon')
self.name = kwargs.get('name') self.name = data.get('name')
self.owner = utils.find(lambda u: u.id == owner_id, self.recipients) self.owner = utils.find(lambda u: u.id == owner_id, self.recipients)
@property @property

42
discord/client.py

@ -32,7 +32,6 @@ from .server import Server
from .message import Message from .message import Message
from .invite import Invite from .invite import Invite
from .object import Object from .object import Object
from .reaction import Reaction
from .role import Role from .role import Role
from .errors import * from .errors import *
from .state import ConnectionState from .state import ConnectionState
@ -145,16 +144,15 @@ class Client:
self.shard_id = options.get('shard_id') self.shard_id = options.get('shard_id')
self.shard_count = options.get('shard_count') self.shard_count = options.get('shard_count')
max_messages = options.get('max_messages')
if max_messages is None or max_messages < 100:
max_messages = 5000
self.connection = ConnectionState(self.dispatch, self.request_offline_members,
self._syncer, max_messages, loop=self.loop)
connector = options.pop('connector', None) connector = options.pop('connector', None)
self.http = HTTPClient(connector, loop=self.loop) self.http = HTTPClient(connector, loop=self.loop)
self.connection = ConnectionState(dispatch=self.dispatch,
chunker=self.request_offline_members,
syncer=self._syncer,
http=self.http, loop=self.loop,
**options)
self._closed = asyncio.Event(loop=self.loop) self._closed = asyncio.Event(loop=self.loop)
self._is_logged_in = asyncio.Event(loop=self.loop) self._is_logged_in = asyncio.Event(loop=self.loop)
self._is_ready = asyncio.Event(loop=self.loop) self._is_ready = asyncio.Event(loop=self.loop)
@ -914,7 +912,7 @@ class Client:
raise InvalidArgument('user argument must be a User') raise InvalidArgument('user argument must be a User')
data = yield from self.http.start_private_message(user.id) data = yield from self.http.start_private_message(user.id)
channel = PrivateChannel(me=self.user, **data) channel = PrivateChannel(me=self.user, data=data, state=self.connection.ctx)
self.connection._add_private_channel(channel) self.connection._add_private_channel(channel)
return channel return channel
@ -1151,7 +1149,7 @@ class Client:
data = yield from self.http.send_message(channel_id, content, guild_id=guild_id, tts=tts, embed=embed) data = yield from self.http.send_message(channel_id, content, guild_id=guild_id, tts=tts, embed=embed)
channel = self.get_channel(data.get('channel_id')) channel = self.get_channel(data.get('channel_id'))
message = self.connection._create_message(channel=channel, **data) message = Message(channel=channel, state=self.connection.ctx, data=data)
return message return message
@asyncio.coroutine @asyncio.coroutine
@ -1233,7 +1231,7 @@ class Client:
data = yield from self.http.send_file(channel_id, buffer, guild_id=guild_id, data = yield from self.http.send_file(channel_id, buffer, guild_id=guild_id,
filename=filename, content=content, tts=tts) filename=filename, content=content, tts=tts)
channel = self.get_channel(data.get('channel_id')) channel = self.get_channel(data.get('channel_id'))
message = self.connection._create_message(channel=channel, **data) message = Message(channel=channel, state=self.connection.ctx, data=data)
return message return message
@asyncio.coroutine @asyncio.coroutine
@ -1438,7 +1436,7 @@ class Client:
embed = embed.to_dict() if embed else None embed = embed.to_dict() if embed else None
guild_id = channel.server.id if not getattr(channel, 'is_private', True) else None guild_id = channel.server.id if not getattr(channel, 'is_private', True) else None
data = yield from self.http.edit_message(message.id, channel.id, content, guild_id=guild_id, embed=embed) data = yield from self.http.edit_message(message.id, channel.id, content, guild_id=guild_id, embed=embed)
return self.connection._create_message(channel=channel, **data) return Message(channel=channel, state=self.connection.ctx, data=data)
@asyncio.coroutine @asyncio.coroutine
def get_message(self, channel, id): def get_message(self, channel, id):
@ -1471,7 +1469,7 @@ class Client:
""" """
data = yield from self.http.get_message(channel.id, id) data = yield from self.http.get_message(channel.id, id)
return self.connection._create_message(channel=channel, **data) return Message(channel=channel, state=self.connection.ctx, data=data)
@asyncio.coroutine @asyncio.coroutine
def pin_message(self, message): def pin_message(self, message):
@ -1541,7 +1539,7 @@ class Client:
""" """
data = yield from self.http.pins_from(channel.id) data = yield from self.http.pins_from(channel.id)
return [self.connection._create_message(channel=channel, **m) for m in data] return [Message(channel=channel, state=self.connection.ctx, data=m) for m in data]
def _logs_from(self, channel, limit=100, before=None, after=None, around=None): def _logs_from(self, channel, limit=100, before=None, after=None, around=None):
"""|coro| """|coro|
@ -1622,7 +1620,7 @@ class Client:
def generator(data): def generator(data):
for message in data: for message in data:
yield self.connection._create_message(channel=channel, **message) yield Message(channel=channel, state=self.connection.ctx, data=message)
result = [] result = []
while limit > 0: while limit > 0:
@ -2161,7 +2159,7 @@ class Client:
perms.append(payload) perms.append(payload)
data = yield from self.http.create_channel(server.id, name, str(type), permission_overwrites=perms) data = yield from self.http.create_channel(server.id, name, str(type), permission_overwrites=perms)
channel = Channel(server=server, **data) channel = Channel(server=server, state=self.connection.ctx, data=data)
return channel return channel
@asyncio.coroutine @asyncio.coroutine
@ -2275,7 +2273,7 @@ class Client:
region = region.name region = region.name
data = yield from self.http.create_server(name, region, icon) data = yield from self.http.create_server(name, region, icon)
return Server(**data) return Server(data=data, state=self.connection.ctx)
@asyncio.coroutine @asyncio.coroutine
def edit_server(self, server, **fields): def edit_server(self, server, **fields):
@ -2397,7 +2395,7 @@ class Client:
""" """
data = yield from self.http.get_bans(server.id) data = yield from self.http.get_bans(server.id)
return [User(**user['user']) for user in data] return [self.connection.try_insert_user(user) for user in data]
@asyncio.coroutine @asyncio.coroutine
def prune_members(self, server, *, days): def prune_members(self, server, *, days):
@ -2514,7 +2512,7 @@ class Client:
img = utils._bytes_to_base64_data(image) img = utils._bytes_to_base64_data(image)
data = yield from self.http.create_custom_emoji(server.id, name, img) data = yield from self.http.create_custom_emoji(server.id, name, img)
return Emoji(server=server, **data) return Emoji(server=server, data=data, state=self.connection.ctx)
@asyncio.coroutine @asyncio.coroutine
def delete_custom_emoji(self, emoji): def delete_custom_emoji(self, emoji):
@ -2989,7 +2987,7 @@ class Client:
""" """
data = yield from self.http.create_role(server.id) data = yield from self.http.create_role(server.id)
role = Role(server=server, **data) role = Role(server=server, data=data, state=self.connection.ctx)
# we have to call edit because you can't pass a payload to the # we have to call edit because you can't pass a payload to the
# http request currently. # http request currently.
@ -3271,7 +3269,7 @@ class Client:
data = yield from self.http.application_info() data = yield from self.http.application_info()
return AppInfo(id=data['id'], name=data['name'], return AppInfo(id=data['id'], name=data['name'],
description=data['description'], icon=data['icon'], description=data['description'], icon=data['icon'],
owner=User(**data['owner'])) owner=User(state=self.connection.ctx, data=data['owner']))
@asyncio.coroutine @asyncio.coroutine
def get_user_info(self, user_id): def get_user_info(self, user_id):
@ -3300,4 +3298,4 @@ class Client:
Fetching the user failed. Fetching the user failed.
""" """
data = yield from self.http.get_user_info(user_id) data = yield from self.http.get_user_info(user_id)
return User(**data) return User(state=self.connection.ctx, data=data)

16
discord/emoji.py

@ -68,11 +68,12 @@ class Emoji(Hashable):
A list of :class:`Role` that is allowed to use this emoji. If roles is empty, A list of :class:`Role` that is allowed to use this emoji. If roles is empty,
the emoji is unrestricted. the emoji is unrestricted.
""" """
__slots__ = ["require_colons", "managed", "id", "name", "roles", 'server'] __slots__ = ('require_colons', 'managed', 'id', 'name', 'roles', 'server', '_state')
def __init__(self, **kwargs): def __init__(self, *, server, state, data):
self.server = kwargs.pop('server') self.server = server
self._from_data(kwargs) self._state = state
self._from_data(data)
def _from_data(self, emoji): def _from_data(self, emoji):
self.require_colons = emoji.get('require_colons') self.require_colons = emoji.get('require_colons')
@ -86,9 +87,10 @@ class Emoji(Hashable):
def _iterator(self): def _iterator(self):
for attr in self.__slots__: for attr in self.__slots__:
value = getattr(self, attr, None) if attr[0] != '_':
if value is not None: value = getattr(self, attr, None)
yield (attr, value) if value is not None:
yield (attr, value)
def __iter__(self): def __iter__(self):
return self._iterator() return self._iterator()

35
discord/invite.py

@ -76,23 +76,24 @@ class Invite(Hashable):
""" """
__slots__ = [ 'max_age', 'code', 'server', 'revoked', 'created_at', 'uses', __slots__ = ( 'max_age', 'code', 'server', 'revoked', 'created_at', 'uses',
'temporary', 'max_uses', 'xkcd', 'inviter', 'channel' ] 'temporary', 'max_uses', 'xkcd', 'inviter', 'channel', '_state' )
def __init__(self, **kwargs): def __init__(self, *, state, data):
self.max_age = kwargs.get('max_age') self._state = state
self.code = kwargs.get('code') self.max_age = data.get('max_age')
self.server = kwargs.get('server') self.code = data.get('code')
self.revoked = kwargs.get('revoked') self.server = data.get('server')
self.created_at = parse_time(kwargs.get('created_at')) self.revoked = data.get('revoked')
self.temporary = kwargs.get('temporary') self.created_at = parse_time(data.get('created_at'))
self.uses = kwargs.get('uses') self.temporary = data.get('temporary')
self.max_uses = kwargs.get('max_uses') self.uses = data.get('uses')
self.xkcd = kwargs.get('xkcdpass') self.max_uses = data.get('max_uses')
self.xkcd = data.get('xkcdpass')
inviter_data = kwargs.get('inviter')
self.inviter = None if inviter_data is None else User(**inviter_data) inviter_data = data.get('inviter')
self.channel = kwargs.get('channel') self.inviter = None if inviter_data is None else User(state=state, data=data)
self.channel = data.get('channel')
def __str__(self): def __str__(self):
return self.url return self.url

20
discord/iterators.py

@ -72,7 +72,6 @@ class LogsFromIterator:
def __init__(self, client, channel, limit, def __init__(self, client, channel, limit,
before=None, after=None, around=None, reverse=False): before=None, after=None, around=None, reverse=False):
self.client = client self.client = client
self.connection = client.connection
self.channel = channel self.channel = channel
self.limit = limit self.limit = limit
self.before = before self.before = before
@ -81,6 +80,7 @@ class LogsFromIterator:
self.reverse = reverse self.reverse = reverse
self._filter = None # message dict -> bool self._filter = None # message dict -> bool
self.messages = asyncio.Queue() self.messages = asyncio.Queue()
self.ctx = client.connection.ctx
if self.around: if self.around:
if self.limit > 101: if self.limit > 101:
@ -92,18 +92,18 @@ class LogsFromIterator:
self._retrieve_messages = self._retrieve_messages_around_strategy self._retrieve_messages = self._retrieve_messages_around_strategy
if self.before and self.after: if self.before and self.after:
self._filter = lambda m: int(self.after.id) < int(m['id']) < int(self.before.id) self._filter = lambda m: self.after.id < m['id'] < self.before.id
elif self.before: elif self.before:
self._filter = lambda m: int(m['id']) < int(self.before.id) self._filter = lambda m: m['id'] < self.before.id
elif self.after: elif self.after:
self._filter = lambda m: int(self.after.id) < int(m['id']) self._filter = lambda m: self.after.id < m['id']
elif self.before and self.after: elif self.before and self.after:
if self.reverse: if self.reverse:
self._retrieve_messages = self._retrieve_messages_after_strategy self._retrieve_messages = self._retrieve_messages_after_strategy
self._filter = lambda m: int(m['id']) < int(self.before.id) self._filter = lambda m: m['id'] < self.before.id
else: else:
self._retrieve_messages = self._retrieve_messages_before_strategy self._retrieve_messages = self._retrieve_messages_before_strategy
self._filter = lambda m: int(m['id']) > int(self.after.id) self._filter = lambda m: m['id'] > self.after.id
elif self.after: elif self.after:
self._retrieve_messages = self._retrieve_messages_after_strategy self._retrieve_messages = self._retrieve_messages_after_strategy
else: else:
@ -126,9 +126,7 @@ class LogsFromIterator:
if self._filter: if self._filter:
data = filter(self._filter, data) data = filter(self._filter, data)
for element in data: for element in data:
yield from self.messages.put( yield from self.messages.put(Message(channel=self.channel, state=self.ctx, data=element))
self.connection._create_message(
channel=self.channel, **element))
@asyncio.coroutine @asyncio.coroutine
def _retrieve_messages(self, retrieve): def _retrieve_messages(self, retrieve):
@ -141,7 +139,7 @@ class LogsFromIterator:
data = yield from self.client._logs_from(self.channel, retrieve, before=self.before) data = yield from self.client._logs_from(self.channel, retrieve, before=self.before)
if len(data): if len(data):
self.limit -= retrieve self.limit -= retrieve
self.before = Object(id=data[-1]['id']) self.before = Object(id=int(data[-1]['id']))
return data return data
@asyncio.coroutine @asyncio.coroutine
@ -150,7 +148,7 @@ class LogsFromIterator:
data = yield from self.client._logs_from(self.channel, retrieve, after=self.after) data = yield from self.client._logs_from(self.channel, retrieve, after=self.after)
if len(data): if len(data):
self.limit -= retrieve self.limit -= retrieve
self.after = Object(id=data[0]['id']) self.after = Object(id=int(data[0]['id']))
return data return data
@asyncio.coroutine @asyncio.coroutine

130
discord/member.py

@ -28,9 +28,11 @@ from .user import User
from .game import Game from .game import Game
from .permissions import Permissions from .permissions import Permissions
from . import utils from . import utils
from .enums import Status, ChannelType from .enums import Status, ChannelType, try_enum
from .colour import Colour from .colour import Colour
import copy import copy
import inspect
class VoiceState: class VoiceState:
"""Represents a Discord user's voice state. """Represents a Discord user's voice state.
@ -52,8 +54,8 @@ class VoiceState:
is not currently in a voice channel. is not currently in a voice channel.
""" """
__slots__ = [ 'session_id', 'deaf', 'mute', 'self_mute', __slots__ = ( 'session_id', 'deaf', 'mute', 'self_mute',
'self_deaf', 'is_afk', 'voice_channel' ] 'self_deaf', 'is_afk', 'voice_channel' )
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.session_id = kwargs.get('session_id') self.session_id = kwargs.get('session_id')
@ -74,12 +76,57 @@ def flatten_voice_states(cls):
setattr(cls, attr, property(getter)) setattr(cls, attr, property(getter))
return cls return cls
def flatten_user(cls):
for attr, value in User.__dict__.items():
# ignore private/special methods
if attr.startswith('_'):
continue
# don't override what we already have
if attr in cls.__dict__:
continue
# if it's a slotted attribute or a property, redirect it
# slotted members are implemented as member_descriptors in Type.__dict__
if hasattr(value, '__get__'):
def getter(self, x=attr):
return getattr(self._user, x)
setattr(cls, attr, property(getter, doc='Equivalent to :attr:`User.%s`' % attr))
else:
# probably a member function by now
def generate_function(x):
def general(self, *args, **kwargs):
return getattr(self._user, x)(*args, **kwargs)
general.__name__ = x
return general
func = generate_function(attr)
func.__doc__ = value.__doc__
setattr(cls, attr, func)
return cls
@flatten_voice_states @flatten_voice_states
class Member(User): @flatten_user
class Member:
"""Represents a Discord member to a :class:`Server`. """Represents a Discord member to a :class:`Server`.
This is a subclass of :class:`User` that extends more functionality This implements a lot of the functionality of :class:`User`.
that server members have such as roles and permissions.
Supported Operations:
+-----------+-----------------------------------------------+
| Operation | Description |
+===========+===============================================+
| x == y | Checks if two members are equal. |
+-----------+-----------------------------------------------+
| x != y | Checks if two members are not equal. |
+-----------+-----------------------------------------------+
| hash(x) | Return the member's hash. |
+-----------+-----------------------------------------------+
| str(x) | Returns the member's name with discriminator. |
+-----------+-----------------------------------------------+
Attributes Attributes
---------- ----------
@ -103,18 +150,31 @@ class Member(User):
The server specific nickname of the user. The server specific nickname of the user.
""" """
__slots__ = [ 'roles', 'joined_at', 'status', 'game', 'server', 'nick', 'voice' ] __slots__ = ('roles', 'joined_at', 'status', 'game', 'server', 'nick', 'voice', '_user', '_state')
def __init__(self, **kwargs): def __init__(self, *, data, server, state):
super().__init__(**kwargs.get('user')) self._state = state
self.voice = VoiceState(**kwargs) self._user = state.try_insert_user(data['user'])
self.joined_at = utils.parse_time(kwargs.get('joined_at')) self.voice = VoiceState(**data)
self.roles = kwargs.get('roles', []) self.joined_at = utils.parse_time(data.get('joined_at'))
self.roles = data.get('roles', [])
self.status = Status.offline self.status = Status.offline
game = kwargs.get('game', {}) game = data.get('game', {})
self.game = Game(**game) if game else None self.game = Game(**game) if game else None
self.server = kwargs.get('server', None) self.server = server
self.nick = kwargs.get('nick', None) self.nick = data.get('nick', None)
def __str__(self):
return self._user.__str__()
def __eq__(self, other):
return isinstance(other, Member) and other._user.id == self._user.id and self.server.id == other.server.id
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self._user.id)
def _update_voice_state(self, **kwargs): def _update_voice_state(self, **kwargs):
self.voice.self_mute = kwargs.get('self_mute', False) self.voice.self_mute = kwargs.get('self_mute', False)
@ -146,6 +206,35 @@ class Member(User):
ret.voice = copy.copy(self.voice) ret.voice = copy.copy(self.voice)
return ret return ret
def _update(self, data, user):
self._user.name = user['username']
self._user.discriminator = user['discriminator']
self._user.avatar = user['avatar']
self._user.bot = user.get('bot', False)
# the nickname change is optional,
# if it isn't in the payload then it didn't change
if 'nick' in data:
self.nick = data['nick']
# update the roles
self.roles = [self.server.default_role]
for role in self.server.roles:
if role.id in data['roles']:
self.roles.append(role)
# sort the roles by ID since they can be "randomised"
self.roles.sort(key=lambda r: int(r.id))
def _presence_update(self, data, user):
self.status = try_enum(Status, data['status'])
game = data.get('game', {})
self.game = Game(**game) if game else None
u = self._user
u.name = user.get('username', u.name)
u.avatar = user.get('avatar', u.avatar)
u.discriminator = user.get('discriminator', u.discriminator)
@property @property
def colour(self): def colour(self):
"""A property that returns a :class:`Colour` denoting the rendered colour """A property that returns a :class:`Colour` denoting the rendered colour
@ -173,13 +262,20 @@ class Member(User):
@property @property
def mention(self): def mention(self):
"""Returns a string that mentions the member."""
if self.nick: if self.nick:
return '<@!{}>'.format(self.id) return '<@!{}>'.format(self.id)
return '<@{}>'.format(self.id) return '<@{}>'.format(self.id)
def mentioned_in(self, message): def mentioned_in(self, message):
mentioned = super().mentioned_in(message) """Checks if the member is mentioned in the specified message.
if mentioned:
Parameters
-----------
message: :class:`Message`
The message to check if you're mentioned in.
"""
if self._user.mentioned_in(message):
return True return True
for role in message.role_mentions: for role in message.role_mentions:

51
discord/message.py

@ -107,43 +107,44 @@ class Message:
Reactions to a message. Reactions can be either custom emoji or standard unicode emoji. Reactions to a message. Reactions can be either custom emoji or standard unicode emoji.
""" """
__slots__ = [ 'edited_timestamp', 'timestamp', 'tts', 'content', 'channel', __slots__ = ( 'edited_timestamp', 'timestamp', 'tts', 'content', 'channel',
'mention_everyone', 'embeds', 'id', 'mentions', 'author', 'mention_everyone', 'embeds', 'id', 'mentions', 'author',
'channel_mentions', 'server', '_raw_mentions', 'attachments', 'channel_mentions', 'server', '_cs_raw_mentions', 'attachments',
'_clean_content', '_raw_channel_mentions', 'nonce', 'pinned', '_cs_clean_content', '_cs_raw_channel_mentions', 'nonce', 'pinned',
'role_mentions', '_raw_role_mentions', 'type', 'call', 'role_mentions', '_cs_raw_role_mentions', 'type', 'call',
'_system_content', 'reactions' ] '_cs_system_content', '_state', 'reactions' )
def __init__(self, **kwargs): def __init__(self, *, state, channel, data):
self._state = state
self.reactions = kwargs.pop('reactions') self.reactions = kwargs.pop('reactions')
for reaction in self.reactions: for reaction in self.reactions:
reaction.message = self reaction.message = self
self._update(**kwargs) self._update(channel, data)
def _update(self, **data): def _update(self, channel, data):
# at the moment, the timestamps seem to be naive so they have no time zone and operate on UTC time. # at the moment, the timestamps seem to be naive so they have no time zone and operate on UTC time.
# we can use this to our advantage to use strptime instead of a complicated parsing routine. # we can use this to our advantage to use strptime instead of a complicated parsing routine.
# example timestamp: 2015-08-21T12:03:45.782000+00:00 # example timestamp: 2015-08-21T12:03:45.782000+00:00
# sometimes the .%f modifier is missing # sometimes the .%f modifier is missing
self.edited_timestamp = utils.parse_time(data.get('edited_timestamp')) self.edited_timestamp = utils.parse_time(data['edited_timestamp'])
self.timestamp = utils.parse_time(data.get('timestamp')) self.timestamp = utils.parse_time(data['timestamp'])
self.tts = data.get('tts', False) self.tts = data.get('tts', False)
self.pinned = data.get('pinned', False) self.pinned = data.get('pinned', False)
self.content = data.get('content') self.content = data['content']
self.mention_everyone = data.get('mention_everyone') self.mention_everyone = data['mention_everyone']
self.embeds = data.get('embeds') self.embeds = data['embeds']
self.id = data.get('id') self.id = data['id']
self.channel = data.get('channel') self.channel = channel
self.author = User(**data.get('author', {})) self.author = self._state.try_insert_user(data['author'])
self.nonce = data.get('nonce') self.nonce = data.get('nonce')
self.attachments = data.get('attachments') self.attachments = data['attachments']
self.type = try_enum(MessageType, data.get('type')) self.type = try_enum(MessageType, data.get('type'))
self._handle_upgrades(data.get('channel_id')) self._handle_upgrades(data['channel_id'])
self._handle_mentions(data.get('mentions', []), data.get('mention_roles', [])) self._handle_mentions(data.get('mentions', []), data.get('mention_roles', []))
self._handle_call(data.get('call')) self._handle_call(data.get('call'))
# clear the cached properties # clear the cached properties
cached = filter(lambda attr: attr[0] == '_', self.__slots__) cached = filter(lambda attr: attr.startswith('_cs_'), self.__slots__)
for attr in cached: for attr in cached:
try: try:
delattr(self, attr) delattr(self, attr)
@ -155,7 +156,7 @@ class Message:
self.channel_mentions = [] self.channel_mentions = []
self.role_mentions = [] self.role_mentions = []
if getattr(self.channel, 'is_private', True): if getattr(self.channel, 'is_private', True):
self.mentions = [User(**m) for m in mentions] self.mentions = [self._state.try_insert_user(m) for m in mentions]
return return
if self.server is not None: if self.server is not None:
@ -193,7 +194,7 @@ class Message:
call['participants'] = participants call['participants'] = participants
self.call = CallMessage(message=self, **call) self.call = CallMessage(message=self, **call)
@utils.cached_slot_property('_raw_mentions') @utils.cached_slot_property('_cs_raw_mentions')
def raw_mentions(self): def raw_mentions(self):
"""A property that returns an array of user IDs matched with """A property that returns an array of user IDs matched with
the syntax of <@user_id> in the message content. the syntax of <@user_id> in the message content.
@ -203,21 +204,21 @@ class Message:
""" """
return re.findall(r'<@!?([0-9]+)>', self.content) return re.findall(r'<@!?([0-9]+)>', self.content)
@utils.cached_slot_property('_raw_channel_mentions') @utils.cached_slot_property('_cs_raw_channel_mentions')
def raw_channel_mentions(self): def raw_channel_mentions(self):
"""A property that returns an array of channel IDs matched with """A property that returns an array of channel IDs matched with
the syntax of <#channel_id> in the message content. the syntax of <#channel_id> in the message content.
""" """
return re.findall(r'<#([0-9]+)>', self.content) return re.findall(r'<#([0-9]+)>', self.content)
@utils.cached_slot_property('_raw_role_mentions') @utils.cached_slot_property('_cs_raw_role_mentions')
def raw_role_mentions(self): def raw_role_mentions(self):
"""A property that returns an array of role IDs matched with """A property that returns an array of role IDs matched with
the syntax of <@&role_id> in the message content. the syntax of <@&role_id> in the message content.
""" """
return re.findall(r'<@&([0-9]+)>', self.content) return re.findall(r'<@&([0-9]+)>', self.content)
@utils.cached_slot_property('_clean_content') @utils.cached_slot_property('_cs_clean_content')
def clean_content(self): def clean_content(self):
"""A property that returns the content in a "cleaned up" """A property that returns the content in a "cleaned up"
manner. This basically means that mentions are transformed manner. This basically means that mentions are transformed
@ -288,7 +289,7 @@ class Message:
if found is not None: if found is not None:
self.author = found self.author = found
@utils.cached_slot_property('_system_content') @utils.cached_slot_property('_cs_system_content')
def system_content(self): def system_content(self):
"""A property that returns the content that is rendered """A property that returns the content that is rendered
regardless of the :attr:`Message.type`. regardless of the :attr:`Message.type`.

29
discord/role.py

@ -78,12 +78,13 @@ class Role(Hashable):
Indicates if the role can be mentioned by users. Indicates if the role can be mentioned by users.
""" """
__slots__ = ['id', 'name', 'permissions', 'color', 'colour', 'position', __slots__ = ('id', 'name', 'permissions', 'color', 'colour', 'position',
'managed', 'mentionable', 'hoist', 'server' ] 'managed', 'mentionable', 'hoist', 'server', '_state' )
def __init__(self, **kwargs): def __init__(self, *, server, state, data):
self.server = kwargs.pop('server') self.server = server
self._update(**kwargs) self._state = state
self._update(data)
def __str__(self): def __str__(self):
return self.name return self.name
@ -118,15 +119,15 @@ class Role(Hashable):
return NotImplemented return NotImplemented
return not r return not r
def _update(self, **kwargs): def _update(self, data):
self.id = kwargs.get('id') self.id = data['id']
self.name = kwargs.get('name') self.name = data['name']
self.permissions = Permissions(kwargs.get('permissions', 0)) self.permissions = Permissions(data.get('permissions', 0))
self.position = kwargs.get('position', 0) self.position = data.get('position', 0)
self.colour = Colour(kwargs.get('color', 0)) self.colour = Colour(data.get('color', 0))
self.hoist = kwargs.get('hoist', False) self.hoist = data.get('hoist', False)
self.managed = kwargs.get('managed', False) self.managed = data.get('managed', False)
self.mentionable = kwargs.get('mentionable', False) self.mentionable = data.get('mentionable', False)
self.color = self.colour self.color = self.colour
@property @property

46
discord/server.py

@ -52,39 +52,39 @@ class Server(Hashable):
Attributes Attributes
---------- ----------
name : str name: str
The server name. The server name.
me : :class:`Member` me: :class:`Member`
Similar to :attr:`Client.user` except an instance of :class:`Member`. Similar to :attr:`Client.user` except an instance of :class:`Member`.
This is essentially used to get the member version of yourself. This is essentially used to get the member version of yourself.
roles roles
A list of :class:`Role` that the server has available. A list of :class:`Role` that the server has available.
emojis emojis
A list of :class:`Emoji` that the server owns. A list of :class:`Emoji` that the server owns.
region : :class:`ServerRegion` region: :class:`ServerRegion`
The region the server belongs on. There is a chance that the region The region the server belongs on. There is a chance that the region
will be a ``str`` if the value is not recognised by the enumerator. will be a ``str`` if the value is not recognised by the enumerator.
afk_timeout : int afk_timeout: int
The timeout to get sent to the AFK channel. The timeout to get sent to the AFK channel.
afk_channel : :class:`Channel` afk_channel: :class:`Channel`
The channel that denotes the AFK channel. None if it doesn't exist. The channel that denotes the AFK channel. None if it doesn't exist.
members members
An iterable of :class:`Member` that are currently on the server. An iterable of :class:`Member` that are currently on the server.
channels channels
An iterable of :class:`Channel` that are currently on the server. An iterable of :class:`Channel` that are currently on the server.
icon : str icon: str
The server's icon. The server's icon.
id : str id: str
The server's ID. The server's ID.
owner : :class:`Member` owner: :class:`Member`
The member who owns the server. The member who owns the server.
unavailable : bool unavailable: bool
Indicates if the server is unavailable. If this is ``True`` then the Indicates if the server is unavailable. If this is ``True`` then the
reliability of other attributes outside of :meth:`Server.id` is slim and they might reliability of other attributes outside of :meth:`Server.id` is slim and they might
all be None. It is best to not do anything with the server if it is unavailable. all be None. It is best to not do anything with the server if it is unavailable.
Check the :func:`on_server_unavailable` and :func:`on_server_available` events. Check the :func:`on_server_unavailable` and :func:`on_server_available` events.
large : bool large: bool
Indicates if the server is a 'large' server. A large server is defined as having Indicates if the server is a 'large' server. A large server is defined as having
more than ``large_threshold`` count members, which for this library is set to more than ``large_threshold`` count members, which for this library is set to
the maximum of 250. the maximum of 250.
@ -108,17 +108,18 @@ class Server(Hashable):
The server's invite splash. The server's invite splash.
""" """
__slots__ = ['afk_timeout', 'afk_channel', '_members', '_channels', 'icon', __slots__ = ('afk_timeout', 'afk_channel', '_members', '_channels', 'icon',
'name', 'id', 'owner', 'unavailable', 'name', 'region', 'name', 'id', 'owner', 'unavailable', 'name', 'region',
'_default_role', '_default_channel', 'roles', '_member_count', '_default_role', '_default_channel', 'roles', '_member_count',
'large', 'owner_id', 'mfa_level', 'emojis', 'features', 'large', 'owner_id', 'mfa_level', 'emojis', 'features',
'verification_level', 'splash' ] 'verification_level', 'splash' )
def __init__(self, **kwargs): def __init__(self, *, data, state):
self._channels = {} self._channels = {}
self.owner = None self.owner = None
self._members = {} self._members = {}
self._from_data(kwargs) self._state = state
self._from_data(data)
@property @property
def channels(self): def channels(self):
@ -197,9 +198,9 @@ class Server(Hashable):
self.icon = guild.get('icon') self.icon = guild.get('icon')
self.unavailable = guild.get('unavailable', False) self.unavailable = guild.get('unavailable', False)
self.id = guild['id'] self.id = guild['id']
self.roles = [Role(server=self, **r) for r in guild.get('roles', [])] self.roles = [Role(server=self, data=r, state=self._state) for r in guild.get('roles', [])]
self.mfa_level = guild.get('mfa_level') self.mfa_level = guild.get('mfa_level')
self.emojis = [Emoji(server=self, **r) for r in guild.get('emojis', [])] self.emojis = [Emoji(server=self, data=r, state=self._state) for r in guild.get('emojis', [])]
self.features = guild.get('features', []) self.features = guild.get('features', [])
self.splash = guild.get('splash') self.splash = guild.get('splash')
@ -211,8 +212,7 @@ class Server(Hashable):
roles.append(role) roles.append(role)
mdata['roles'] = roles mdata['roles'] = roles
member = Member(**mdata) member = Member(data=mdata, server=self, state=self._state)
member.server = self
self._add_member(member) self._add_member(member)
self._sync(guild) self._sync(guild)
@ -236,18 +236,14 @@ class Server(Hashable):
user_id = presence['user']['id'] user_id = presence['user']['id']
member = self.get_member(user_id) member = self.get_member(user_id)
if member is not None: if member is not None:
member.status = presence['status'] member.status = try_enum(Status, presence['status'])
try:
member.status = Status(member.status)
except:
pass
game = presence.get('game', {}) game = presence.get('game', {})
member.game = Game(**game) if game else None member.game = Game(**game) if game else None
if 'channels' in data: if 'channels' in data:
channels = data['channels'] channels = data['channels']
for c in channels: for c in channels:
channel = Channel(server=self, **c) channel = Channel(server=self, data=c, state=self._state)
self._add_channel(channel) self._add_channel(channel)
@ -311,7 +307,7 @@ class Server(Hashable):
Parameters Parameters
----------- -----------
name : str name: str
The name of the member to lookup with an optional discriminator. The name of the member to lookup with an optional discriminator.
Returns Returns

93
discord/state.py

@ -47,18 +47,20 @@ class ListenerType(enum.Enum):
chunk = 0 chunk = 0
Listener = namedtuple('Listener', ('type', 'future', 'predicate')) Listener = namedtuple('Listener', ('type', 'future', 'predicate'))
StateContext = namedtuple('StateContext', 'try_insert_user http')
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
ReadyState = namedtuple('ReadyState', ('launch', 'servers')) ReadyState = namedtuple('ReadyState', ('launch', 'servers'))
class ConnectionState: class ConnectionState:
def __init__(self, dispatch, chunker, syncer, max_messages, *, loop): def __init__(self, *, dispatch, chunker, syncer, http, loop, **options):
self.loop = loop self.loop = loop
self.max_messages = max_messages self.max_messages = max(options.get('max_messages', 5000), 100)
self.dispatch = dispatch self.dispatch = dispatch
self.chunker = chunker self.chunker = chunker
self.syncer = syncer self.syncer = syncer
self.is_bot = None self.is_bot = None
self._listeners = [] self._listeners = []
self.ctx = StateContext(try_insert_user=self.try_insert_user, http=http)
self.clear() self.clear()
def clear(self): def clear(self):
@ -66,6 +68,7 @@ class ConnectionState:
self.sequence = None self.sequence = None
self.session_id = None self.session_id = None
self._calls = {} self._calls = {}
self._users = {}
self._servers = {} self._servers = {}
self._voice_clients = {} self._voice_clients = {}
self._private_channels = {} self._private_channels = {}
@ -116,6 +119,15 @@ class ConnectionState:
for vc in self.voice_clients: for vc in self.voice_clients:
vc.main_ws = ws vc.main_ws = ws
def try_insert_user(self, data):
# this way is 300% faster than `dict.setdefault`.
user_id = data['id']
try:
return self._users[user_id]
except KeyError:
self._users[user_id] = user = User(state=self.ctx, data=data)
return user
@property @property
def servers(self): def servers(self):
return self._servers.values() return self._servers.values()
@ -153,7 +165,7 @@ class ConnectionState:
return utils.find(lambda m: m.id == msg_id, self.messages) return utils.find(lambda m: m.id == msg_id, self.messages)
def _add_server_from_data(self, guild): def _add_server_from_data(self, guild):
server = Server(**guild) server = Server(data=guild, state=self.ctx)
Server.me = property(lambda s: s.get_member(self.user.id)) Server.me = property(lambda s: s.get_member(self.user.id))
Server.voice_client = property(lambda s: self._get_voice_client(s.id)) Server.voice_client = property(lambda s: self._get_voice_client(s.id))
self._add_server(server) self._add_server(server)
@ -207,7 +219,7 @@ class ConnectionState:
def parse_ready(self, data): def parse_ready(self, data):
self._ready_state = ReadyState(launch=asyncio.Event(), servers=[]) self._ready_state = ReadyState(launch=asyncio.Event(), servers=[])
self.user = User(**data['user']) self.user = self.try_insert_user(data['user'])
guilds = data.get('guilds') guilds = data.get('guilds')
servers = self._ready_state.servers servers = self._ready_state.servers
@ -217,7 +229,7 @@ class ConnectionState:
servers.append(server) servers.append(server)
for pm in data.get('private_channels'): for pm in data.get('private_channels'):
self._add_private_channel(PrivateChannel(self.user, **pm)) self._add_private_channel(PrivateChannel(me=self.user, data=pm, state=self.ctx))
compat.create_task(self._delay_ready(), loop=self.loop) compat.create_task(self._delay_ready(), loop=self.loop)
@ -226,7 +238,7 @@ class ConnectionState:
def parse_message_create(self, data): def parse_message_create(self, data):
channel = self.get_channel(data.get('channel_id')) channel = self.get_channel(data.get('channel_id'))
message = self._create_message(channel=channel, **data) message = Message(channel=channel, data=data, state=self.ctx)
self.dispatch('message', message) self.dispatch('message', message)
self.messages.append(message) self.messages.append(message)
@ -255,7 +267,7 @@ class ConnectionState:
# embed only edit # embed only edit
message.embeds = data['embeds'] message.embeds = data['embeds']
else: else:
message._update(channel=message.channel, **data) message._update(channel=message.channel, data=data)
self.dispatch('message_edit', older_message, message) self.dispatch('message_edit', older_message, message)
@ -329,22 +341,11 @@ class ConnectionState:
server._add_member(member) server._add_member(member)
old_member = member._copy() old_member = member._copy()
member.status = data.get('status') member._presence_update(data=data, user=user)
try:
member.status = Status(member.status)
except:
pass
game = data.get('game', {})
member.game = Game(**game) if game else None
member.name = user.get('username', member.name)
member.avatar = user.get('avatar', member.avatar)
member.discriminator = user.get('discriminator', member.discriminator)
self.dispatch('member_update', old_member, member) self.dispatch('member_update', old_member, member)
def parse_user_update(self, data): def parse_user_update(self, data):
self.user = User(**data) self.user = User(state=self.ctx, data=data)
def parse_channel_delete(self, data): def parse_channel_delete(self, data):
server = self._get_server(data.get('guild_id')) server = self._get_server(data.get('guild_id'))
@ -361,7 +362,7 @@ class ConnectionState:
if channel_type is ChannelType.group: if channel_type is ChannelType.group:
channel = self._get_private_channel(channel_id) channel = self._get_private_channel(channel_id)
old_channel = copy.copy(channel) old_channel = copy.copy(channel)
channel._update_group(**data) channel._update_group(data)
self.dispatch('channel_update', old_channel, channel) self.dispatch('channel_update', old_channel, channel)
return return
@ -370,32 +371,32 @@ class ConnectionState:
channel = server.get_channel(channel_id) channel = server.get_channel(channel_id)
if channel is not None: if channel is not None:
old_channel = copy.copy(channel) old_channel = copy.copy(channel)
channel._update(server=server, **data) channel._update(server, data)
self.dispatch('channel_update', old_channel, channel) self.dispatch('channel_update', old_channel, channel)
def parse_channel_create(self, data): def parse_channel_create(self, data):
ch_type = try_enum(ChannelType, data.get('type')) ch_type = try_enum(ChannelType, data.get('type'))
channel = None channel = None
if ch_type in (ChannelType.group, ChannelType.private): if ch_type in (ChannelType.group, ChannelType.private):
channel = PrivateChannel(self.user, **data) channel = PrivateChannel(me=self.user, data=data, state=self.ctx)
self._add_private_channel(channel) self._add_private_channel(channel)
else: else:
server = self._get_server(data.get('guild_id')) server = self._get_server(data.get('guild_id'))
if server is not None: if server is not None:
channel = Channel(server=server, **data) channel = Channel(server=server, state=self.ctx, data=data)
server._add_channel(channel) server._add_channel(channel)
self.dispatch('channel_create', channel) self.dispatch('channel_create', channel)
def parse_channel_recipient_add(self, data): def parse_channel_recipient_add(self, data):
channel = self._get_private_channel(data.get('channel_id')) channel = self._get_private_channel(data.get('channel_id'))
user = User(**data.get('user', {})) user = self.try_insert_user(data['user'])
channel.recipients.append(user) channel.recipients.append(user)
self.dispatch('group_join', channel, user) self.dispatch('group_join', channel, user)
def parse_channel_recipient_remove(self, data): def parse_channel_recipient_remove(self, data):
channel = self._get_private_channel(data.get('channel_id')) channel = self._get_private_channel(data.get('channel_id'))
user = User(**data.get('user', {})) user = self.try_insert_user(data['user'])
try: try:
channel.recipients.remove(user) channel.recipients.remove(user)
except ValueError: except ValueError:
@ -411,7 +412,7 @@ class ConnectionState:
roles.append(role) roles.append(role)
data['roles'] = sorted(roles, key=lambda r: int(r.id)) data['roles'] = sorted(roles, key=lambda r: int(r.id))
return Member(server=server, **data) return Member(server=server, data=data, state=self.ctx)
def parse_guild_member_add(self, data): def parse_guild_member_add(self, data):
server = self._get_server(data.get('guild_id')) server = self._get_server(data.get('guild_id'))
@ -441,35 +442,18 @@ class ConnectionState:
def parse_guild_member_update(self, data): def parse_guild_member_update(self, data):
server = self._get_server(data.get('guild_id')) server = self._get_server(data.get('guild_id'))
user_id = data['user']['id'] user = data['user']
user_id = user['id']
member = server.get_member(user_id) member = server.get_member(user_id)
if member is not None: if member is not None:
user = data['user']
old_member = member._copy() old_member = member._copy()
member.name = user['username'] member._update(data, user)
member.discriminator = user['discriminator']
member.avatar = user['avatar']
member.bot = user.get('bot', False)
# the nickname change is optional,
# if it isn't in the payload then it didn't change
if 'nick' in data:
member.nick = data['nick']
# update the roles
member.roles = [server.default_role]
for role in server.roles:
if role.id in data['roles']:
member.roles.append(role)
# sort the roles by ID since they can be "randomised"
member.roles.sort(key=lambda r: int(r.id))
self.dispatch('member_update', old_member, member) self.dispatch('member_update', old_member, member)
def parse_guild_emojis_update(self, data): def parse_guild_emojis_update(self, data):
server = self._get_server(data.get('guild_id')) server = self._get_server(data.get('guild_id'))
before_emojis = server.emojis before_emojis = server.emojis
server.emojis = [Emoji(server=server, **e) for e in data.get('emojis', [])] server.emojis = [Emoji(server=server, data=e, state=self.ctx) for e in data.get('emojis', [])]
self.dispatch('server_emojis_update', before_emojis, server.emojis) self.dispatch('server_emojis_update', before_emojis, server.emojis)
def _get_create_server(self, data): def _get_create_server(self, data):
@ -584,13 +568,13 @@ class ConnectionState:
server = self._get_server(data.get('guild_id')) server = self._get_server(data.get('guild_id'))
if server is not None: if server is not None:
if 'user' in data: if 'user' in data:
user = User(**data['user']) user = self.try_insert_user(data['user'])
self.dispatch('member_unban', server, user) self.dispatch('member_unban', server, user)
def parse_guild_role_create(self, data): def parse_guild_role_create(self, data):
server = self._get_server(data.get('guild_id')) server = self._get_server(data['guild_id'])
role_data = data.get('role', {}) role_data = data['role']
role = Role(server=server, **role_data) role = Role(server=server, data=role_data, state=self.ctx)
server._add_role(role) server._add_role(role)
self.dispatch('server_role_create', role) self.dispatch('server_role_create', role)
@ -609,11 +593,12 @@ class ConnectionState:
def parse_guild_role_update(self, data): def parse_guild_role_update(self, data):
server = self._get_server(data.get('guild_id')) server = self._get_server(data.get('guild_id'))
if server is not None: if server is not None:
role_id = data['role']['id'] role_data = data['role']
role_id = role_data['id']
role = utils.find(lambda r: r.id == role_id, server.roles) role = utils.find(lambda r: r.id == role_id, server.roles)
if role is not None: if role is not None:
old_role = copy.copy(role) old_role = copy.copy(role)
role._update(**data['role']) role._update(role_data)
self.dispatch('server_role_update', old_role, role) self.dispatch('server_role_update', old_role, role)
def parse_guild_members_chunk(self, data): def parse_guild_members_chunk(self, data):

17
discord/user.py

@ -58,14 +58,15 @@ class User:
Specifies if the user is a bot account. Specifies if the user is a bot account.
""" """
__slots__ = ['name', 'id', 'discriminator', 'avatar', 'bot'] __slots__ = ['name', 'id', 'discriminator', 'avatar', 'bot', '_state']
def __init__(self, **kwargs): def __init__(self, *, state, data):
self.name = kwargs.get('username') self._state = state
self.id = kwargs.get('id') self.name = data['username']
self.discriminator = kwargs.get('discriminator') self.id = data['id']
self.avatar = kwargs.get('avatar') self.discriminator = data['discriminator']
self.bot = kwargs.get('bot', False) self.avatar = data['avatar']
self.bot = data.get('bot', False)
def __str__(self): def __str__(self):
return '{0.name}#{0.discriminator}'.format(self) return '{0.name}#{0.discriminator}'.format(self)

Loading…
Cancel
Save