How to dynamically generate Discord bot buttons in Python?

I’m trying to build a flexible system for creating buttons in my Discord bot. Currently, I can define buttons like this:

class YesNoView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=30)
        self.result = None

    @discord.ui.button(label='Yes', style=discord.ButtonStyle.green)
    async def yes_click(self, interaction, button):
        self.result = 'Yes'
        await interaction.response.defer()
        self.stop()

    @discord.ui.button(label='No', style=discord.ButtonStyle.red)
    async def no_click(self, interaction, button):
        self.result = 'No'
        await interaction.response.defer()
        self.stop()

I use it like this:

view = YesNoView()
await ctx.send('Are you sure?', view=view)
await view.wait()
print(view.result)

However, I want to create a generic class that can generate an arbitrary number of buttons. I tried this approach:

class MultiButtonView(discord.ui.View):
    def __init__(self, buttons):
        super().__init__()
        self.result = None
        for label, value, color in buttons:
            self.add_item(self.make_button(label, value, color))

    def make_button(self, label, value, color):
        async def click(interaction):
            self.result = value
            await interaction.response.defer()
            self.stop()
        return discord.ui.Button(label=label, style=color, callback=click)

When I call view = MultiButtonView([('Yes', 'Yes', discord.ButtonStyle.green), ('No', 'No', discord.ButtonStyle.red)]), no buttons appear. What could be the issue and how can I resolve it?

hey mate, i had a similar prob with my bot. try using lambda functions for the callbacks. something like:

class MultiButtonView(discord.ui.View):
    def __init__(self, buttons):
        super().__init__()
        self.result = None
        for label, value, color in buttons:
            self.add_item(discord.ui.Button(label=label, style=color, custom_id=value, callback=lambda i, b=value: self.click(i, b)))

    async def click(self, interaction, value):
        self.result = value
        await interaction.response.defer()
        self.stop()

this should work for ya. lemme know if u need more help!

I’ve dealt with similar challenges in my Discord bot projects. The issue you’re facing is likely related to how button callbacks are handled in discord.py.

One approach that worked well for me was using a class method as the callback instead of a closure. Here’s a modified version of your MultiButtonView class that should solve the problem:

class MultiButtonView(discord.ui.View):
    def __init__(self, buttons):
        super().__init__()
        self.result = None
        for label, value, color in buttons:
            button = discord.ui.Button(label=label, style=color)
            button.callback = self.make_callback(value)
            self.add_item(button)

    def make_callback(self, value):
        async def callback(interaction):
            self.result = value
            await interaction.response.defer()
            self.stop()
        return callback

This method creates a separate callback function for each button, which should resolve the issue you’re experiencing. Give it a try and see if it works for your use case.

I’ve encountered a similar issue when trying to dynamically generate buttons. The problem likely stems from how Discord.py handles button callbacks. Instead of using a closure, try subclassing discord.ui.Button and overriding its callback method. Here’s a modified version that should work:

class DynamicButton(discord.ui.Button):
    def __init__(self, label, value, color):
        super().__init__(label=label, style=color)
        self.value = value

    async def callback(self, interaction):
        view = self.view
        view.result = self.value
        await interaction.response.defer()
        view.stop()

class MultiButtonView(discord.ui.View):
    def __init__(self, buttons):
        super().__init__()
        self.result = None
        for label, value, color in buttons:
            self.add_item(DynamicButton(label, value, color))

This approach should create the buttons as expected and properly handle interactions. Let me know if you need any clarification on this solution.