diff --git a/discord/http.py b/discord/http.py index 0c0a5497d..3789b3c86 100644 --- a/discord/http.py +++ b/discord/http.py @@ -402,6 +402,11 @@ class HTTPClient: return self.request(r) + def clear_single_reaction(self, channel_id, message_id, emoji): + r = Route('DELETE', '/channels/{channel_id}/messages/{message_id}/reactions/{emoji}', + channel_id=channel_id, message_id=message_id, emoji=emoji) + return self.request(r) + def get_message(self, channel_id, message_id): r = Route('GET', '/channels/{channel_id}/messages/{message_id}', channel_id=channel_id, message_id=message_id) return self.request(r) @@ -425,7 +430,7 @@ class HTTPClient: return self.request(Route('GET', '/channels/{channel_id}/messages', channel_id=channel_id), params=params) def publish_message(self, channel_id, message_id): - return self.request(Route('POST', '/channels/{channel_id}/messages/{message_id}/crosspost', + return self.request(Route('POST', '/channels/{channel_id}/messages/{message_id}/crosspost', channel_id=channel_id, message_id=message_id)) def pin_message(self, channel_id, message_id): diff --git a/discord/message.py b/discord/message.py index e2ae71930..4010a2c3a 100644 --- a/discord/message.py +++ b/discord/message.py @@ -371,6 +371,18 @@ class Message: return reaction + def _clear_emoji(self, emoji): + to_check = str(emoji) + for index, reaction in enumerate(self.reactions): + if str(reaction.emoji) == to_check: + break + else: + # didn't find anything so just return + return + + del self.reactions[index] + return reaction + def _update(self, data): handlers = self._HANDLERS for key, value in data.items(): @@ -945,6 +957,37 @@ class Message: else: await self._state.http.remove_reaction(self.channel.id, self.id, emoji, member.id) + async def clear_reaction(self, emoji): + """|coro| + + Clears a specific reaction from the message. + + The emoji may be a unicode emoji or a custom guild :class:`Emoji`. + + You need the :attr:`~Permissions.manage_messages` permission to use this. + + .. versionadded:: 1.3 + + Parameters + ----------- + emoji: Union[:class:`Emoji`, :class:`Reaction`, :class:`PartialEmoji`, :class:`str`] + The emoji to clear. + + Raises + -------- + HTTPException + Clearing the reaction failed. + Forbidden + You do not have the proper permissions to clear the reaction. + NotFound + The emoji you specified was not found. + InvalidArgument + The emoji parameter is invalid. + """ + + emoji = self._emoji_reaction(emoji) + await self._state.http.clear_single_reaction(self.channel.id, self.id, emoji) + @staticmethod def _emoji_reaction(emoji): if isinstance(emoji, Reaction): diff --git a/discord/raw_models.py b/discord/raw_models.py index 1f3185642..dc094e342 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -177,3 +177,32 @@ class RawReactionClearEvent(_RawReprMixin): self.guild_id = int(data['guild_id']) except KeyError: self.guild_id = None + +class RawReactionClearEmojiEvent(_RawReprMixin): + """Represents the payload for a :func:`on_raw_reaction_clear_emoji` event. + + .. versionadded:: 1.3.0 + + Attributes + ----------- + message_id: :class:`int` + The message ID that got its reactions cleared. + channel_id: :class:`int` + The channel ID where the reactions got cleared. + guild_id: Optional[:class:`int`] + The guild ID where the reactions got cleared. + emoji: :class:`PartialEmoji` + The custom or unicode emoji being removed. + """ + + __slots__ = ('message_id', 'channel_id', 'guild_id', 'emoji') + + def __init__(self, data, emoji): + self.emoji = emoji + self.message_id = int(data['message_id']) + self.channel_id = int(data['channel_id']) + + try: + self.guild_id = int(data['guild_id']) + except KeyError: + self.guild_id = None diff --git a/discord/reaction.py b/discord/reaction.py index c7f2ef77c..79c8efe22 100644 --- a/discord/reaction.py +++ b/discord/reaction.py @@ -121,6 +121,28 @@ class Reaction: await self.message.remove_reaction(self.emoji, user) + async def clear(self): + """|coro| + + Clears this reaction from the message. + + You need the :attr:`~Permissions.manage_messages` permission to use this. + + .. versionadded:: 1.3 + + Raises + -------- + HTTPException + Clearing the reaction failed. + Forbidden + You do not have the proper permissions to clear the reaction. + NotFound + The emoji you specified was not found. + InvalidArgument + The emoji parameter is invalid. + """ + await self.message.clear_reaction(self.emoji) + def users(self, limit=None, after=None): """Returns an :class:`AsyncIterator` representing the users that have reacted to the message. diff --git a/discord/state.py b/discord/state.py index aea0984d5..a3a8e2432 100644 --- a/discord/state.py +++ b/discord/state.py @@ -509,6 +509,23 @@ class ConnectionState: if user: self.dispatch('reaction_remove', reaction, user) + def parse_message_reaction_remove_emoji(self, data): + emoji = data['emoji'] + emoji_id = utils._get_as_snowflake(emoji, 'id') + emoji = PartialEmoji.with_state(self, animated=emoji.get('animated', False), id=emoji_id, name=emoji['name']) + raw = RawReactionClearEmojiEvent(data, emoji) + self.dispatch('raw_reaction_clear_emoji', raw) + + message = self._get_message(raw.message_id) + if message is not None: + try: + reaction = message._clear_emoji(emoji) + except (AttributeError, ValueError): # eventual consistency lol + pass + else: + if reaction: + self.dispatch('reaction_clear_emoji', reaction) + def parse_presence_update(self, data): guild_id = utils._get_as_snowflake(data, 'guild_id') guild = self._get_guild(guild_id) diff --git a/docs/api.rst b/docs/api.rst index 8c22e0bc5..e43028437 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -250,7 +250,8 @@ to handle it, which defaults to print a traceback and ignoring the exception. Messages might not be in cache if the message is too old or the client is participating in high traffic guilds. - If this occurs increase the :attr:`Client.max_messages` attribute. + If this occurs increase the :attr:`Client.max_messages` attribute + or use the :func:`on_raw_message_delete` event instead. :param message: The deleted message. :type message: :class:`Message` @@ -264,7 +265,8 @@ to handle it, which defaults to print a traceback and ignoring the exception. the messages list. Messages might not be in cache if the message is too old or the client is participating in high traffic guilds. - If this occurs increase the :attr:`Client.max_messages` attribute. + If this occurs increase the :attr:`Client.max_messages` attribute + or use the :func:`on_raw_bulk_message_delete` event instead. :param messages: The messages that have been deleted. :type messages: List[:class:`Message`] @@ -298,7 +300,8 @@ to handle it, which defaults to print a traceback and ignoring the exception. Messages might not be in cache if the message is too old or the client is participating in high traffic guilds. - If this occurs increase the :attr:`Client.max_messages` attribute. + If this occurs increase the :attr:`Client.max_messages` attribute + or use the :func:`on_raw_message_edit` event instead. The following non-exhaustive cases trigger this event: @@ -339,7 +342,7 @@ to handle it, which defaults to print a traceback and ignoring the exception. Called when a message has a reaction added to it. Similar to :func:`on_message_edit`, if the message is not found in the internal message cache, then this - event will not be called. + event will not be called. Consider using :func:`on_raw_reaction_add` instead. .. note:: @@ -385,7 +388,7 @@ to handle it, which defaults to print a traceback and ignoring the exception. Called when a message has all its reactions removed from it. Similar to :func:`on_message_edit`, if the message is not found in the internal message cache, then this event - will not be called. + will not be called. Consider using :func:`on_raw_reaction_clear` instead. :param message: The message that had its reactions cleared. :type message: :class:`Message` @@ -400,6 +403,27 @@ to handle it, which defaults to print a traceback and ignoring the exception. :param payload: The raw event payload data. :type payload: :class:`RawReactionClearEvent` +.. function:: on_reaction_clear_emoji(reaction) + + Called when a message has a specific reaction removed from it. Similar to :func:`on_message_edit`, + if the message is not found in the internal message cache, then this event + will not be called. Consider using :func:`on_raw_reaction_clear_emoji` instead. + + .. versionadded:: 1.3.0 + + :param reaction: The reaction that got cleared. + :type reaction: :class:`Reaction` + +.. function:: on_raw_reaction_clear_emoji(payload) + + Called when a message has a specific reaction removed from it. Unlike :func:`on_reaction_clear_emoji` this is called + regardless of the state of the internal message cache. + + .. versionadded:: 1.3.0 + + :param payload: The raw event payload data. + :type payload: :class:`RawReactionClearEmojiEvent` + .. function:: on_private_channel_delete(channel) on_private_channel_create(channel) @@ -2472,6 +2496,12 @@ RawReactionClearEvent .. autoclass:: RawReactionClearEvent() :members: +RawReactionClearEmojiEvent +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: RawReactionClearEmojiEvent() + :members: + .. _discord_api_data: