Skip to content

Commit 8df8216

Browse files
committed
Merge remote-tracking branch 'dlchamp/update-page/select-menu' into dev
2 parents 337719e + 9d92d91 commit 8df8216

File tree

3 files changed

+293
-2
lines changed

3 files changed

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

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)