aboutsummaryrefslogtreecommitdiff
path: root/discord/state.py
diff options
context:
space:
mode:
authorRapptz <[email protected]>2015-12-04 00:16:34 -0500
committerRapptz <[email protected]>2015-12-04 00:16:34 -0500
commitf197c345832ff09fe081b7cb542de9288d0b780e (patch)
tree868d84f0708f9ceb9d3395bfd0223ef3c59a2514 /discord/state.py
parentPoint to the docs in the README and clear up notes on breaking changes (diff)
downloaddiscord.py-f197c345832ff09fe081b7cb542de9288d0b780e.tar.xz
discord.py-f197c345832ff09fe081b7cb542de9288d0b780e.zip
Begin working on asyncio port.
Diffstat (limited to 'discord/state.py')
-rw-r--r--discord/state.py295
1 files changed, 295 insertions, 0 deletions
diff --git a/discord/state.py b/discord/state.py
new file mode 100644
index 00000000..b62817a7
--- /dev/null
+++ b/discord/state.py
@@ -0,0 +1,295 @@
+# -*- coding: utf-8 -*-
+
+"""
+The MIT License (MIT)
+
+Copyright (c) 2015 Rapptz
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+"""
+
+from .server import Server
+from .user import User
+from .message import Message
+from .channel import Channel, PrivateChannel
+from .member import Member
+from .role import Role
+from . import utils
+
+from collections import deque
+import copy
+import datetime
+
+class ConnectionState:
+ def __init__(self, dispatch, max_messages):
+ self.user = None
+ self.email = None
+ self.servers = []
+ self.private_channels = []
+ self.messages = deque(maxlen=max_messages)
+ self.dispatch = dispatch
+
+ def _get_message(self, msg_id):
+ return utils.find(lambda m: m.id == msg_id, self.messages)
+
+ def _get_server(self, guild_id):
+ return utils.find(lambda g: g.id == guild_id, self.servers)
+
+ def _add_server(self, guild):
+ server = Server(**guild)
+ self.servers.append(server)
+
+ def parse_ready(self, data):
+ self.user = User(**data['user'])
+ guilds = data.get('guilds')
+
+ for guild in guilds:
+ if guild.get('unavailable', False):
+ continue
+ self._add_server(guild)
+
+ for pm in data.get('private_channels'):
+ self.private_channels.append(PrivateChannel(id=pm['id'],
+ user=User(**pm['recipient'])))
+
+ # we're all ready
+ self.dispatch('ready')
+
+ def parse_message_create(self, data):
+ channel = self.get_channel(data.get('channel_id'))
+ message = Message(channel=channel, **data)
+ self.dispatch('message', message)
+ self.messages.append(message)
+
+ def parse_message_delete(self, data):
+ channel = self.get_channel(data.get('channel_id'))
+ message_id = data.get('id')
+ found = self._get_message(message_id)
+ if found is not None:
+ self.dispatch('message_delete', found)
+ self.messages.remove(found)
+
+ def parse_message_update(self, data):
+ older_message = self._get_message(data.get('id'))
+ if older_message is not None:
+ # create a copy of the new message
+ message = copy.copy(older_message)
+ # update the new update
+ for attr in data:
+ if attr == 'channel_id' or attr == 'author':
+ continue
+ value = data[attr]
+ if 'time' in attr:
+ setattr(message, attr, utils.parse_time(value))
+ else:
+ setattr(message, attr, value)
+ self.dispatch('message_edit', older_message, message)
+ # update the older message
+ older_message = message
+
+ def parse_presence_update(self, data):
+ server = self._get_server(data.get('guild_id'))
+ if server is not None:
+ status = data.get('status')
+ user = data['user']
+ member_id = user['id']
+ member = utils.find(lambda m: m.id == member_id, server.members)
+ if member is not None:
+ old_member = copy.copy(member)
+ member.status = data.get('status')
+ member.game_id = data.get('game_id')
+ member.name = user.get('username', member.name)
+ member.avatar = user.get('avatar', member.avatar)
+
+ # call the event now
+ self.dispatch('status', member, old_member.game_id, old_member.status)
+ self.dispatch('member_update', old_member, member)
+
+ def parse_user_update(self, data):
+ self.user = User(**data)
+
+ def parse_channel_delete(self, data):
+ server = self._get_server(data.get('guild_id'))
+ if server is not None:
+ channel_id = data.get('id')
+ channel = utils.find(lambda c: c.id == channel_id, server.channels)
+ try:
+ server.channels.remove(channel)
+ self.dispatch('channel_delete', channel)
+ except ValueError:
+ return
+
+ def parse_channel_update(self, data):
+ server = self._get_server(data.get('guild_id'))
+ if server is not None:
+ channel_id = data.get('id')
+ channel = utils.find(lambda c: c.id == channel_id, server.channels)
+ channel.update(server=server, **data)
+ self.dispatch('channel_update', channel)
+
+ def parse_channel_create(self, data):
+ 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)
+ self.private_channels.append(channel)
+ else:
+ server = self._get_server(data.get('guild_id'))
+ if server is not None:
+ channel = Channel(server=server, **data)
+ server.channels.append(channel)
+
+ self.dispatch('channel_create', channel)
+
+ def parse_guild_member_add(self, data):
+ server = self._get_server(data.get('guild_id'))
+ member = Member(server=server, deaf=False, mute=False, **data)
+ server.members.append(member)
+ self.dispatch('member_join', member)
+
+ def parse_guild_member_remove(self, data):
+ server = self._get_server(data.get('guild_id'))
+ if server is not None:
+ user_id = data['user']['id']
+ member = utils.find(lambda m: m.id == user_id, server.members)
+ try:
+ server.members.remove(member)
+ self.dispatch('member_remove', member)
+ except ValueError:
+ return
+
+ def parse_guild_member_update(self, data):
+ server = self._get_server(data.get('guild_id'))
+ user_id = data['user']['id']
+ member = utils.find(lambda m: m.id == user_id, server.members)
+ if member is not None:
+ user = data['user']
+ old_member = copy.copy(member)
+ member.name = user['username']
+ member.discriminator = user['discriminator']
+ member.avatar = user['avatar']
+ member.roles = []
+ # update the roles
+ for role in server.roles:
+ if role.id in data['roles']:
+ member.roles.append(role)
+
+ self.dispatch('member_update', old_member, member)
+
+ def parse_guild_create(self, data):
+ unavailable = data.get('unavailable')
+ if unavailable == False:
+ # GUILD_CREATE with unavailable in the response
+ # usually means that the server has become available
+ # and is therefore in the cache
+ server = self._get_server(data.get('id'))
+ if server is not None:
+ server.unavailable = False
+ self.dispatch('server_available', server)
+ return
+
+ if unavailable == True:
+ # joined a server with unavailable == True so..
+ return
+
+ # if we're at this point then it was probably
+ # unavailable during the READY event and is now
+ # available, so it isn't in the cache...
+
+ self._add_server(data)
+ self.dispatch('server_join', self.servers[-1])
+
+ def parse_guild_delete(self, data):
+ server = self._get_server(data.get('id'))
+ if data.get('unavailable', False) and server is not None:
+ # GUILD_DELETE with unavailable being True means that the
+ # server that was available is now currently unavailable
+ server.unavailable = True
+ self.dispatch('server_unavailable', server)
+ return
+
+ try:
+ self.servers.remove(server)
+ self.dispatch('server_remove', server)
+ except ValueError:
+ return
+
+ def parse_guild_role_create(self, data):
+ server = self._get_server(data.get('guild_id'))
+ role_data = data.get('role', {})
+ everyone = server.id == role_data.get('id')
+ role = Role(everyone=everyone, **role_data)
+ server.roles.append(role)
+ self.dispatch('server_role_create', server, role)
+
+ def parse_guild_role_delete(self, data):
+ server = self._get_server(data.get('guild_id'))
+ if server is not None:
+ role_id = data.get('role_id')
+ role = utils.find(lambda r: r.id == role_id, server.roles)
+ server.roles.remove(role)
+ self.dispatch('server_role_delete', server, role)
+
+ def parse_guild_role_update(self, data):
+ server = self._get_server(data.get('guild_id'))
+ if server is not None:
+ role_id = data['role']['id']
+ role = utils.find(lambda r: r.id == role_id, server.roles)
+ role.update(**data['role'])
+ self.dispatch('server_role_update', role)
+
+ def parse_voice_state_update(self, data):
+ server = self._get_server(data.get('guild_id'))
+ if server is not None:
+ updated_member = server._update_voice_state(data)
+ self.dispatch('voice_state_update', updated_member)
+
+ def parse_typing_start(self, data):
+ channel = self.get_channel(data.get('channel_id'))
+ if channel is not None:
+ member = None
+ user_id = data.get('user_id')
+ is_private = getattr(channel, 'is_private', None)
+ if is_private == None:
+ return
+
+ if is_private:
+ member = channel.user
+ else:
+ members = channel.server.members
+ member = utils.find(lambda m: m.id == user_id, members)
+
+ if member is not None:
+ timestamp = datetime.datetime.utcfromtimestamp(data.get('timestamp'))
+ self.dispatch('typing', channel, member, timestamp)
+
+ def get_channel(self, id):
+ if id is None:
+ return None
+
+ for server in self.servers:
+ for channel in server.channels:
+ if channel.id == id:
+ return channel
+
+ for pm in self.private_channels:
+ if pm.id == id:
+ return pm