Before Starting, note that this is a Walkthrough of How to respond and not How to make. Responding includes understanding of interactions and responding to User, whereas making includes Components which are made using Views and Component Classes.
If you want to know about how to make them, there's not any guide in particular yet (or atleast I'm not aware of any) but there is always the Documentation and some great Examples! If there will be any guide for this in future, I'll link that here.
The reference to keywords I used in the guide.
If you find any mistake or have something to add, ping Akshu#7472 (
764462046032560128
) in discord.gg/dpy server or DM me.
Interactions works differently than the usual Messages.
The Bot needs to send a Message including a View (Button/Select) or it should be a Slash Command. When a user clicks on the button, discord sends you an Interaction
with an interaction token
back. You're given 3 seconds to respond back to interaction else it'll fail and "This Interaction Failed" will be displayed on the User's Client and your interaction token
will become invalid.
To prevent this, just send a response in the next 3 seconds of receiving Interactions.
In case your command takes more than 3 seconds because of any task to send a response, you can call
Interaction.response.defer()
which defers the Interaction (basically sends a response back to discord that "I've received the Interaction, you can now approve this on client side") and later reply to the user (You cannot use the response back again but there are some other ways of responding back which are discussed Below).
This is the basic working of an Interaction based system.
Let's have a look around this class' Attributes and Methods. These are referenced from the discord.py docs but with some additional information and examples.
-
application_id
- A unique id of the parent Application. -
channel
- The channel from where the interaction is sent. (Optional)In case of Message Components, it'll be the channel where you sent the message with the Components and in case of Application Commands, it'll be the channel where your
/
or context menu command is invoked. -
channel_id
- ID of the Channel. -
data
- The raw interaction data. -
guild
- The guild from where the interaction is sent. -
guild_id
- ID of the Guild. -
id
- The Interaction's ID -
message
- The message that sent this Interaction.Note:
Message
andInteractionMessage
are different! We'll talk aboutInteractionMessage
in depth later! -
permissions
- The permissions of the Member who responded to interaction. -
response
- The object responsible for all the "responding back" stuff! This returns anInteractionResponse
object. We'll talk about this in depth later! -
token
- Remember where I mentioned a "token" in the start which discord sends you back for responding? Note that this is valid for 15 minutes (if you send an initial response usingInteraction.response
or defer the response)If you don't send an initial response within 3 seconds, discord invalidates the token, so you cannot use it.
-
type
- The type of the interaction you received from discord back. It's Enum can be found atdiscord.InteractionType
-
user
- The User/Member who sent the interaction (clicked button or used/
command or etc...)
Note that the methods below only works if you respond to an Interaction by either deferring it or send any initial response, else it'll raise
NotFound (code: 10015): Unknown Webhook
Exception. If you defer the interaction, these methods will function on your original message (which contains buttons). Or if you send an initial response, this will be executed on that responded message.
-
The Message associated with the interaction. This can be accessed only after responding back to interaction.
If you responded to the interaction using
interaction.response.send_message()
, it'll return that message which bot sent as the response (Note that you will not receive anything in return of responding to interaction (interaction.response.send_message
returnsNone
), so this is the only way to get that message).If you didn't responded to the Interaction by sending message and just deferred it, this will return the message which contains the Buttons and stuff.
Note that the returned type is
InteractionMessage
and notMessage
! -
This method is for editing the original message, i.e., the message you received from the
original_message
method.This is equivalent to getting
InteractionMessage
fromInteraction.original_message()
and then using.edit()
on the returned, just the difference is that this saves an API call! -
This method is for deleting the original message, i.e. the message you received from the
original_message
method.This is equivalent to getting
InteractionMessage
fromInteraction.original_message()
and then using.delete()
on the returned, just the difference is that this saves an API call!
class MyView(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
@discord.ui.button(label='Click Me!')
async def my_button(self, button, interaction):
await interaction.response.send_message('Hello!') # Sends a "Hello!" to the user who clicked the Button
msg = await interaction.original_message() # Returns the InteractionMessage object of the message sent with `send_message()`
@discord.ui.button(label='Click Me too!')
async def my_another_button(self, button, interaction):
await interaction.response.defer() # Marks the interaction to be recognized, but does not send anything to user.
msg = await interaction.original_message() # This time, it'll return the message which contains the Button, since we didn't send anything.
@bot.command()
async def test(ctx):
await ctx.send(content='Interaction Examples', view=MyView())
This is the object returned by discord.Interaction.response
to respond to an interaction. All the "responding back" stuff is handled by this class.
Let's have a look at it's Attributes and Methods.
Pong! Oops, sorry to interrupt but since we reached "how to respond", have a look at this:
Note that if you have a callback function which gets finished in less than 3 seconds and you don't need to send any response back, you can just skip it because discord.py handles that for you. Skipping this isn't really necessary but you can save a few lines of code.
@discord.ui.button(label='Click Me!')
async def my_button(self, button, interaction):
await asyncio.sleep(2.9) # A task which gets completed before 3 seconds
# If you don't want to respond anything, you can just leave them here
For tasks taking longer than 3 seconds, you need to defer it first and then do the task. Also note that this isn't a discord's implementation but of the library.
Not getting what this all means? Continue reading and have a look back to this. Okay let's continue...
- This class don't have any...
-
This method is used to respond back to an interaction by sending a Message.
Example:
class MyView(discord.ui.View): def __init__(self): super().__init__(timeout=None) @discord.ui.button(label='Click Me!') async def my_button(self, button, interaction): await interaction.response.send_message('You clicked a Button!') @bot.command() async def test(ctx): await ctx.send(content='Interaction Examples', view=MyView())
-
This method is used to respond to the interaction by editing the message which contains the component.
Responding to an interaction is also possible by editing the message and just not sending a new message or defer the response.
Example:
class MyView(discord.ui.View): def __init__(self): super().__init__(timeout=None) @discord.ui.button(label='Click Me!') async def my_button(self, button, interaction): await interaction.response.edit_message(content='You clicked a Button!') @bot.command() async def test(ctx): await ctx.send(content='Interaction Examples', view=MyView())
-
This is used to acknowledge the Interaction (i.e., telling discord that "Hey! I received an interaction but I need more time to respond back, so don't invalidate my token...", since discord has a time limit of 3 seconds of responding back to interactions.)
A common example including some common errors usually made:
class MyView(discord.ui.View): def __init__(self, ctx): super().__init__(timeout=None) self.ctx = ctx # Correct @discord.ui.button(label='Click Me!') async def my_button(self, button, interaction): await interaction.response.defer() await asyncio.sleep(10) # Now I'll do some time-taking task... await self.ctx.send('Task Completed!') # Or use a followup webhook! What's that? Later... # Incorrect @discord.ui.button(label='Click Me!') async def my_incorrect_button(self, button, interaction): await asyncio.sleep(10) # I'll do some heavy, time-taking task first... await interaction.response.defer() # Discord: "You should've responded before doing task bruh. Now I won't acknowledge it" # Incorrect @discord.ui.button(label='Click Me!') async def my_another_incorrect_button(self, button, interaction): await interaction.response.defer() await asyncio.sleep(10) # Now I'll do some time-taking task... await interaction.response.send_message('Task Completed!') # `defer` is also a type of response. Since you already used it, you can't respond again back using the same interaction. @bot.command() async def test(ctx): await ctx.send(content='Interaction Examples', view=MyView(ctx))
-
Method to check whether you already responded to interaction or not?
-
pong
:To "Pong!" the interaction.
Suppose you have a long running task which prevents you from responding back in 3 seconds. In that case, you can use defer
before doing that task telling discord that "Hey, I received the interaction, I need some more time to respond. So please mark it as approved..."
Hey! I deferred the Interaction but what about replying the user back??!!!
If you try to respond back with interaction.response.send_message()
after deferring it, it'll raise InteractionResponded
error. Why so? The interaction.response
can only be used once, and once used (since we already used it for defer
ring), you can't respond back by the same Interaction.
Don't worry, there are still a plenty of ways to respond back!
-
One is you save the
context
object and send a normal message usingctx.send()
to the same channel. Example:class MyView(discord.ui.View): def __init__(self, ctx): super().__init__(timeout=None) self.ctx = ctx @discord.ui.button(label='Click Me!') async def my_button(self, button, interaction): await interaction.response.defer() # Oops! We used the interaction, now we can't use this back to respond! await self.ctx.send('This message is sent using context and not interaction.response') # This way you can send *technically* infinite messages. @bot.command() async def test(ctx): await ctx.send(content='Interaction Examples', view=MyView(ctx)) # We'll pass ctx as an argument in the View constructor.
-
The other is to use
followup
. As I told, we'll talk about this later!
When you call the original_message
, instead of a normal Message
object, it returns an InteractionMessage
object instead.
You can get this object using Interaction.original_message()
.
This class just has 2 methods, edit
and delete
, equivalent to Interaction.edit_original_message()
and Interaction.delete_original_message()
respectively (Only difference is that latter saves an API call).
Sometimes this is possible that after responding to an interaction, you need to send some more messages to the user. But the response can only be used once.
For this, discord provides a temporary Webhook
which you can use to send more messages after sending an initial response.
Just like normal Webhooks, this uses interaction token
to make requests to the Webhook. That means followups are also valid for 15 minutes if you send an initial response and if you don't respond to the interaction within 3 seconds, these webhooks will not be valid too anymore.
These are not usual webhooks you can find by going to the channel settings and finding one in integrations. Instead this is associated separately with your interaction.
This returns a usual Webhook
object, so you can pretty much do most of the things you can do with a Webhook!
class MyView(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
# Correct
@discord.ui.button(label='Click Me!')
async def my_button(self, button, interaction):
await interaction.response.send_message('This is a message sent using response.')
# Oops! I already used the response. How do I send another message??
await interaction.followup.send('This is a message sent using followup.') # Here I am, use me!
# Incorrect
@discord.ui.button(label='Click Me!')
async def my_another_button(self, button, interaction):
await interaction.followup.send('This is a message sent using followup.')
# This won't work since your interaction isn't validated yet and the webhook will be invalid. This will raise `InvalidWebhook` exception.
# To make followups to work, you need to send or defer an initial response first.
@bot.command()
async def test(ctx):
await ctx.send(content='Interaction Examples', view=MyView())
This was all the basic stuff how the Interactions work. This was made with context of Buttons and Selects only since Slash Commands and Context Menu Commands are not yet released on discord.py 2.0 yet. This may not be covered so deeply but I tried to give the basic understanding of how this all works.
Again, if you find any errors or have something to add or change, ping Akshu#7472 (764462046032560128
) in discord.gg/dpy server or DM me.
Some Resources I took reference from:
- discord.py Documentation
- Official Discord API Docs
- discord.py Support Server (If you have any more questions, you can ask them here)