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

examples/select.py - creating a Bot object#
from __future__ import annotations

import os
import typing

import disnake
from disnake.ext import commands, components

bot = commands.InteractionBot()

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

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

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 and register the select.

examples/select.py - creating a select component#
 1@manager.register
 2class MySelect(components.RichStringSelect):
 3    placeholder: typing.Optional[str] = "Please select a square."
 4    options: typing.List[disnake.SelectOption] = SLOT_OPTIONS
 5
 6    slot: str = "0"
 7    state: str = "slot"
 8    colour_left: str = BLACK_SQUARE
 9    colour_middle: str = BLACK_SQUARE
10    colour_right: str = BLACK_SQUARE
11
12    async def callback(self, inter: components.MessageInteraction) -> None:
13        assert inter.values is not None
14        selected = inter.values[0]
15
16        if self.state == "slot":
17            self.handle_slots(selected)
18
19        else:
20            self.handle_colours(selected)
21
22        msg = self.render_colours()
23        await inter.response.edit_message(msg, components=self)
24
25    def handle_slots(self, selected: str) -> None:
26        if selected == "Finalise":
27            self.disabled = True
28            self.placeholder = "Woo!"
29            return
30
31        self.options = COLOUR_OPTIONS
32        self.placeholder = f"Please select a colour for the {selected} square."
33
34        self.slot = selected
35        self.state = "colour"
36
37    def handle_colours(self, selected: str) -> None:
38        self.options = SLOT_OPTIONS
39
40        setattr(self, f"colour_{self.slot}", selected)
41        self.state = "slot"
42
43    def render_colours(self) -> str:
44        return f"{self.colour_left}{self.colour_middle}{self.colour_right}\n"

Set the placeholder text (line 3)… Set the options (line 4)… We store the slot the user is currently working with (line 6)… We store whether they’re picking a slot or a colour (line 7)… And we store the colours for the three slots (lines 63-65)…

In the callback first we get the selected value.

This should never raise for a select.

 1    async def callback(self, inter: components.MessageInteraction) -> None:
 2        assert inter.values is not None
 3        selected = inter.values[0]
 4
 5        if self.state == "slot":
 6            self.handle_slots(selected)
 7
 8        else:
 9            self.handle_colours(selected)
10
11        msg = self.render_colours()
12        await inter.response.edit_message(msg, components=self)

If the selection was a slot, run slot selection logic (lines 5-6). To keep things tidy, we use a separate function for this. Otherwise, run colour selection logic (lines 8-9). Finally we render the new colours and update the select (lines 77-78).

Then in handle_slots:

 1    def handle_slots(self, selected: str) -> None:
 2        if selected == "Finalise":
 3            self.disabled = True
 4            self.placeholder = "Woo!"
 5            return
 6
 7        self.options = COLOUR_OPTIONS
 8        self.placeholder = f"Please select a colour for the {selected} square."
 9
10        self.slot = selected
11        self.state = "colour"

In case the user wishes to finalize, disable the select (lines 2-5). Update options and display (lines 7-8). Set the slot to the user’s selection and set state to colour (lines 10-11).

Then in handle_colours:

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

        setattr(self, f"colour_{self.slot}", selected)
        self.state = "slot"

Update the options set the corresponding colour attribute and set state to slot.

Then in render_colours:

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

Render our three squares.

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

examples/select.py - sending the component#
@bot.slash_command()  # pyright: ignore  # still some unknowns in disnake
async def test_select(inter: disnake.CommandInteraction) -> None:
    wrapped = components.wrap_interaction(inter)

    component = MySelect()
    await wrapped.response.send_message(
        component.render_colours(), components=component
    )

Tip

Wrapping the interaction allows you to send the component as-is.

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

Lastly, we run the bot.

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

Source Code#

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