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
.
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.
manager = components.get_manager()
manager.add_to_bot(bot)
Define possible slots for our select.
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.
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.
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.
@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.
bot.run(os.getenv("EXAMPLE_TOKEN"))
Source Code#
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"))