Re-add support for reactions.

We now store emojis in a global cache and make things like adding
and removing reactions part of the stateful Message class.
This commit is contained in:
Rapptz
2016-11-11 03:12:43 -05:00
parent 59a0df5f98
commit c187d87dae
6 changed files with 238 additions and 88 deletions

View File

@ -27,7 +27,7 @@ DEALINGS IN THE SOFTWARE.
from .guild import Guild
from .user import User
from .game import Game
from .emoji import Emoji
from .emoji import Emoji, PartialEmoji
from .reaction import Reaction
from .message import Message
from .channel import *
@ -47,10 +47,16 @@ class ListenerType(enum.Enum):
chunk = 0
Listener = namedtuple('Listener', ('type', 'future', 'predicate'))
StateContext = namedtuple('StateContext', 'store_user http self_id')
log = logging.getLogger(__name__)
ReadyState = namedtuple('ReadyState', ('launch', 'guilds'))
class StateContext:
__slots__ = ('store_user', 'http', 'self_id', 'store_emoji', 'reaction_emoji')
def __init__(self, **kwargs):
for attr, value in kwargs.items():
setattr(self, attr, value)
class ConnectionState:
def __init__(self, *, dispatch, chunker, syncer, http, loop, **options):
self.loop = loop
@ -60,7 +66,10 @@ class ConnectionState:
self.syncer = syncer
self.is_bot = None
self._listeners = []
self.ctx = StateContext(store_user=self.store_user, http=http, self_id=None)
self.ctx = StateContext(store_user=self.store_user,
store_emoji=self.store_emoji,
reaction_emoji=self._get_reaction_emoji,
http=http, self_id=None)
self.clear()
def clear(self):
@ -69,6 +78,7 @@ class ConnectionState:
self.session_id = None
self._calls = {}
self._users = {}
self._emojis = {}
self._guilds = {}
self._voice_clients = {}
self._private_channels = {}
@ -128,6 +138,14 @@ class ConnectionState:
self._users[user_id] = user = User(state=self.ctx, data=data)
return user
def store_emoji(self, guild, data):
emoji_id = int(data['id'])
try:
return self._emojis[emoji_id]
except KeyError:
self._emojis[emoji_id] = emoji = Emoji(guild=guild, state=self.ctx, data=data)
return emoji
@property
def guilds(self):
return self._guilds.values()
@ -274,26 +292,11 @@ class ConnectionState:
self.dispatch('message_edit', older_message, message)
def parse_message_reaction_add(self, data):
message = self._get_message(data['message_id'])
message = self._get_message(int(data['message_id']))
if message is not None:
emoji = self._get_reaction_emoji(**data.pop('emoji'))
reaction = utils.get(message.reactions, emoji=emoji)
is_me = data['user_id'] == self.user.id
if not reaction:
reaction = Reaction(
message=message, emoji=emoji, me=is_me, **data)
message.reactions.append(reaction)
else:
reaction.count += 1
if is_me:
reaction.me = True
channel = self.get_channel(data['channel_id'])
member = self._get_member(channel, data['user_id'])
self.dispatch('reaction_add', reaction, member)
reaction = message._add_reaction(data)
user = self._get_reaction_user(message.channel, int(data['user_id']))
self.dispatch('reaction_add', reaction, user)
def parse_message_reaction_remove_all(self, data):
message = self._get_message(data['message_id'])
@ -303,26 +306,15 @@ class ConnectionState:
self.dispatch('reaction_clear', message, old_reactions)
def parse_message_reaction_remove(self, data):
message = self._get_message(data['message_id'])
message = self._get_message(int(data['message_id']))
if message is not None:
emoji = self._get_reaction_emoji(**data['emoji'])
reaction = utils.get(message.reactions, emoji=emoji)
# Eventual consistency means we can get out of order or duplicate removes.
if not reaction:
log.warning("Unexpected reaction remove {}".format(data))
return
reaction.count -= 1
if data['user_id'] == self.user.id:
reaction.me = False
if reaction.count == 0:
message.reactions.remove(reaction)
channel = self.get_channel(data['channel_id'])
member = self._get_member(channel, data['user_id'])
self.dispatch('reaction_remove', reaction, member)
try:
reaction = message._remove_reaction(data)
except (AttributeError, ValueError) as e: # eventual consistency lol
pass
else:
user = self._get_reaction_user(message.channel, int(data['user_id']))
self.dispatch('reaction_remove', reaction, user)
def parse_presence_update(self, data):
guild = self._get_guild(utils._get_as_snowflake(data, 'guild_id'))
@ -462,7 +454,7 @@ class ConnectionState:
def parse_guild_emojis_update(self, data):
guild = self._get_guild(int(data['guild_id']))
before_emojis = guild.emojis
guild.emojis = [Emoji(guild=guild, data=e, state=self.ctx) for e in data.get('emojis', [])]
guild.emojis = tuple(map(lambda d: self.store_emoji(guild, d), data['emojis']))
self.dispatch('guild_emojis_update', before_emojis, guild.emojis)
def _get_create_guild(self, data):
@ -675,35 +667,26 @@ class ConnectionState:
if call is not None:
self.dispatch('call_remove', call)
def _get_member(self, channel, id):
if channel.is_private:
return utils.get(channel.recipients, id=id)
def _get_reaction_user(self, channel, user_id):
if isinstance(channel, DMChannel) and user_id == channel.recipient.id:
return channel.recipient
elif isinstance(channel, TextChannel):
return channel.guild.get_member(user_id)
elif isinstance(channel, GroupChannel):
return utils.find(lambda m: m.id == user_id, channel.recipients)
else:
return channel.server.get_member(id)
return None
def _create_message(self, **message):
"""Helper mostly for injecting reactions."""
reactions = [
self._create_reaction(**r) for r in message.pop('reactions', [])
]
return Message(channel=message.pop('channel'),
reactions=reactions, **message)
def _get_reaction_emoji(self, data):
emoji_id = utils._get_as_snowflake(data, 'id')
def _create_reaction(self, **reaction):
emoji = self._get_reaction_emoji(**reaction.pop('emoji'))
return Reaction(emoji=emoji, **reaction)
def _get_reaction_emoji(self, **data):
id = data['id']
if not id:
if not emoji_id:
return data['name']
for server in self.servers:
for emoji in server.emojis:
if emoji.id == id:
return emoji
return Emoji(server=None, **data)
try:
return self._emojis[emoji_id]
except KeyError:
return PartialEmoji(id=emoji_id, name=data['name'])
def get_channel(self, id):
if id is None: