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

@ -29,10 +29,12 @@ import re
from .user import User
from .reaction import Reaction
from .emoji import Emoji
from . import utils, abc
from .object import Object
from .calls import CallMessage
from .enums import MessageType, try_enum
from .errors import InvalidArgument
class Message:
"""Represents a message from Discord.
@ -66,8 +68,6 @@ class Message:
In :issue:`very rare cases <21>` this could be a :class:`Object` instead.
For the sake of convenience, this :class:`Object` instance has an attribute ``is_private`` set to ``True``.
guild: Optional[:class:`Guild`]
The guild that the message belongs to. If not applicable (i.e. a PM) then it's None instead.
call: Optional[:class:`CallMessage`]
The call that the message refers to. This is only applicable to messages of type
:attr:`MessageType.call`.
@ -112,16 +112,15 @@ class Message:
__slots__ = ( 'edited_timestamp', 'tts', 'content', 'channel', 'webhook_id',
'mention_everyone', 'embeds', 'id', 'mentions', 'author',
'_cs_channel_mentions', 'guild', '_cs_raw_mentions', 'attachments',
'_cs_channel_mentions', '_cs_raw_mentions', 'attachments',
'_cs_clean_content', '_cs_raw_channel_mentions', 'nonce', 'pinned',
'role_mentions', '_cs_raw_role_mentions', 'type', 'call',
'_cs_system_content', '_state', 'reactions' )
def __init__(self, *, state, channel, data):
self._state = state
self.reactions = kwargs.pop('reactions')
for reaction in self.reactions:
reaction.message = self
self.id = int(data['id'])
self.reactions = [Reaction(message=self, data=d) for d in data.get('reactions', [])]
self._update(channel, data)
def _try_patch(self, data, key, transform):
@ -132,6 +131,41 @@ class Message:
else:
setattr(self, key, transform(value))
def _add_reaction(self, data):
emoji = self._state.reaction_emoji(data['emoji'])
reaction = utils.find(lambda r: r.emoji == emoji, self.reactions)
is_me = data['me'] = int(data['user_id']) == self._state.self_id
if reaction is None:
reaction = Reaction(message=self, data=data, emoji=emoji)
self.reactions.append(reaction)
else:
reaction.count += 1
if is_me:
reaction.me = is_me
return reaction
def _remove_reaction(self, data):
emoji = self._state.reaction_emoji(data['emoji'])
reaction = utils.find(lambda r: r.emoji == emoji, self.reactions)
if reaction is None:
# already removed?
raise ValueError('Emoji already removed?')
# if reaction isn't in the list, we crash. This means discord
# sent bad data, or we stored improperly
reaction.count -= 1
if int(data['user_id']) == self._state.self_id:
reaction.me = False
if reaction.count == 0:
# this raises ValueError if something went wrong as well.
self.reactions.remove(reaction)
return reaction
def _update(self, channel, data):
self.channel = channel
for handler in ('mentions', 'mention_roles', 'call'):
@ -198,6 +232,11 @@ class Message:
call['participants'] = participants
self.call = CallMessage(message=self, **call)
@property
def guild(self):
"""Optional[:class:`Guild`]: The guild that the message belongs to, if applicable."""
return getattr(self.channel, 'guild', None)
@utils.cached_slot_property('_cs_raw_mentions')
def raw_mentions(self):
"""A property that returns an array of user IDs matched with
@ -428,3 +467,82 @@ class Message:
yield from self._state.http.unpin_message(self.channel.id, self.id)
self.pinned = False
@asyncio.coroutine
def add_reaction(self, emoji):
"""|coro|
Add a reaction to the message.
The emoji may be a unicode emoji or a custom server :class:`Emoji`.
You must have the :attr:`Permissions.add_reactions` permission to
add new reactions to a message.
Parameters
------------
emoji: :class:`Emoji` or str
The emoji to react with.
Raises
--------
HTTPException
Adding the reaction failed.
Forbidden
You do not have the proper permissions to react to the message.
NotFound
The emoji you specified was not found.
InvalidArgument
The emoji parameter is invalid.
"""
if isinstance(emoji, Emoji):
emoji = '%s:%s' % (emoji.name, emoji.id)
elif isinstance(emoji, str):
pass # this is okay
else:
raise InvalidArgument('emoji argument must be a string or discord.Emoji')
yield from self._state.http.add_reaction(self.id, self.channel.id, emoji)
@asyncio.coroutine
def remove_reaction(self, emoji, member):
"""|coro|
Remove a reaction by the member from the message.
The emoji may be a unicode emoji or a custom server :class:`Emoji`.
If the reaction is not your own (i.e. ``member`` parameter is not you) then
the :attr:`Permissions.manage_messages` permission is needed.
The ``member`` parameter must represent a member and meet
the :class:`abc.Snowflake` abc.
Parameters
------------
emoji: :class:`Emoji` or str
The emoji to remove.
member: :class:`abc.Snowflake`
The member for which to remove the reaction.
Raises
--------
HTTPException
Removing the reaction failed.
Forbidden
You do not have the proper permissions to remove the reaction.
NotFound
The member or emoji you specified was not found.
InvalidArgument
The emoji parameter is invalid.
"""
if isinstance(emoji, Emoji):
emoji = '%s:%s' % (emoji.name, emoji.id)
elif isinstance(emoji, str):
pass # this is okay
else:
raise InvalidArgument('emoji argument must be a string or discord.Emoji')
yield from self._state.http.remove_reaction(self.id, self.channel.id, emoji, member.id)