Select#

A simple example on the use of selects with disnake-ext-components.

For this example, we implement a select menu with double functionality. Firstly, the select allows you to select one of three slots. After selecting a slot, the select is modified to instead allow you to select a colour. The selected slot and colour are then combined to colour the corresponding square.

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

examples/select.py - creating a Bot object#
import os

import disnake
from disnake.ext import commands, components

bot = commands.InteractionBot()

Create a manager object and register all components (current and future) to this manager.

examples/select.py - creating a manager and registering components#
manager = components.ComponentManager(bot)
manager.basic_config()

Define possible slots for our select.

examples/select.py - defining slots#
LEFT = "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}"
MIDDLE = "\N{BLACK CIRCLE FOR RECORD}\N{VARIATION SELECTOR-16}"
RIGHT = "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}"

SLOT_OPTIONS = [
    disnake.SelectOption(label="Left", value="left", emoji=LEFT),
    disnake.SelectOption(label="Middle", value="middle", emoji=MIDDLE),
    disnake.SelectOption(label="Right", value="right", emoji=RIGHT),
    disnake.SelectOption(label="Finalise", emoji="\N{WHITE HEAVY CHECK MARK}"),
]

Define possible colours for our select.

examples/select.py - defining colours#
BLACK_SQUARE = "\N{BLACK LARGE SQUARE}"
BLUE_SQUARE = "\N{LARGE BLUE SQUARE}"
BROWN_SQUARE = "\N{LARGE BROWN SQUARE}"
GREEN_SQUARE = "\N{LARGE GREEN SQUARE}"
PURPLE_SQUARE = "\N{LARGE PURPLE SQUARE}"
RED_SQUARE = "\N{LARGE RED SQUARE}"
WHITE_SQUARE = "\N{WHITE LARGE SQUARE}"
YELLOW_SQUARE = "\N{LARGE YELLOW SQUARE}"

COLOUR_OPTIONS = [
    disnake.SelectOption(label="Black", value=BLACK_SQUARE, emoji=BLACK_SQUARE),
    disnake.SelectOption(label="Blue", value=BLUE_SQUARE, emoji=BLUE_SQUARE),
    disnake.SelectOption(label="Brown", value=BROWN_SQUARE, emoji=BROWN_SQUARE),
    disnake.SelectOption(label="Green", value=GREEN_SQUARE, emoji=GREEN_SQUARE),
    disnake.SelectOption(label="Purple", value=PURPLE_SQUARE, emoji=PURPLE_SQUARE),
    disnake.SelectOption(label="Red", value=RED_SQUARE, emoji=RED_SQUARE),
    disnake.SelectOption(label="White", value=WHITE_SQUARE, emoji=WHITE_SQUARE),
    disnake.SelectOption(label="Yellow", value=YELLOW_SQUARE, emoji=YELLOW_SQUARE),
]

Then, we make the select.

examples/select.py - creating a select subclass#
class MySelect(components.RichStringSelect):

    placeholder = "Please select a square."  # Set the placeholder text...
    options = SLOT_OPTIONS  # Set the options...

    slot: str = "0"  # We store the slot the user is currently working with...
    state: str = "slot"  # We store whether they're picking a slot or a colour...
    colour_left: str = BLACK_SQUARE  # And we store the colours for the three slots...
    colour_middle: str = BLACK_SQUARE
    colour_right: str = BLACK_SQUARE

    async def callback(self, inter: components.MessageInteraction) -> None:
        # First we get the selected value.
        assert inter.values is not None  # This should never raise for a select.
        selected = inter.values[0]

        # If the selection was a slot, run slot selection logic.
        # To keep things tidy, we use a separate function for this.
        if self.state == "slot":
            self.handle_slots(selected)

        # Otherwise, run colour selection logic.
        else:
            self.handle_colours(selected)

        # Render the new colours and update the select.
        msg = self.render_colours()
        await inter.response.edit_message(msg, components=self)

    def handle_slots(self, selected: str) -> None:
        # In case the user wishes to finalize, disable the select.
        if selected == "Finalise":
            self.disabled = True
            self.placeholder = "Woo!"
            return

        # Update options and display.
        self.options = COLOUR_OPTIONS
        self.placeholder = f"Please select a colour for the {selected} square."

        # Set the slot to the user's selection and set state to colour.
        self.slot = selected
        self.state = "colour"

    def handle_colours(self, selected: str) -> None:
        # Update options.
        self.options = SLOT_OPTIONS

        # Set the corresponding colour attribute and set state to slot.
        setattr(self, f"colour_{self.slot}", selected)
        self.state = "slot"

    def render_colours(self) -> str:
        # Render our three squares.
        return f"{self.colour_left}{self.colour_middle}{self.colour_right}\n"

Finally, we make a command that sends the component. In this command, we initialise the timeout for the component.

examples/select.py - creating a select subclass#
@bot.slash_command()  # pyright: ignore  # still some unknowns in disnake
async def test_select(inter: disnake.CommandInteraction) -> None:
    # Wrapping the interaction allows you to send the component as-is.
    wrapped = components.wrap_interaction(inter)

    component = MySelect()
    await wrapped.send(component.render_colours(), components=component)

    # If we had not wrapped the interaction, we would have needed to do
    # `await inter.send(components=await component.as_ui_component())`
    # instead.


bot.run(os.getenv("EXAMPLE_TOKEN"))

Source Code#

examples/select.py#
 1import os
 2
 3import disnake
 4from disnake.ext import commands, components
 5
 6bot = commands.InteractionBot()
 7manager = components.ComponentManager(bot)
 8manager.basic_config()
 9
10
11LEFT = "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}"
12MIDDLE = "\N{BLACK CIRCLE FOR RECORD}\N{VARIATION SELECTOR-16}"
13RIGHT = "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}"
14
15SLOT_OPTIONS = [
16    disnake.SelectOption(label="Left", value="left", emoji=LEFT),
17    disnake.SelectOption(label="Middle", value="middle", emoji=MIDDLE),
18    disnake.SelectOption(label="Right", value="right", emoji=RIGHT),
19    disnake.SelectOption(label="Finalise", emoji="\N{WHITE HEAVY CHECK MARK}"),
20]
21
22
23BLACK_SQUARE = "\N{BLACK LARGE SQUARE}"
24BLUE_SQUARE = "\N{LARGE BLUE SQUARE}"
25BROWN_SQUARE = "\N{LARGE BROWN SQUARE}"
26GREEN_SQUARE = "\N{LARGE GREEN SQUARE}"
27PURPLE_SQUARE = "\N{LARGE PURPLE SQUARE}"
28RED_SQUARE = "\N{LARGE RED SQUARE}"
29WHITE_SQUARE = "\N{WHITE LARGE SQUARE}"
30YELLOW_SQUARE = "\N{LARGE YELLOW SQUARE}"
31
32COLOUR_OPTIONS = [
33    disnake.SelectOption(label="Black", value=BLACK_SQUARE, emoji=BLACK_SQUARE),
34    disnake.SelectOption(label="Blue", value=BLUE_SQUARE, emoji=BLUE_SQUARE),
35    disnake.SelectOption(label="Brown", value=BROWN_SQUARE, emoji=BROWN_SQUARE),
36    disnake.SelectOption(label="Green", value=GREEN_SQUARE, emoji=GREEN_SQUARE),
37    disnake.SelectOption(label="Purple", value=PURPLE_SQUARE, emoji=PURPLE_SQUARE),
38    disnake.SelectOption(label="Red", value=RED_SQUARE, emoji=RED_SQUARE),
39    disnake.SelectOption(label="White", value=WHITE_SQUARE, emoji=WHITE_SQUARE),
40    disnake.SelectOption(label="Yellow", value=YELLOW_SQUARE, emoji=YELLOW_SQUARE),
41]
42
43
44class MySelect(components.RichStringSelect):
45
46    placeholder = "Please select a square."  # Set the placeholder text...
47    options = SLOT_OPTIONS  # Set the options...
48
49    slot: str = "0"  # We store the slot the user is currently working with...
50    state: str = "slot"  # We store whether they're picking a slot or a colour...
51    colour_left: str = BLACK_SQUARE  # And we store the colours for the three slots...
52    colour_middle: str = BLACK_SQUARE
53    colour_right: str = BLACK_SQUARE
54
55    async def callback(self, inter: components.MessageInteraction) -> None:
56        assert inter.values is not None  # This should never raise for a select.
57        selected = inter.values[0]
58
59        if self.state == "slot":
60            self.handle_slots(selected)
61
62        else:
63            self.handle_colours(selected)
64
65        msg = self.render_colours()
66        await inter.response.edit_message(msg, components=self)
67
68    def handle_slots(self, selected: str) -> None:
69        if selected == "Finalise":
70            self.disabled = True
71            self.placeholder = "Woo!"
72            return
73
74        self.options = COLOUR_OPTIONS
75        self.placeholder = f"Please select a colour for the {selected} square."
76
77        self.slot = selected
78        self.state = "colour"
79
80    def handle_colours(self, selected: str) -> None:
81        self.options = SLOT_OPTIONS
82
83        setattr(self, f"colour_{self.slot}", selected)
84        self.state = "slot"
85
86    def render_colours(self) -> str:
87        return f"{self.colour_left}{self.colour_middle}{self.colour_right}\n"
88
89
90@bot.slash_command()  # pyright: ignore  # still some unknowns in disnake
91async def test_select(inter: disnake.CommandInteraction) -> None:
92    wrapped = components.wrap_interaction(inter)
93
94    component = MySelect()
95    await wrapped.send(component.render_colours(), components=component)
96
97
98bot.run(os.getenv("EXAMPLE_TOKEN"))