How do I add a button to a slash command response in discord.py

ghz 14hours ago ⋅ 6 views

How do I add a button to a message that my bot sends after a slash command is executed. (I'm running python 3.8).

I want to type '/image prompt' in discord and have my program check if the user has accepted the terms and conditions. If they haven't then the bot will send a message with a button attached to agree to the terms.

I would prefer not to use classes if possible.

import discord, threading, asyncio, discord.ext.commands, discord.app_commands, datetime, pathlib, hashlib, random, json, os
from discord.ext import commands
from discord import app_commands, Interaction#, ButtonStyle, Button
from discord.ui import Button, ButtonStyle
from typing import Optional

TOKEN='my token'
intents=discord.Intents.default()
intents.message_content=True
client=commands.Bot(command_prefix='!', case_insensitive=True, intents=intents)
tree=client.tree

#other commands here

@client.event #add button function
async def on_button_click(interaction):
    if interaction.component.custom_id=='Confirm image':
        #add user to the list of users who have accepted the terms and conditions

@tree.command(name="image", description="Generates an image using stable diffusion", guild=discord.Object(id=1140580736412434472))
async def image(ctx, prompt: str, replyprivately: Optional[bool]):
    print(f'{ctx.user.name} added {prompt} to the queue.')
    import json
    with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'botconfig', 'imageusers.json'), 'r') as f:
        file=json.load(f)
    if ctx.user.id not in file:
        ctx.response.send_message("Please agree to T&Cs") #I want to add a button to this message

client.run(TOKEN)

I have tried using ctx.respond.send_message('message', components=[Button(style=ButtonStyle.primary, label='I understand', custom_id="Confirm image")]) but i get errors like this

Traceback (most recent call last):
  File "C:\Users\tjgor\anaconda3\envs\ldm\lib\site-packages\discord\app_commands\commands.py", line 828, in _do_call
    return await self._callback(interaction, **params)  # type: ignore
  File "E:\Documents\coding\Python\stable diffusion discord bot\stable-diffusion-main\mainbot.py", line 109, in image
    ctx.response.send_message("Some of the models we use have the ability to generate NSFW images. Please confirm that you understand", components=[Button(style=ButtonStyle.primary, label='I understand', custom_id="Confirm image")])
TypeError: __init__() got an unexpected keyword argument 'style'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\tjgor\anaconda3\envs\ldm\lib\site-packages\discord\app_commands\tree.py", line 1248, in _call
    await command._invoke_with_namespace(interaction, namespace)
  File "C:\Users\tjgor\anaconda3\envs\ldm\lib\site-packages\discord\app_commands\commands.py", line 853, in _invoke_with_namespace
    return await self._do_call(interaction, transformed_values)
  File "C:\Users\tjgor\anaconda3\envs\ldm\lib\site-packages\discord\app_commands\commands.py", line 842, in _do_call
    raise CommandInvokeError(self, e) from e
discord.app_commands.errors.CommandInvokeError: Command 'image' raised an exception: TypeError: __init__() got an unexpected keyword argument 'style'```

Answer

It seems like the error you're encountering (TypeError: __init__() got an unexpected keyword argument 'style') is due to the method you're using to send the message and add the button.

The issue here is that discord.py (as of version 2.x, which is the current version you're likely using) has moved to a new system for handling buttons, using discord.ui.Button in conjunction with discord.ui.View. The error happens because the components argument is no longer valid, and you should instead use discord.ui.View to create a button and attach it to the message.

Here’s the updated approach for your problem using the correct methods.

1. Define a View with the Button:

We need to create a discord.ui.View object, which will hold the button, and then pass that view when sending the message. Additionally, you'll need to handle the interaction with the button.

2. Example Code:

import discord
from discord.ext import commands
from discord import app_commands
from discord.ui import Button, View

TOKEN = 'my token'

intents = discord.Intents.default()
intents.message_content = True
client = commands.Bot(command_prefix='!', case_insensitive=True, intents=intents)
tree = client.tree

# Define the button and interaction handler
class ConfirmButton(View):
    def __init__(self):
        super().__init__(timeout=None)

    @discord.ui.button(label="I understand", style=discord.ButtonStyle.primary, custom_id="Confirm image")
    async def confirm_button(self, interaction: discord.Interaction, button: discord.ui.Button):
        if interaction.user.id == interaction.message.author.id:  # Optional: make sure it's the same user who triggered the command
            await interaction.response.send_message("You have accepted the terms!", ephemeral=True)

            # Here you can handle adding the user to the list of users who accepted the T&Cs
            # For example, saving it to a file or database
            with open('accepted_users.json', 'r+') as f:
                data = json.load(f)
                data[str(interaction.user.id)] = True  # Mark this user as accepted
                f.seek(0)
                json.dump(data, f)

            # Perform the action to generate the image after terms are accepted

# Command handler for '/image'
@tree.command(name="image", description="Generates an image using stable diffusion", guild=discord.Object(id=1140580736412434472))
async def image(ctx, prompt: str, replyprivately: Optional[bool]):
    print(f'{ctx.user.name} added {prompt} to the queue.')

    # Assuming you have a json file storing users who have accepted the terms
    import json
    with open('accepted_users.json', 'r') as f:
        accepted_users = json.load(f)
    
    if str(ctx.user.id) not in accepted_users:
        # Create a view with the button
        view = ConfirmButton()
        await ctx.response.send_message("Please agree to T&Cs by clicking the button below:", view=view)
    else:
        # Proceed with the image generation process
        await ctx.response.send_message(f"Generating image for prompt: {prompt}")

# Run the bot
client.run(TOKEN)

Explanation:

  1. Define a View: ConfirmButton is a subclass of discord.ui.View. It defines a button and a method that gets called when the button is clicked. The style=discord.ButtonStyle.primary argument styles the button as a primary button, and the custom_id="Confirm image" is used to identify the button.

  2. Handling Button Interaction: When the button is clicked, the confirm_button method is called. Inside this method, we can process the user’s response (e.g., mark the user as accepted). In the code above, I assume the accepted users are stored in a JSON file (accepted_users.json), and we update it when the user clicks the button.

  3. Command Handling: In the /image command, we check if the user has accepted the T&Cs (i.e., if their ID is in the accepted_users.json file). If not, we send a message with the ConfirmButton view attached, which contains the button. If the user has already accepted, we proceed with generating the image.

Things to note:

  • Ephemeral Messages: When a button is clicked, I used ephemeral=True for the response to ensure that only the user who clicked the button can see the confirmation message.
  • Button Style: The style of the button is set with ButtonStyle.primary, but you can also use ButtonStyle.secondary, ButtonStyle.success, ButtonStyle.danger, etc., based on your preference.
  • Timeout: The timeout=None argument in View ensures the view never times out. You can set it to a number of seconds if you want the button to expire after some time.

This should allow you to send a message with a button and have your bot process the interaction when the button is clicked.