Attrs#

An example showcasing how attrs utilities can be used with ext-components.

Say we wish to create a component, but we do not know the number of options beforehand, and we would like the user to be able to select all of them. It can be cumbersome to manually keep updating the max_values parameter of the select.

Luckily, with the knowledge that ext-components is built upon the attrs lib, a few options become available to us.

For this example, we will be making use of attrs classes’ __attrs_post_init__ method, which is called immediately after attrs finishes its normal initialisation logic. If you’re familiar with dataclasses, this is essentially the same as a dataclass’ __post_init__ method.

First and foremost, we create a bot as per usual. Since we don’t need any prefix command capabilities, we opt for an InteractionBot.

examples/attrs.py - create a Bot object#
import os

import disnake
from disnake.ext import commands, components

bot = commands.InteractionBot()

Next, we make a component manager and register it to the bot.

examples/attrs.py - create a manager and registering it#
manager = components.get_manager()
manager.add_to_bot(bot)

Now we create our customizable select.

examples/attrs.py - create a select#
@manager.register
class CustomisableSelect(components.RichStringSelect):
    def __attrs_post_init__(self) -> None:
        self.max_values = len(self.options)

    async def callback(self, interaction: components.MessageInteraction) -> None:
        selection = (
            "\n".join(f"- {value}" for value in interaction.values)
            if interaction.values
            else "nothing :("
        )

        await interaction.response.send_message(
            f"You selected:\n{selection}", ephemeral=True
        )

Now, we ensure that max_values adapts to the passed number of options. Since rich components use attrs under the hood, this can easily be achieved through the __attrs_post_init__ method.

examples/attrs.py - customisation logic#
    def __attrs_post_init__(self) -> None:
        self.max_values = len(self.options)

Then we create our test command and send the previously created customisable select.

examples/attrs.py - create a command#
 1@bot.slash_command()  # pyright: ignore
 2async def make_select(interaction: disnake.CommandInteraction, options: str) -> None:
 3    """Make your own select menu.
 4
 5    Parameters
 6    ----------
 7    options:
 8        A comma-separated string with all options. Max 25.
 9    """
10    if not options.strip():
11        await interaction.response.send_message("You must specify at least one option!")
12        return
13
14    actual_options = [
15        disnake.SelectOption(label=option.strip())
16        for option in options.split(",")
17    ]  # fmt: skip
18
19    if len(actual_options) > 25:
20        await interaction.response.send_message("You must specify at most 25 options!")
21        return
22
23    wrapped = components.wrap_interaction(interaction)
24    await wrapped.response.send_message(
25        components=CustomisableSelect(options=actual_options),
26    )

If the string is empty or whitespace, the user did not provide options (lines 10-12). Next, we make the options by splitting over commas (lines 14-17). Before creating the component, validate that there’s max 25 options (lines 19-21). Finally if everything went correctly, we send the component.

Lastly, we run the bot.

examples/attrs.py - run the bot#
bot.run(os.getenv("EXAMPLE_TOKEN"))

Source Code#

examples/attrs.py#
 1import os
 2
 3import disnake
 4from disnake.ext import commands, components
 5
 6bot = commands.InteractionBot()
 7
 8manager = components.get_manager()
 9manager.add_to_bot(bot)
10
11
12@manager.register
13class CustomisableSelect(components.RichStringSelect):
14    def __attrs_post_init__(self) -> None:
15        self.max_values = len(self.options)
16
17    async def callback(self, interaction: components.MessageInteraction) -> None:
18        selection = (
19            "\n".join(f"- {value}" for value in interaction.values)
20            if interaction.values
21            else "nothing :("
22        )
23
24        await interaction.response.send_message(
25            f"You selected:\n{selection}", ephemeral=True
26        )
27
28
29@bot.slash_command()  # pyright: ignore
30async def make_select(interaction: disnake.CommandInteraction, options: str) -> None:
31    """Make your own select menu.
32
33    Parameters
34    ----------
35    options:
36        A comma-separated string with all options. Max 25.
37    """
38    if not options.strip():
39        await interaction.response.send_message("You must specify at least one option!")
40        return
41
42    actual_options = [
43        disnake.SelectOption(label=option.strip())
44        for option in options.split(",")
45    ]  # fmt: skip
46
47    if len(actual_options) > 25:
48        await interaction.response.send_message("You must specify at most 25 options!")
49        return
50
51    wrapped = components.wrap_interaction(interaction)
52    await wrapped.response.send_message(
53        components=CustomisableSelect(options=actual_options),
54    )
55
56
57bot.run(os.getenv("EXAMPLE_TOKEN"))