Skip to content

Commit e3b801b

Browse files
dlchampshiftinvabhigyantrips
authored
feat: update /interaction/select-menu (#54)
## Description Updates the page at /interactions/select-menu with relevant code examples and information about using the various select menus within disnake. ## Relevant Issues Closes #53 --------- Signed-off-by: DLCHAMP <[email protected]> Co-authored-by: shiftinv <[email protected]> Co-authored-by: Abhigyan Tripathi <[email protected]>
1 parent f440392 commit e3b801b

File tree

3 files changed

+298
-2
lines changed

3 files changed

+298
-2
lines changed
24 KB
Loading
Lines changed: 292 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,298 @@
11
---
22
description: They refer to views, buttons and select menus that can be added to the messages your bot sends.
3-
hide_table_of_contents: true
43
---
54

65
# Select Menus
76

8-
<WorkInProgress />
7+
Select Menus allow users to interact with your bot through an interactive dropdown component. This component provides selectable options for your users to choose from.
8+
You can require users to select a minimum/maximum number of options using <DocsLink reference="disnake.ui.BaseSelect.min_values">min_values</DocsLink> and <DocsLink reference="disnake.ui.BaseSelect.max_values">max_values</DocsLink>.
9+
10+
Select Menus are available as the following types:
11+
12+
- <DocsLink reference="disnake.ui.StringSelect">disnake.ui.StringSelect</DocsLink> Allows you to input custom string options
13+
for users to select from, up to 25
14+
- <DocsLink reference="disnake.ui.UserSelect">disnake.ui.UserSelect</DocsLink> User or Member objects as options
15+
- <DocsLink reference="disnake.ui.RoleSelect">disnake.ui.RoleSelect</DocsLink> Role objects as options
16+
- <DocsLink reference="disnake.ui.ChannelSelect">disnake.ui.ChannelSelect</DocsLink> Channel objects as options
17+
18+
The options for user, role, and channel select menus are populated by Discord, and currently cannot be limited to a specific subset. See [below](#other-selects) for details.
19+
20+
This guide will walk you through a pretty basic use-case for a `StringSelect` dropdowns using [Views](#example-of-stringselects) and [low-level components](#views-vs-low-level-components).
21+
If you need more examples, you can always check the examples in the [disnake repo](https://github.com/DisnakeDev/disnake/tree/master/examples).
22+
&nbsp;
23+
:::note
24+
A message can have a maximum of 5 rows of components. Select components each take a single row, therefore you cannot have more than 5 Select components per message
25+
:::
26+
27+
<br />
28+
<p align="center">
29+
<img src={require('./images/string-select.png').default} alt="StringSelect example" width="60%" />
30+
</p>
31+
<br />
32+
33+
### Example of `StringSelect`s
34+
35+
<Tabs>
36+
<TabItem value="subclass" label="subclassing.py">
37+
38+
```python
39+
import os
40+
41+
import disnake
42+
from disnake.ext import commands
43+
44+
45+
# Defines the StringSelect that contains animals that your users can choose from
46+
class AnimalDropdown(disnake.ui.StringSelect):
47+
def __init__(self):
48+
49+
# Define the options that will be displayed inside the dropdown.
50+
# You may not have more than 25 options.
51+
# There is a `value` keyword that is being omitted, which is useful if
52+
# you wish to display a label to the user, but handle a different value
53+
# here within the code, like an index number or database id.
54+
options = [
55+
disnake.SelectOption(label="Dog", description="Dogs are your favorite type of animal"),
56+
disnake.SelectOption(label="Cat", description="Cats are your favorite type of animal"),
57+
disnake.SelectOption(
58+
label="Snake", description="Snakes are your favorite type of animal"
59+
),
60+
disnake.SelectOption(
61+
label="Gerbil", description="Gerbils are your favorite type of animal"
62+
),
63+
]
64+
65+
# We will include a placeholder that will be shown until an option has been selected.
66+
# The min and max values indicate the minimum and maximum number of options to be selected -
67+
# in this example we will only allow one option to be selected.
68+
super().__init__(
69+
placeholder="Choose an animal",
70+
min_values=1,
71+
max_values=1,
72+
options=options,
73+
)
74+
75+
# This callback is called each time a user has selected an option
76+
async def callback(self, inter: disnake.MessageInteraction):
77+
# Use the interaction object to respond to the interaction.
78+
# `self` refers to this StringSelect object, and the `values`
79+
# attribute contains a list of the user's selected options.
80+
# We only want the first (and in this case, only) one.
81+
await inter.response.send_message(f"Your favorite type of animal is: {self.values[0]}")
82+
83+
84+
# Now that we have created the Select object with its options and callback, we need to attach it to a
85+
# View that will be displayed to the user in the command response.
86+
#
87+
# Views have a default timeout of 180s, at which the bot will stop listening for those events.
88+
# You may pass any float here, or `None` if you wish to remove the timeout.
89+
# Note: If `None` is passed, this view will persist only until the bot is restarted. If you wish to have persistent views,
90+
# consider using low-level components or check out the persistent view example:
91+
# https://github.com/DisnakeDev/disnake/blob/master/examples/views/persistent.py
92+
class DropDownView(disnake.ui.View):
93+
def __init__(self):
94+
# You would pass a new `timeout=` if you wish to alter it, but
95+
# we will leave it empty for this example so that it uses the default 180s.
96+
super().__init__()
97+
98+
# Now let's add the `StringSelect` object we created above to this view
99+
self.add_item(AnimalDropdown())
100+
101+
102+
# Finally, we create a basic bot instance with a command that will utilize the created view and dropdown.
103+
bot = commands.Bot()
104+
105+
106+
@bot.listen()
107+
async def on_ready():
108+
print(f"Logged in as {bot.user} (ID: {bot.user.id})\n------")
109+
110+
111+
@bot.slash_command()
112+
async def animals(inter: disnake.ApplicationCommandInteraction):
113+
"""Sends a message with our dropdown containing the animals"""
114+
115+
# Create the view with our dropdown object
116+
view = DropDownView()
117+
118+
# Respond to the interaction with a message and our view
119+
await inter.response.send_message("What is your favorite type of animal?", view=view)
120+
121+
122+
if __name__ == "__main__":
123+
bot.run(os.getenv("BOT_TOKEN"))
124+
```
125+
126+
</TabItem>
127+
<TabItem value="decorator" label="decorator.py">
128+
129+
```python
130+
# Instead of subclassing `disnake.ui.StringSelect`, this example shows how to use the
131+
# `@disnake.ui.string_select` decorator directly inside the View to create the dropdown
132+
# component.
133+
class AnimalView(disnake.ui.View):
134+
def __init__(self):
135+
super().__init__()
136+
137+
# If you wish to pass a previously defined sequence of values to this `View` so that
138+
# you may have dynamic options, you can do so by defining them within this __init__ method.
139+
# `self.animal_callback.options = [...]`
140+
141+
@disnake.ui.string_select(
142+
placeholder="Choose an animal",
143+
options=[
144+
disnake.SelectOption(label="Dog", description="Dogs are your favorite type of animal"),
145+
disnake.SelectOption(label="Cat", description="Cats are your favorite type of animal"),
146+
disnake.SelectOption(
147+
label="Snake", description="Snakes are your favorite type of animal"
148+
),
149+
disnake.SelectOption(
150+
label="Gerbil", description="Gerbils are your favorite type of animal"
151+
),
152+
],
153+
min_values=1,
154+
max_values=1,
155+
)
156+
async def animal_callback(
157+
self, select: disnake.ui.StringSelect, inter: disnake.MessageInteraction
158+
):
159+
# The main difference in this callback is that we access the `StringSelect` through the
160+
# parameter passed to the callback, vs the subclass example where we access it via `self`
161+
await inter.response.send_message(f"You favorite type of animal is: {select.values[0]}")
162+
```
163+
164+
</TabItem>
165+
</Tabs>
166+
167+
### Other Selects
168+
169+
The three other select components available are constructed and can be used in the same manner.
170+
The main difference is that we do not create nor pass any options to these Select objects as Discord will provide these options to the user automatically.
171+
The selected option(s) that are available in `self.values` will be the selected object(s) rather than the string values.
172+
173+
- `UserSelect.values` will return a list of <DocsLink reference="disnake.User">disnake.User</DocsLink> or <DocsLink reference="disnake.Member">disnake.Member</DocsLink>
174+
- `RoleSelect.values` will return a list of <DocsLink reference="disnake.Role">disnake.Role</DocsLink>
175+
- `ChannelSelect.values` returns a list of <DocsLink reference="disnake.abc.GuildChannel">disnake.abc.GuildChannel</DocsLink>, <DocsLink reference="disnake.Thread">disnake.Thread</DocsLink>, or <DocsLink reference="disnake.PartialMessageable">disnake.PartialMessageable</DocsLink>
176+
177+
:::note
178+
179+
<DocsLink reference="disnake.ui.ChannelSelect">disnake.ui.ChannelSelect</DocsLink> has an extra keyword argument that is
180+
not available to the other SelectMenu types.
181+
182+
`channel_types` will allow you to specify the types of channels made available as options (omit to show all available channels):
183+
184+
- `channel_types=[ChannelType.text]` to display only guild text channels as options
185+
- `channel_types=[ChannelType.voice, ChannelType.stage_voice]` to display only guild voice and stage channels as options
186+
- etc.
187+
188+
See <DocsLink reference="disnake.ChannelType">disnake.ChannelType</DocsLink> to see more channel types.
189+
:::
190+
191+
### Handling View Timeouts
192+
193+
When a View times out, the bot will no longer listen for these events, and your users will receive an error `This interaction failed`.
194+
195+
To avoid this, you have a couple of options when the view times out:
196+
197+
1. Disable the components within the view so that they are no longer interactive.
198+
2. Remove the view altogether, leaving only the original message without components.
199+
200+
For this example we will disable the components using an `on_timeout` method. However, if you wish to remove the View completely you can pass `view=None` (instead of `view=self` like the example below).
201+
202+
We'll continue with the `subclassing.py` example from above, only altering the relevant parts:
203+
204+
```python title="viewtimeout.py"
205+
...
206+
207+
208+
class DropDownView(disnake.ui.View):
209+
210+
message: disnake.Message # adding to typehint the future `message` attribute
211+
212+
def __init__(self):
213+
# this time we will set the timeout to 30.0s
214+
super().__init__(timeout=30.0)
215+
# now let's add the `StringSelect` object we created above to this view
216+
self.add_item(AnimalDropdown())
217+
218+
# To handle this timeout, we'll need to override the default `on_timeout` method within the `View``
219+
async def on_timeout(self):
220+
# Now we will edit the original command response so that it displays the disabled view.
221+
# Since `self.children` returns a list of the components attached to this `View` and we want
222+
# to prevent the components from remaining interactive after the timeout, we can easily loop
223+
# over its components and disable them.
224+
for child in self.children:
225+
if isinstance(child, (disnake.ui.Button, disnake.ui.BaseSelect)):
226+
child.disabled = True
227+
228+
# Now, edit the message with this updated `View`
229+
await self.message.edit(view=self)
230+
231+
232+
...
233+
# The only changes we need to make to handle the updated view is to fetch the original response after,
234+
# so that the message can be edited later.
235+
# This is necessary because `interaction.send` methods do not return a message object
236+
@bot.slash_command()
237+
async def animals(inter: disnake.ApplicationCommandInteraction):
238+
"""Sends a message with our dropdown containing the animals"""
239+
240+
# Create the view with our dropdown object.
241+
view = DropDownView()
242+
243+
# Respond to the interaction with a message and our view.
244+
await inter.response.send_message("What is your favorite type of animal?", view=view)
245+
246+
# This will add a new `message` attribute to the `DropDownView` that will be edited
247+
# once the view has timed out
248+
view.message = await inter.original_response()
249+
250+
251+
...
252+
```
253+
254+
### Views vs. low-level components
255+
256+
As an alternative to using `View`s, it is possible to use Select Menus as low-level components.
257+
These components do not need to be sent as part of a View and can be sent as is.
258+
259+
Note that any component being sent in this manner must have a `custom_id` explicitly set. Component interactions are sent to all listeners,
260+
which means the `custom_id` should be unique for each component to be able to identify the component in your code.
261+
262+
The main advantage of this is that listeners, by nature, are persistent and will remain fully functional over bot reloads. Listeners are stored in the bot
263+
strictly once, and are shared by all components. Because of this, the memory footprint will generally be smaller than that of an equivalent view.
264+
265+
The example below will be similar to the above examples, however will be executed as a low level component instead.
266+
267+
```python title="low_level_dropdown.py"
268+
@bot.slash_command()
269+
async def animals(inter: disnake.ApplicationCommandInteraction):
270+
"""Sends a message with our dropdown containing the animals"""
271+
272+
await inter.response.send_message(
273+
"What is your favorite type of animal?",
274+
components=[
275+
disnake.ui.StringSelect(
276+
custom_id="fav_animal",
277+
options=["Dog", "Cat", "Snake", "Gerbil"],
278+
)
279+
],
280+
)
281+
282+
283+
# Now we create the listener that will handle the users's selection(s), similarly to the callback we used above.
284+
@bot.listen("on_dropdown")
285+
async def fav_animal_listener(inter: disnake.MessageInteraction):
286+
# First we should check if the interaction is for the `fav_animal` dropdown we created
287+
# and ignore if it isn't.
288+
if inter.component.custom_id != "fav_animal":
289+
return
290+
291+
# Now we can respond with the user's favorite animal
292+
await inter.response.send_message(f"Your favorite type of animal is: {inter.values[0]}")
293+
```
294+
295+
:::note
296+
These component listeners can be used inside cogs as well. Simply replace `@bot.listen()` with `@commands.Cog.listener()` and
297+
be sure to pass `self` as the first argument of the listener function
298+
:::

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)