Skip to content

Instantly share code, notes, and snippets.

@AkshuAgarwal
Last active February 11, 2025 16:47
Show Gist options
  • Save AkshuAgarwal/bc7d45bcecd5d29de4d6d7904e8b8bd8 to your computer and use it in GitHub Desktop.
Save AkshuAgarwal/bc7d45bcecd5d29de4d6d7904e8b8bd8 to your computer and use it in GitHub Desktop.
A Basic guide about Discord Interactions and how to use them in discord.py

Interactions

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.



Index



References

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.

reference



How do Interactions Work?

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.

Attributes

  • 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.

  • followup - Will talk about this later in detail!

  • 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 and InteractionMessage are different! We'll talk about InteractionMessage 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 an InteractionResponse 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 using Interaction.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 at discord.InteractionType

  • user - The User/Member who sent the interaction (clicked button or used / command or etc...)

Methods

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.


  • original_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 returns None), 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 not Message!

  • edit_original_message:

    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 from Interaction.original_message() and then using .edit() on the returned, just the difference is that this saves an API call!

  • delete_original_message:

    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 from Interaction.original_message() and then using .delete() on the returned, just the difference is that this saves an API call!

Examples

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())

example



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...


Attributes

  • This class don't have any...

Methods

  • send_message:

    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())

    example_send_message

  • edit_message:

    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())

    example_edit_message

  • defer:

    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))
  • is_done:

    Method to check whether you already responded to interaction or not?

  • pong:

    To "Pong!" the interaction.



Responding to Deferred 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 deferring), 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 using ctx.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.

    example_responding_to_defer

  • 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!

Examples

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())

example_followup



Wrapping Up

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:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment