diff --git a/discord/calls.py b/discord/calls.py index 6a221e50a..94c55a146 100644 --- a/discord/calls.py +++ b/discord/calls.py @@ -112,7 +112,7 @@ class GroupCall: self.ringing = list(filter(None, map(lambda i: lookup.get(i), kwargs.get('ringing', [])))) def _update_voice_state(self, data): - user_id = data['user_id'] + user_id = int(data['user_id']) # left the voice channel? if data['channel_id'] is None: self._voice_states.pop(user_id, None) diff --git a/discord/channel.py b/discord/channel.py index 91bf878c9..87ce67efa 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -58,7 +58,7 @@ class Channel(Hashable): The channel name. server: :class:`Server` The server the channel belongs to. - id: str + id: int The channel ID. topic: Optional[str] The channel's topic. None if it doesn't exist. @@ -87,6 +87,7 @@ class Channel(Hashable): def __init__(self, *, state, server, data): self._state = state + self.id = int(data['id']) self._update(server, data) self.voice_members = [] @@ -96,7 +97,6 @@ class Channel(Hashable): def _update(self, server, data): self.server = server self.name = data['name'] - self.id = data['id'] self.topic = data.get('topic') self.position = data['position'] self.bitrate = data.get('bitrate') @@ -107,8 +107,8 @@ class Channel(Hashable): everyone_id = self.server.id for index, overridden in enumerate(data.get('permission_overwrites', [])): - overridden_id = overridden['id'] - self._permission_overwrites.append(Overwrites(**overridden)) + overridden_id = int(overridden.pop('id')) + self._permission_overwrites.append(Overwrites(id=overridden_id, **overridden)) if overridden['type'] == 'member': continue @@ -336,7 +336,7 @@ class PrivateChannel(Hashable): The users you are participating with in the private channel. me: :class:`User` The user presenting yourself. - id: str + id: int The private channel ID. is_private: bool ``True`` if the channel is a private channel (i.e. PM). ``True`` in this case. @@ -358,13 +358,13 @@ class PrivateChannel(Hashable): def __init__(self, *, me, state, data): self._state = state self.recipients = [state.try_insert_user(u) for u in data['recipients']] - self.id = data['id'] + self.id = int(data['id']) self.me = me - self.type = ChannelType(data['type']) + self.type = try_enum(ChannelType, data['type']) self._update_group(data) def _update_group(self, data): - owner_id = data.get('owner_id') + owner_id = utils._get_as_snowflake(data, 'owner_id') self.icon = data.get('icon') self.name = data.get('name') self.owner = utils.find(lambda u: u.id == owner_id, self.recipients) diff --git a/discord/emoji.py b/discord/emoji.py index 81d5dabac..65a276db5 100644 --- a/discord/emoji.py +++ b/discord/emoji.py @@ -54,17 +54,17 @@ class Emoji(Hashable): Attributes ----------- - name : str + name: str The name of the emoji. - id : str + id: int The emoji's ID. - require_colons : bool + require_colons: bool If colons are required to use this emoji in the client (:PJSalt: vs PJSalt). - managed : bool + managed: bool If this emoji is managed by a Twitch integration. - server : :class:`Server` + server: :class:`Server` The server the emoji belongs to. - roles : List[:class:`Role`] + roles: List[:class:`Role`] A list of :class:`Role` that is allowed to use this emoji. If roles is empty, the emoji is unrestricted. """ @@ -76,10 +76,10 @@ class Emoji(Hashable): self._from_data(data) def _from_data(self, emoji): - self.require_colons = emoji.get('require_colons') - self.managed = emoji.get('managed') - self.id = emoji.get('id') - self.name = emoji.get('name') + self.require_colons = emoji['require_colons'] + self.managed = emoji['managed'] + self.id = int(emoji['id']) + self.name = emoji['name'] self.roles = emoji.get('roles', []) if self.roles: roles = set(self.roles) diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index 04dae3e92..b57e9436b 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -90,7 +90,7 @@ class MemberConverter(IDConverter): else: result = _get_from_servers(bot, 'get_member_named', self.argument) else: - user_id = match.group(1) + user_id = int(match.group(1)) if server: result = server.get_member(user_id) else: @@ -118,7 +118,7 @@ class ChannelConverter(IDConverter): else: result = discord.utils.get(bot.get_all_channels(), name=self.argument) else: - channel_id = match.group(1) + channel_id = int(match.group(1)) if server: result = server.get_channel(channel_id) else: @@ -151,7 +151,7 @@ class RoleConverter(IDConverter): raise NoPrivateMessage() match = self._get_id_match() or re.match(r'<@&([0-9]+)>$', self.argument) - params = dict(id=match.group(1)) if match else dict(name=self.argument) + params = dict(id=int(match.group(1))) if match else dict(name=self.argument) result = discord.utils.get(server.roles, **params) if result is None: raise BadArgument('Role "{}" not found.'.format(self.argument)) @@ -187,7 +187,7 @@ class EmojiConverter(IDConverter): if result is None: result = discord.utils.get(bot.get_all_emojis(), name=self.argument) else: - emoji_id = match.group(1) + emoji_id = int(match.group(1)) # Try to look up emoji by id. if server: diff --git a/discord/message.py b/discord/message.py index 9497c2b2c..28ab18d1b 100644 --- a/discord/message.py +++ b/discord/message.py @@ -95,9 +95,9 @@ class Message: role_mentions: list A list of :class:`Role` that were mentioned. If the message is in a private message then the list is always empty. - id: str + id: int The message ID. - webhook_id: Optional[str] + webhook_id: Optional[int] If this message was sent by a webhook, then this is the webhook ID's that sent this message. attachments: list @@ -163,7 +163,7 @@ class Message: return for mention in mentions: - id_search = mention['id'] + id_search = int(mention['id']) member = self.server.get_member(id_search) if member is not None: self.mentions.append(member) @@ -185,7 +185,7 @@ class Message: # the author participants = [] - for uid in call.get('participants', []): + for uid in map(int, call.get('participants', [])): if uid == self.author.id: participants.append(self.author) else: @@ -204,21 +204,21 @@ class Message: This allows you receive the user IDs of mentioned users even in a private message context. """ - return re.findall(r'<@!?([0-9]+)>', self.content) + return [int(x) for x in re.findall(r'<@!?([0-9]+)>', self.content)] @utils.cached_slot_property('_cs_raw_channel_mentions') def raw_channel_mentions(self): """A property that returns an array of channel IDs matched with the syntax of <#channel_id> in the message content. """ - return re.findall(r'<#([0-9]+)>', self.content) + return [int(x) for x in re.findall(r'<#([0-9]+)>', self.content)] @utils.cached_slot_property('_cs_raw_role_mentions') def raw_role_mentions(self): """A property that returns an array of role IDs matched with the syntax of <@&role_id> in the message content. """ - return re.findall(r'<@&([0-9]+)>', self.content) + return [int(x) for x in re.findall(r'<@&([0-9]+)>', self.content)] @utils.cached_slot_property('_cs_channel_mentions') def channel_mentions(self): diff --git a/discord/role.py b/discord/role.py index eb111d339..daee2c925 100644 --- a/discord/role.py +++ b/discord/role.py @@ -56,25 +56,25 @@ class Role(Hashable): Attributes ---------- - id : str + id: int The ID for the role. - name : str + name: str The name of the role. - permissions : :class:`Permissions` + permissions: :class:`Permissions` Represents the role's permissions. - server : :class:`Server` + server: :class:`Server` The server the role belongs to. - colour : :class:`Colour` + colour: :class:`Colour` Represents the role colour. An alias exists under ``color``. - hoist : bool + hoist: bool Indicates if the role will be displayed separately from other members. - position : int + position: int The position of the role. This number is usually positive. The bottom role has a position of 0. - managed : bool + managed: bool Indicates if the role is managed by the server through some form of integrations such as Twitch. - mentionable : bool + mentionable: bool Indicates if the role can be mentioned by users. """ @@ -84,6 +84,7 @@ class Role(Hashable): def __init__(self, *, server, state, data): self.server = server self._state = state + self.id = int(data['id']) self._update(data) def __str__(self): @@ -120,7 +121,6 @@ class Role(Hashable): return not r def _update(self, data): - self.id = data['id'] self.name = data['name'] self.permissions = Permissions(data.get('permissions', 0)) self.position = data.get('position', 0) diff --git a/discord/server.py b/discord/server.py index fb572fc5b..7d4cb4683 100644 --- a/discord/server.py +++ b/discord/server.py @@ -76,10 +76,10 @@ class Server(Hashable): An iterable of :class:`Channel` that are currently on the server. icon: str The server's icon. - id: str + id: int The server's ID. - owner: :class:`Member` - The member who owns the server. + owner_id: int + The server owner's ID. Use :attr:`Server.owner` instead. unavailable: bool 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 @@ -111,14 +111,13 @@ class Server(Hashable): """ __slots__ = ('afk_timeout', 'afk_channel', '_members', '_channels', 'icon', - 'name', 'id', 'owner', 'unavailable', 'name', 'region', + 'name', 'id', 'unavailable', 'name', 'region', '_default_role', '_default_channel', 'roles', '_member_count', 'large', 'owner_id', 'mfa_level', 'emojis', 'features', 'verification_level', 'splash', '_voice_states' ) def __init__(self, *, data, state): self._channels = {} - self.owner = None self._members = {} self._voice_states = {} self._state = state @@ -158,9 +157,9 @@ class Server(Hashable): def __str__(self): return self.name - def _update_voice_state(self, data): - user_id = data['user_id'] - channel = self.get_channel(data['channel_id']) + def _update_voice_state(self, data, channel_id): + user_id = int(data['user_id']) + channel = self.get_channel(channel_id) try: # check if we should remove the voice state from cache if channel is None: @@ -230,7 +229,7 @@ class Server(Hashable): self.afk_timeout = guild.get('afk_timeout') self.icon = guild.get('icon') self.unavailable = guild.get('unavailable', False) - self.id = guild['id'] + self.id = int(guild['id']) self.roles = [Role(server=self, data=r, state=self._state) for r in guild.get('roles', [])] self.mfa_level = guild.get('mfa_level') self.emojis = [Emoji(server=self, data=r, state=self._state) for r in guild.get('emojis', [])] @@ -251,22 +250,20 @@ class Server(Hashable): self._sync(guild) self.large = None if member_count is None else self._member_count >= 250 - if 'owner_id' in guild: - self.owner_id = guild['owner_id'] - self.owner = self.get_member(self.owner_id) - - afk_id = guild.get('afk_channel_id') - self.afk_channel = self.get_channel(afk_id) + self.owner_id = utils._get_as_snowflake(guild, 'owner_id') + self.afk_channel = self.get_channel(utils._get_as_snowflake(guild, 'afk_channel_id')) for obj in guild.get('voice_states', []): - self._update_voice_state(obj) + self._update_voice_state(obj, int(obj['channel_id'])) def _sync(self, data): - if 'large' in data: + try: self.large = data['large'] + except KeyError: + pass for presence in data.get('presences', []): - user_id = presence['user']['id'] + user_id = int(presence['user']['id']) member = self.get_member(user_id) if member is not None: member.status = try_enum(Status, presence['status']) @@ -279,7 +276,6 @@ class Server(Hashable): channel = Channel(server=self, data=c, state=self._state) self._add_channel(channel) - @utils.cached_slot_property('_default_role') def default_role(self): """Gets the @everyone role that all members have by default.""" @@ -290,6 +286,11 @@ class Server(Hashable): """Gets the default :class:`Channel` for the server.""" return utils.find(lambda c: c.is_default, self.channels) + @property + def owner(self): + """:class:`Member`: The member that owns the server.""" + return self.get_member(self.owner_id) + @property def icon_url(self): """Returns the URL version of the server's icon. Returns an empty string if it has no icon.""" diff --git a/discord/state.py b/discord/state.py index ff28c496e..ad9bb172c 100644 --- a/discord/state.py +++ b/discord/state.py @@ -121,7 +121,7 @@ class ConnectionState: def try_insert_user(self, data): # this way is 300% faster than `dict.setdefault`. - user_id = data['id'] + user_id = int(data['id']) try: return self._users[user_id] except KeyError: @@ -237,27 +237,27 @@ class ConnectionState: self.dispatch('resumed') def parse_message_create(self, data): - channel = self.get_channel(data.get('channel_id')) + channel = self.get_channel(int(data['channel_id'])) message = Message(channel=channel, data=data, state=self.ctx) self.dispatch('message', message) self.messages.append(message) def parse_message_delete(self, data): - message_id = data.get('id') + message_id = int(data['id']) found = self._get_message(message_id) if found is not None: self.dispatch('message_delete', found) self.messages.remove(found) def parse_message_delete_bulk(self, data): - message_ids = set(data.get('ids', [])) + message_ids = set(map(int, data.get('ids', []))) to_be_deleted = list(filter(lambda m: m.id in message_ids, self.messages)) for msg in to_be_deleted: self.dispatch('message_delete', msg) self.messages.remove(msg) def parse_message_update(self, data): - message = self._get_message(data.get('id')) + message = self._get_message(int(data['id'])) if message is not None: older_message = copy.copy(message) if 'call' in data: @@ -323,7 +323,7 @@ class ConnectionState: self.dispatch('reaction_remove', reaction, member) def parse_presence_update(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(utils._get_as_snowflake(data, 'guild_id')) if server is None: return @@ -348,7 +348,7 @@ class ConnectionState: self.user = User(state=self.ctx, data=data) def parse_channel_delete(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) if server is not None: channel_id = data.get('id') channel = server.get_channel(channel_id) @@ -358,7 +358,7 @@ class ConnectionState: def parse_channel_update(self, data): channel_type = try_enum(ChannelType, data.get('type')) - channel_id = data.get('id') + channel_id = int(data['id']) if channel_type is ChannelType.group: channel = self._get_private_channel(channel_id) old_channel = copy.copy(channel) @@ -366,7 +366,7 @@ class ConnectionState: self.dispatch('channel_update', old_channel, channel) return - server = self._get_server(data.get('guild_id')) + server = self._get_server(utils._get_as_snowflake(data, 'guild_id')) if server is not None: channel = server.get_channel(channel_id) if channel is not None: @@ -381,7 +381,7 @@ class ConnectionState: channel = PrivateChannel(me=self.user, data=data, state=self.ctx) self._add_private_channel(channel) else: - server = self._get_server(data.get('guild_id')) + server = self._get_server(utils._get_as_snowflake(data, 'guild_id')) if server is not None: channel = Channel(server=server, state=self.ctx, data=data) server._add_channel(channel) @@ -389,13 +389,13 @@ class ConnectionState: self.dispatch('channel_create', channel) def parse_channel_recipient_add(self, data): - channel = self._get_private_channel(data.get('channel_id')) + channel = self._get_private_channel(int(data['channel_id'])) user = self.try_insert_user(data['user']) channel.recipients.append(user) self.dispatch('group_join', channel, user) def parse_channel_recipient_remove(self, data): - channel = self._get_private_channel(data.get('channel_id')) + channel = self._get_private_channel(int(data['channel_id'])) user = self.try_insert_user(data['user']) try: channel.recipients.remove(user) @@ -411,18 +411,18 @@ class ConnectionState: if role is not None: roles.append(role) - data['roles'] = sorted(roles, key=lambda r: int(r.id)) + data['roles'] = sorted(roles, key=lambda r: r.id) return Member(server=server, data=data, state=self.ctx) def parse_guild_member_add(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) member = self._make_member(server, data) server._add_member(member) server._member_count += 1 self.dispatch('member_join', member) def parse_guild_member_remove(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) if server is not None: user_id = data['user']['id'] member = server.get_member(user_id) @@ -443,7 +443,7 @@ class ConnectionState: self.dispatch('member_remove', member) def parse_guild_member_update(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) user = data['user'] user_id = user['id'] member = server.get_member(user_id) @@ -453,7 +453,7 @@ class ConnectionState: self.dispatch('member_update', old_member, member) def parse_guild_emojis_update(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) before_emojis = server.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) @@ -525,18 +525,18 @@ class ConnectionState: self.dispatch('server_join', server) def parse_guild_sync(self, data): - server = self._get_server(data.get('id')) + server = self._get_server(int(data['id'])) server._sync(data) def parse_guild_update(self, data): - server = self._get_server(data.get('id')) + server = self._get_server(int(data['id'])) if server is not None: old_server = copy.copy(server) server._from_data(data) self.dispatch('server_update', old_server, server) def parse_guild_delete(self, data): - server = self._get_server(data.get('id')) + server = self._get_server(int(data['id'])) if server is None: return @@ -559,7 +559,7 @@ class ConnectionState: # hence we don't remove it from cache or do anything # strange with it, the main purpose of this event # is mainly to dispatch to another event worth listening to for logging - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) if server is not None: user_id = data.get('user', {}).get('id') member = utils.get(server.members, id=user_id) @@ -567,21 +567,21 @@ class ConnectionState: self.dispatch('member_ban', member) def parse_guild_ban_remove(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) if server is not None: if 'user' in data: user = self.try_insert_user(data['user']) self.dispatch('member_unban', server, user) def parse_guild_role_create(self, data): - server = self._get_server(data['guild_id']) + server = self._get_server(int(data['guild_id'])) role_data = data['role'] role = Role(server=server, data=role_data, state=self.ctx) server._add_role(role) self.dispatch('server_role_create', role) def parse_guild_role_delete(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) if server is not None: role_id = data.get('role_id') role = utils.find(lambda r: r.id == role_id, server.roles) @@ -593,7 +593,7 @@ class ConnectionState: self.dispatch('server_role_delete', role) def parse_guild_role_update(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) if server is not None: role_data = data['role'] role_id = role_data['id'] @@ -604,7 +604,7 @@ class ConnectionState: self.dispatch('server_role_update', old_role, role) def parse_guild_members_chunk(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(int(data['guild_id'])) members = data.get('members', []) for member in members: m = self._make_member(server, member) @@ -612,35 +612,32 @@ class ConnectionState: if existing is None or existing.joined_at is None: server._add_member(m) - # if the owner is offline, server.owner is potentially None - # therefore we should check if this chunk makes it point to a valid - # member. - server.owner = server.get_member(server.owner_id) log.info('processed a chunk for {} members.'.format(len(members))) self.process_listeners(ListenerType.chunk, server, len(members)) def parse_voice_state_update(self, data): - server = self._get_server(data.get('guild_id')) + server = self._get_server(utils._get_as_snowflake(data, 'guild_id')) + channel_id = utils._get_as_snowflake(data, 'channel_id') if server is not None: - if data.get('user_id') == self.user.id: + if int(data['user_id']) == self.user.id: voice = self._get_voice_client(server.id) if voice is not None: - voice.channel = server.get_channel(data.get('channel_id')) + voice.channel = server.get_channel(channel_id) - member, before, after = server._update_voice_state(data) + member, before, after = server._update_voice_state(data, channel_id) if after is not None: self.dispatch('voice_state_update', member, before, after) else: # in here we're either at private or group calls - call = self._calls.get(data.get('channel_id'), None) + call = self._calls.get(channel_id) if call is not None: call._update_voice_state(data) def parse_typing_start(self, data): - channel = self.get_channel(data.get('channel_id')) + channel = self.get_channel(int(data['channel_id'])) if channel is not None: member = None - user_id = data.get('user_id') + user_id = utils._get_as_snowflake(data, 'user_id') is_private = getattr(channel, 'is_private', None) if is_private == None: return @@ -655,21 +652,21 @@ class ConnectionState: self.dispatch('typing', channel, member, timestamp) def parse_call_create(self, data): - message = self._get_message(data.get('message_id')) + message = self._get_message(int(data['message_id'])) if message is not None: call = GroupCall(call=message, **data) - self._calls[data['channel_id']] = call + self._calls[int(data['channel_id'])] = call self.dispatch('call', call) def parse_call_update(self, data): - call = self._calls.get(data.get('channel_id'), None) + call = self._calls.get(int(data['channel_id'])) if call is not None: before = copy.copy(call) call._update(**data) self.dispatch('call_update', before, call) def parse_call_delete(self, data): - call = self._calls.pop(data.get('channel_id'), None) + call = self._calls.pop(int(data['channel_id']), None) if call is not None: self.dispatch('call_remove', call) diff --git a/discord/user.py b/discord/user.py index 919d6deba..fa3618374 100644 --- a/discord/user.py +++ b/discord/user.py @@ -46,15 +46,15 @@ class User: Attributes ----------- - name : str + name: str The user's username. - id : str + id: int The user's unique ID. - discriminator : str or int + discriminator: str The user's discriminator. This is given when the username has conflicts. - avatar : str + avatar: str The avatar hash the user has. Could be None. - bot : bool + bot: bool Specifies if the user is a bot account. """ @@ -63,7 +63,7 @@ class User: def __init__(self, *, state, data): self._state = state self.name = data['username'] - self.id = data['id'] + self.id = int(data['id']) self.discriminator = data['discriminator'] self.avatar = data['avatar'] self.bot = data.get('bot', False) diff --git a/discord/utils.py b/discord/utils.py index 831296541..5b19c8c3e 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -120,7 +120,7 @@ def oauth_url(client_id, permissions=None, server=None, redirect_uri=None): def snowflake_time(id): """Returns the creation date in UTC of a discord id.""" - return datetime.datetime.utcfromtimestamp(((int(id) >> 22) + DISCORD_EPOCH) / 1000) + return datetime.datetime.utcfromtimestamp(((id >> 22) + DISCORD_EPOCH) / 1000) def time_snowflake(datetime_obj, high=False): """Returns a numeric snowflake pretending to be created at the given date. @@ -231,8 +231,15 @@ def _unique(iterable): adder = seen.add return [x for x in iterable if not (x in seen or adder(x))] -def _null_event(*args, **kwargs): - pass +def _get_as_snowflake(data, key): + try: + value = data[key] + except KeyError: + return None + else: + if value is None: + return value + return int(value) def _get_mime_type_for_image(data): if data.startswith(b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A'):