Skip to content

Commit 337719e

Browse files
committed
Response to Reviews on PR #55
Merged the suggesions from Shiftinv and took their comments into consideration and made some alterations
1 parent c210d39 commit 337719e

File tree

1 file changed

+114
-89
lines changed

1 file changed

+114
-89
lines changed

guide/docs/popular-topics/reactions.mdx

Lines changed: 114 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,25 @@ hide_table_of_contents: true
55

66
# Reactions
77

8-
Reactions are Discord's way of adding emojis to other messages. Early on, before Discord introduced [components](../interactions/buttons.mdx), this system was largely used to make interactive messages and apps.
8+
Reactions are Discord's way of adding emojis to other messages. Early on, before Discord introduced [components](../interactions/buttons.mdx), this system was largely used to make interactive messages and apps.
99
Having bots react to messages is less common now, and is somewhat considered legacy behaviour.
1010
This guide will teach you the basics of how they work, since they still have their use cases, like reaction role systems and polling.
1111

1212
In Disnake, reactions are represented with <DocsLink reference="disnake.Reaction">Reaction</DocsLink> objects. Whenever you operate on a <DocsLink reference="disnake.Message">Message</DocsLink> you can access a list of reactions attached to that message.
1313
In this guide we will be providing an example using the <DocsLink reference="disnake.on_raw_reaction_add">on_raw_reaction_add / remove</DocsLink> events and a <DocsLink ext="commands" reference="disnake.ext.commands.Bot.message_command">message_command</DocsLink>'s <DocsLink reference="disnake.MessageCommandInteraction">interaction</DocsLink> to demonstrate.
1414

15+
- <DocsLink reference="disnake.Reactions.users">disnake.Reactions.users</DocsLink> won't be covered here since the docs
16+
demonstrate its use elegantly.
17+
1518
:::info
1619
**Reaction limitations**
1720

18-
- Removing reactions that are not owned by the bot requires <DocsLink reference="disnake.Intents.reactions">Intents.reactions</DocsLink> to be set
19-
- Therefore <DocsLink reference="disnake.Intents.messages">Intents.messages</DocsLink> is indirectly required if you want to manipulate reactions
21+
- To maintain a consistent reaction cache <DocsLink reference="disnake.Intents.reactions">Intents.reactions</DocsLink> is recommended to manipulate others reactions, and is required if you intend to utilize events.
2022
- A message can have a maximum of 20 unique reactions on it at one time.
21-
- Reactions are inherently linked to emojis, and your bot will not have access to resend all emojis used by Discord users.
23+
- Reactions are inherently linked to emojis, and your bot will not have access to resend all emojis used by Discord users. ( The bot can always react to others reactions )
2224
- Dealing with reactions results in a fair amount of extra API calls, meaning it can have rate-limit implications on deployment scale.
2325
- Using Reactions as a UX interface was never a intended behavior, and is ultimately inferior to the newer component style interface.
26+
2427
:::
2528

2629
<DiscordMessages>
@@ -49,17 +52,14 @@ In this guide we will be providing an example using the <DocsLink reference="dis
4952
### Emojis
5053

5154
Since reactions utilize Emojis this guide will also include a quick primer on how disnake handles emojis
52-
Emojis have three forms:
55+
**Emojis have three forms:**
5356

54-
- <DocsLink reference="disnake.Emoji">Emoji</DocsLink> Custom emojis
55-
- <DocsLink reference="disnake.PartialEmoji">PartialEmoji</DocsLink> Stripped down version of Emoji
56-
- [`string`](https://docs.python.org/3/library/string.html) String containing one or more emoji unicodepoints (Emoji modifiers complicates things but thats out of scope)
57-
58-
**Which one you get is circumstancial:**
59-
Emoji class: is primarely returned when custom emojis are grabbed from the guild/bot
60-
PartialEmoji: are most often custom emojis too, but will usually represent custom emojis the bot can't access
61-
Strings: are normally returned when Unicode CodePoints are used. These are the standard emojis most are familiar with (✅🎮💛💫)
62-
but these can also come as a PartialEmoji
57+
- <DocsLink reference="disnake.Emoji">Emoji</DocsLink> Custom emojis are primarely returned when custom emojis are grabbed
58+
from the guild/bot
59+
- <DocsLink reference="disnake.PartialEmoji">PartialEmoji</DocsLink> Stripped down version of Emoji. Which appears in raw
60+
events or when the bot cannot access the custom emoji
61+
- [`string`](https://docs.python.org/3/library/string.html) Strings: are normally returned when unicode emojis are used. These are the standard emojis most are familiar with (✅🎮💛💫)
62+
but these will also come as a PartialEmoji in raw events
6363

6464
There is also a small write up about this [here](../faq/general.mdx#how-can-i-add-a-reaction-to-a-message).
6565

@@ -71,21 +71,23 @@ Some examples are also available in the [GitHub repository](https://github.com/D
7171

7272
### Example using on_reaction events
7373

74-
There are a few reaction related events we can listen/subscribe to:
74+
There are a few reaction related [events](https://docs.disnake.dev/en/stable/api.html#event-reference) we can listen/subscribe to:
7575

7676
- <DocsLink reference="disnake.on_raw_reaction_add">on_raw_reaction_add</DocsLink>, called when a user adds a reaction
77-
- <DocsLink reference="disnake.on_raw_reaction_remove">on_raw_reaction_remove</DocsLink>, called when a user's reaction is removed
78-
- <DocsLink reference="disnake.on_raw_reaction_clear">on_raw_reaction_clear</DocsLink>, called when a message has all reactions removed
79-
- <DocsLink reference="disnake.on_raw_reaction_clear_emoji">on_raw_reaction_clear_emoji</DocsLink>, called when all reactions with a specific emoji are removed from a message
77+
- <DocsLink reference="disnake.on_raw_reaction_remove">on_raw_reaction_remove</DocsLink>, called when a user's
78+
reaction is removed
79+
- <DocsLink reference="disnake.on_raw_reaction_clear">on_raw_reaction_clear</DocsLink>, called when a message has all
80+
reactions removed
81+
- <DocsLink reference="disnake.on_raw_reaction_clear_emoji">on_raw_reaction_clear_emoji</DocsLink>, called when all
82+
reactions with a specific emoji are removed from a message
8083

8184
There are non-raw equivalents, but they rely on the cache. If the message is not found in the internal cache, then the event is not called.
8285
For this reason raw events are preferred, and you are only giving up on an included User/Member object that you can easily fetch if you need it.
8386

84-
- More information about events can be found in the docs, [`here`](https://docs.disnake.dev/en/stable/api.html#event-reference)
85-
8687
One important thing about raw_reaction events is that all the payloads are only populated with <DocsLink reference="disnake.PartialEmoji">PartialEmojis</DocsLink>
8788
This is generally not an issue since it contains everything we need, but its something you should be aware of.
88-
Raw reaction events come a <DocsLink reference="disnake.RawReactionActionEvent">RawReactionActionEvent</DocsLink> which is called `payload` in the examples.
89+
Raw reaction add/remove events come as <DocsLink reference="disnake.RawReactionActionEvent">RawReactionActionEvent</DocsLink> which is called `payload` in the examples.
90+
The raw clearing events each have their own event payloads.
8991

9092
```python title="on_raw_reaction_add.py"
9193
@bot.listen()
@@ -151,75 +153,112 @@ Notice how second emoji resolved into **:disnake:** because the emoji is on a se
151153
<DiscordMessage profile="bot">Reaction :disnake: added by: AbhigyanTrips!</DiscordMessage>
152154
</DiscordMessages>
153155

154-
<br/>
156+
<br />
155157

156158
:::caution
157159
We can only use custom emojis from servers the bot has joined, but we can use them interchangably on those servers.
158160
Bots can make <DocsLink reference="disnake.ui.Button">buttons</DocsLink> using emojis from servers they're not members of, this may or may not be intended behaviour by Discord and should not be relied on.
159161
:::
160162

161-
Here's a few ways you could filter on reactions to do various things
163+
<br />
162164

163-
```python title=react_actions.py
164-
import disnake
165+
**Here are a few examples on how reactions can be implemented:**
165166

166-
# These lists are arbitrary and is just to provide context. Using static lists like this can be ok in small bots, but should really be supplied by a db.
167+
<Tabs>
168+
<TabItem value="deny_reactions.py" label="Deny a role using reactions">
169+
170+
```python
167171
allowed_emojis = ["💙"]
168-
button_emojis = [""]
169172
restricted_role_ids = [951263965235773480, 1060778008039919616]
170-
reaction_messages = [1060797825417478154]
171-
reaction_roles = {
172-
"🎮": 1060778008039919616,
173-
"🚀": 1007024363616350308,
174-
"<:catpat:967269162386858055>": 1056775021281943583,
175-
}
176173

177174

178175
@bot.listen()
179176
async def on_raw_reaction_add(payload: disnake.RawReactionActionEvent):
180-
# This example is more to illustrate the different ways you can filter which emoji's are used and then you can do your actions on them
181-
# All the functions in it have been tested, but you should add more checks and returns if you actually wanted all these functions at the same time
182177

183-
# We usually don't want the bot to react to its own actions, nor DM's in this case
184178
if payload.user_id == bot.user.id:
185179
return
186180
if not payload.guild_id:
187181
return # guild_id is None if its a DM
188182

189-
# Again, getting the channel, and fetching message as these will be useful
183+
# Getting the channel, and fetching message as these will be useful
190184
event_channel = bot.get_channel(payload.channel_id)
191185
event_message = await event_channel.fetch_message(payload.message_id)
192186

193-
# Members with a restricted role, are only allowed to react with💙 -- From the docs we know that str(PartialEmoji) returns either the codepoint or <:emoji:id>
187+
# Members with a restricted role, are only allowed to react with 💙 -- From the docs we know that str(PartialEmoji) returns either the codepoint or <:emoji:id>
194188
if [role for role in payload.member.roles if role.id in restricted_role_ids] and not str(
195189
payload.emoji
196190
) in allowed_emojis:
197191
# Since the list did not return empty and is not a allowed emoji, we remove it
198192
await event_message.remove_reaction(emoji=payload.emoji, member=payload.member)
193+
```
194+
195+
</TabItem>
196+
197+
<TabItem value="main2.py" label="Simple reaction button">
198+
199+
```python
200+
# Since you can to un-react for the user we can emulate a button
201+
# This can be usefull if you want the functionality of buttons, but want a more compact look.
202+
203+
button_emojis = [""] # What emojis to react to
204+
reaction_messages = [1060797825417478154] # What messages to monitor
205+
206+
207+
@bot.listen()
208+
async def on_raw_reaction_add(payload: disnake.RawReactionActionEvent):
209+
210+
if payload.user_id == bot.user.id:
211+
return
212+
if not payload.guild_id:
213+
return
214+
if payload.channel_id not in reaction_messages or str(payload.emoji) not in button_emojis:
215+
return
216+
217+
# Getting the channel, and fetching message as these will be useful
218+
event_channel = bot.get_channel(payload.channel_id)
219+
event_message = await event_channel.fetch_message(payload.message_id)
220+
221+
await event_message.remove_reaction(
222+
emoji=payload.emoji, member=payload.member
223+
) # Remove the reaction
224+
awesome_function() # Do some stuff
225+
await event_channel.send("Done!", delete_after=10.0)
226+
# Short message to let the user know it went ok. This is not an interaction so a message response is not strictly needed
227+
```
228+
229+
</TabItem>
230+
<TabItem value="reaction_role.py" label="Simple reaction roles">
199231

200-
# Similar behavior can be useful if you want to use reactions as buttons. Since you have to un-react and react again to repeat the effect
201-
# This can be usefull if you want the functionality of buttons, but want a more compact look.
202-
# but its also a lot of extra api calls compared to components
203-
if str(payload.emoji) in button_emojis:
204-
# In a proper bot you would do more checks, against message_id most likely.
205-
# As theese reactions might normaly be supplied by the bot in the first place
206-
207-
# Or if the member has the right roles in case the reaction has a moderation function for instance
208-
# Otherwise the awesome_function() can end up going off at wrong places
209-
await event_message.remove_reaction(
210-
emoji=payload.emoji, member=payload.member
211-
) # Remove the reaction
212-
awesome_function()
213-
await event_channel.send("Done!", delete_after=10.0)
214-
# Short message to let the user know it went ok. This is not an interaction so a message response is not strictly needed
215-
216-
# A very simple reaction role system
217-
if str(payload.emoji) in reaction_roles.keys() and payload.message_id in reaction_messages:
218-
role_to_apply = bot.get_guild(payload.guild_id).get_role(reaction_roles[str(payload.emoji)])
219-
if (
220-
role_to_apply and not role_to_apply in payload.member.roles
221-
): # Check if we actually got a role, then check if the member already has it, if not add it
222-
await payload.member.add_roles(role_to_apply)
232+
```python
233+
# A very simple reaction role system
234+
235+
reaction_messages = [1060797825417478154] # What messages to monitor
236+
reaction_roles = {
237+
"🎮": 1060778008039919616,
238+
"🚀": 1007024363616350308,
239+
"<:catpat:967269162386858055>": 1056775021281943583,
240+
} # The emojis, and their corresponding role ids
241+
242+
243+
@bot.listen()
244+
async def on_raw_reaction_add(payload: disnake.RawReactionActionEvent):
245+
246+
# We usually don't want the bot to react to its own actions, nor DM's in this case
247+
if payload.user_id == bot.user.id:
248+
return
249+
if not payload.guild_id:
250+
return # guild_id is None if its a DM
251+
if (
252+
str(payload.emoji) not in reaction_roles.keys()
253+
or payload.message_id not in reaction_messages
254+
):
255+
return
256+
257+
role_to_apply = bot.get_guild(payload.guild_id).get_role(reaction_roles[str(payload.emoji)])
258+
if (
259+
role_to_apply and not role_to_apply in payload.member.roles
260+
): # Check if we actually got a role, then check if the member already has it, if not add it
261+
await payload.member.add_roles(role_to_apply)
223262

224263

225264
@bot.listen()
@@ -228,46 +267,32 @@ async def on_raw_reaction_remove(payload: disnake.RawReactionActionEvent):
228267
return
229268
if not payload.guild_id:
230269
return # guild_id is None if its a DM
270+
if (
271+
str(payload.emoji) not in reaction_roles.keys()
272+
or payload.message_id not in reaction_messages
273+
):
274+
return
231275

232-
# Counterpart to the simple reaction role system
276+
role_to_remove = bot.get_guild(payload.guild_id).get_role(reaction_roles[str(payload.emoji)])
233277
if (
234-
str(payload.emoji) in reaction_roles.keys() and payload.message_id in reaction_messages
235-
): # Check that the emoji and message is correct
236-
role_to_remove = bot.get_guild(payload.guild_id).get_role(
237-
reaction_roles[str(payload.emoji)]
238-
)
239-
if (
240-
role_to_apply and role_to_apply in payload.member.roles
241-
): # Check if we actually got a role, then check if the member actually has it, then remove it
242-
await payload.member.remove_roles(role_to_apply)
278+
role_to_apply and role_to_apply in payload.member.roles
279+
): # Check if we actually got a role, then check if the member actually has it, then remove it
280+
await payload.member.remove_roles(role_to_apply)
243281
```
244282

245-
### Example using message_command
283+
</TabItem>
246284

247-
We could go with a `slash_command` here, but since we will be targeting other messages, it adds a complication because if the message is in a different channel from where the command is executed; the retrieved message will be `None`.
248-
Using a `message_command` instead side-steps this issue, since the targeted message will be present in the interaction object.
285+
</Tabs>
249286

250-
- <DocsLink reference="disnake.Reactions.users">disnake.Reactions.users</DocsLink> won't be covered here since the docs
251-
demonstrate its use elegantly.
287+
### Example using an ApplicationCommandInteraction
252288

289+
Using a `message_command` here because the message object is always included in the message commands interaction instance.
253290
This example is purely to demonstrate using the Reaction object since events deal with a similar but different class
254291

255292
```python title="message_command.py"
256293
@commands.message_command()
257294
async def list_reactions(self, inter: disnake.MessageCommandInteraction):
258295

259-
# Here's a very pythonic way of making a list of the reactions
260-
response_string = (
261-
"".join(
262-
[
263-
f"{index+1}. {reaction.emoji} - {reaction.count}\n"
264-
for index, reaction in enumerate(inter.target.reactions)
265-
]
266-
)
267-
or "No Reactions found"
268-
)
269-
270-
# Here it is broken up in case list comprehensions are too confusing
271296
response_string = "" # Start with an empty string
272297
reaction_list = inter.target.reactions # First we get the list of disnake.Reaction objects
273298
for index, reaction in enumerate(
@@ -289,7 +314,7 @@ async def list_reactions(self, inter: disnake.MessageCommandInteraction):
289314
await inter.target.add_reaction(reaction)
290315

291316
# However we still cannot add new reactions we don't have access to.
292-
# When listing through reactions PartialEmojis are generated if the bot does not have access to it, so we can filter on that to skip them
317+
# PartialEmojis are generated if the bot does not have access to it, so we can filter on that to skip them
293318
if isinstance(reaction.emoji, disnake.PartialEmoji):
294319
continue
295320
await message.add_reaction(reaction)

0 commit comments

Comments
 (0)