Skip to content

Commit 57e4f77

Browse files
authored
docs: improve slash command guide (#1631)
* docs: improve slash command guide * fix: oops * fix: grammar
1 parent 962084b commit 57e4f77

File tree

1 file changed

+103
-50
lines changed

1 file changed

+103
-50
lines changed

docs/src/Guides/03 Creating Commands.md

Lines changed: 103 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ async def my_long_command_function(ctx: SlashContext):
3232
await ctx.send("Hello World")
3333
```
3434
???+ note
35-
Command names must be lowercase and can only contain `-` and `_` as special symbols and must not contain spaces.
35+
Command names must be lowercase, can only contain `-` and `_` as special symbols, and must not contain spaces.
3636

37-
When testing, it is recommended to use non-global commands, as they sync instantly.
37+
By default, commands are assumed to be global, meaning they can be used by every server the bot is in. When testing, it is recommended to use non-global commands.
3838
For that, you can either define `scopes` in every command or set `debug_scope` in the bot instantiation which sets the scope automatically for all commands.
3939

4040
You can define non-global commands by passing a list of guild ids to `scopes` in the interaction creation.
@@ -64,15 +64,17 @@ async def my_command_function(ctx: SlashContext):
6464
await ctx.send("Hello World")
6565
```
6666

67-
This will show up in discord as `/base group command`. There are more ways to add additional subcommands:
67+
This will show up in discord as `/base group command`. You may also wish to drop the `group` part to have a `/base command` subcommand, which you can do by removing the `group_name` and `group_description` parameters (they are optional).
68+
69+
There are more ways to add additional subcommands to the base/group from there:
6870

6971
=== ":one: Decorator"
7072
```python
7173
@my_command_function.subcommand(
7274
group_name="group",
7375
group_description="My command group",
74-
sub_cmd_name="sub",
75-
sub_cmd_description="My subcommand",
76+
sub_cmd_name="second_command",
77+
sub_cmd_description="My second command",
7678
)
7779
async def my_second_command_function(ctx: SlashContext):
7880
await ctx.send("Hello World")
@@ -106,11 +108,12 @@ This will show up in discord as `/base group command`. There are more ways to ad
106108
await ctx.send("Hello World")
107109
```
108110

109-
For all of these, the "group" parts are optional, allowing you to do `/base command` instead.
111+
All of these would create a subcommand called `/base group second_command`.
110112

111113
???+ note
112-
You cannot mix group subcommands and non-group subcommands into one base command - you must either use all group subcommands or normal subcommands.
114+
Using subcommands makes using the base command unusable - IE, you cannot use `/base` as a normal command.
113115

116+
Group subcommands follow a similar logic, as by defining a group, you can no longer create normal subcommands - IE, you cannot use either `/base` or `/base command`.
114117

115118
## Options
116119

@@ -119,13 +122,13 @@ Interactions can also have options. There are a bunch of different [types of opt
119122
| Option Type | Return Type | Description |
120123
|---------------------------|--------------------------------------------|---------------------------------------------------------------------------------------------|
121124
| `OptionType.STRING` | `str` | Limit the input to a string. |
122-
| `OptionType.INTEGER` | `int` | Limit the input to a integer. |
123-
| `OptionType.NUMBER` | `float` | Limit the input to a float. |
125+
| `OptionType.INTEGER` | `int` | Limit the input to an integer between -2^53 and 2^53. |
126+
| `OptionType.NUMBER` | `float` | Limit the input to a float between -2^53 and 2^53. |
124127
| `OptionType.BOOLEAN` | `bool` | Let the user choose either `True` or `False`. |
125128
| `OptionType.USER` | `Member` in guilds, else `User` | Let the user choose a discord user from an automatically-generated list of options. |
126129
| `OptionType.CHANNEL` | `GuildChannel` in guilds, else `DMChannel` | Let the user choose a discord channel from an automatically-generated list of options. |
127130
| `OptionType.ROLE` | `Role` | Let the user choose a discord role from an automatically-generated list of options. |
128-
| `OptionType.MENTIONABLE` | `DiscordObject` | Let the user chose any discord mentionable from an automatically generated list of options. |
131+
| `OptionType.MENTIONABLE` | `Union[Member, User, Role]` | Let the user chose any discord mentionable from an automatically generated list of options. |
129132
| `OptionType.ATTACHMENT` | `Attachment` | Let the user upload an attachment. |
130133

131134
Now that you know all the options you have for options, you can opt into adding options to your interaction.
@@ -145,19 +148,30 @@ async def my_command_function(ctx: SlashContext, integer_option: int):
145148
await ctx.send(f"You input {integer_option}")
146149
```
147150

151+
!!! danger "Option Names"
152+
Be aware that, by default, the option `name` and the function parameter need to be the same (in this example both are `integer_option`).
153+
154+
If you want to use a different name for the function parameter, you can use the `argument_name` parameter in `@slash_option()`.
155+
148156
Options can either be required or not. If an option is not required, make sure to set a default value for them.
149157

150158
Always make sure to define all required options first, this is a Discord requirement!
151159
```python
152160
@slash_command(name="my_command", ...)
153161
@slash_option(
154-
name="integer_option",
155-
description="Integer Option",
162+
name="integer_option_1",
163+
description="Integer Option 1",
164+
required=True,
165+
opt_type=OptionType.INTEGER
166+
)
167+
@slash_option(
168+
name="integer_option_2",
169+
description="Integer Option 2",
156170
required=False,
157171
opt_type=OptionType.INTEGER
158172
)
159-
async def my_command_function(ctx: SlashContext, integer_option: int = 5):
160-
await ctx.send(f"You input {integer_option}")
173+
async def my_command_function(ctx: SlashContext, integer_option_1: int, integer_option_2: int = 5):
174+
await ctx.send(f"Sum: {integer_option_1 + integer_option_2}")
161175
```
162176

163177
For more information, please visit the API reference [here](/interactions.py/API Reference/API Reference/models/Internal/application_commands/#interactions.models.internal.application_commands.slash_option).
@@ -212,9 +226,6 @@ async def my_command_function(ctx: SlashContext, string_option: str):
212226
await ctx.send(f"You input `{string_option}` which is between 5 and 10 characters long")
213227
```
214228

215-
!!! danger "Option Names"
216-
Be aware that the option `name` and the function parameter need to be the same (In this example both are `integer_option`).
217-
218229

219230
## Option Choices
220231

@@ -242,7 +253,7 @@ async def my_command_function(ctx: SlashContext, integer_option: int):
242253

243254
For more information, please visit the API reference [here](/interactions.py/API Reference/API Reference/models/Internal/application_commands/#interactions.models.internal.application_commands.SlashCommandChoice).
244255

245-
## Autocomplete / More than 25 choices needed
256+
## Autocomplete / More Than 25 Choices Needed
246257

247258
If you have more than 25 choices the user can choose from, or you want to give a dynamic list of choices depending on what the user is currently typing, then you will need autocomplete options.
248259
The downside is that you need to supply the choices on request, making this a bit more tricky to set up.
@@ -269,7 +280,7 @@ from interactions import AutocompleteContext
269280

270281
@my_command_function.autocomplete("string_option")
271282
async def autocomplete(ctx: AutocompleteContext):
272-
string_option_input = ctx.input_text # can be empty
283+
string_option_input = ctx.input_text # can be empty/None
273284
# you can use ctx.kwargs.get("name") to get the current state of other options - note they can be empty too
274285

275286
# make sure you respond within three seconds
@@ -291,9 +302,12 @@ async def autocomplete(ctx: AutocompleteContext):
291302
)
292303
```
293304

294-
## Command definition without decorators
305+
???+ note
306+
Discord does not handle search narrowing for you when using autocomplete. You need to handle that yourself.
307+
308+
## Other Methods of Defining Slash Commands
295309

296-
There are currently four different ways to define interactions, one does not need any decorators at all.
310+
There are currently four different ways to define slash commands - one does not need any decorators at all.
297311

298312
=== ":one: Multiple Decorators"
299313

@@ -333,10 +347,14 @@ There are currently four different ways to define interactions, one does not nee
333347
=== ":three: Function Annotations"
334348

335349
```python
350+
from typing import Annotation # using Annotation is optional, but recommended
336351
from interactions import slash_int_option
337352

338353
@slash_command(name="my_command", description="My first command :)")
339-
async def my_command_function(ctx: SlashContext, integer_option: slash_int_option("Integer Option")):
354+
async def my_command_function(
355+
ctx: SlashContext,
356+
integer_option: Annotation[int, slash_int_option("Integer Option")]
357+
):
340358
await ctx.send(f"You input {integer_option}")
341359
```
342360

@@ -349,7 +367,7 @@ There are currently four different ways to define interactions, one does not nee
349367
await ctx.send(f"You input {integer_option}")
350368

351369
bot.add_interaction(
352-
command=SlashCommand(
370+
SlashCommand(
353371
name="my_command",
354372
description="My first command :)",
355373
options=[
@@ -359,23 +377,24 @@ There are currently four different ways to define interactions, one does not nee
359377
required=True,
360378
type=OptionType.INTEGER
361379
)
362-
]
380+
],
381+
callback=my_command_function
363382
)
364383
)
365384
```
366385

367-
## Restrict commands using permissions
386+
## Restrict Commands Using Permissions
368387

369-
It is possible to disable interactions (slash commands as well as context menus) for users that do not have a set of permissions.
388+
It is possible to disable application commands (which include slash commands) for users that do not have a set of permissions.
370389

371-
This functionality works for **permissions**, not to confuse with roles. If you want to restrict some command if the user does not have a certain role, this cannot be done on the bot side. However, it can be done on the Discord server side, in the Server Settings > Integrations page.
390+
This functionality works for **permissions**, not to confuse with roles. If you want to restrict some command if the user does not have a certain role, this cannot be done on the bot side. However, it can be done on the Discord server side (in the Server Settings > Integrations page) or through [Checks][checks] as discussed below.
372391

373392
!!!warning Administrators
374393
Remember that administrators of a Discord server have all permissions and therefore will always see the commands.
375394

376395
If you do not want admins to be able to overwrite your permissions, or the permissions are not flexible enough for you, you should use [checks][checks].
377396

378-
In this example, we will limit access to the command to members with the `MANAGE_EVENTS` and `MANAGE_THREADS` permissions.
397+
In this example, we will limit access to the command to members with the `MANAGE_EVENTS` *and* `MANAGE_THREADS` permissions.
379398
There are two ways to define permissions.
380399

381400
=== ":one: Decorators"
@@ -427,7 +446,7 @@ There are a few pre-made checks for you to use, and you can simply create your o
427446
Check that the author is the owner of the bot:
428447

429448
```python
430-
from interactions import SlashContext, check, is_owner, slash_command
449+
from interactions import check, is_owner
431450

432451
@slash_command(name="my_command")
433452
@check(is_owner())
@@ -439,7 +458,7 @@ There are a few pre-made checks for you to use, and you can simply create your o
439458
Check that the author's username starts with `a`:
440459

441460
```python
442-
from interactions import BaseContext, SlashContext, check, slash_command
461+
from interactions import check
443462

444463
async def my_check(ctx: BaseContext):
445464
return ctx.author.username.startswith("a")
@@ -451,33 +470,30 @@ There are a few pre-made checks for you to use, and you can simply create your o
451470
```
452471

453472
=== ":three: Reusing Checks"
454-
You can reuse checks in extensions by adding them to the extension check list
473+
While you can simply reuse checks by doing `@check(my_check)` for every command you wish to use the check with, you can also reuse them by making your own decorator wrapping the `@check()` decorator:
455474

456475
```python
457-
from interactions import Extension
476+
from interactions import check
458477

459-
class MyExtension(Extension):
460-
def __init__(self, bot) -> None:
461-
super().__init__(bot)
462-
self.add_ext_check(is_owner())
478+
def my_check():
479+
async def predicate(ctx: ipy.BaseContext):
480+
return ctx.author.username.startswith("a")
463481

464-
@slash_command(name="my_command")
465-
async def my_command_function(ctx: SlashContext):
466-
...
482+
return check(predicate)
467483

468-
@slash_command(name="my_command2")
469-
async def my_command_function2(ctx: SlashContext):
470-
...
484+
@slash_command(name="my_command")
485+
@my_check()
486+
async def command(ctx: SlashContext):
487+
await ctx.send("Your username starts with an 'a'!", ephemeral=True)
471488
```
472489

473-
The check will be checked for every command in the extension.
490+
When multiple checks are used, *all* checks must pass for the command to be executed.
474491

475-
476-
## Avoid redefining the same option everytime
492+
## Avoid Redefining Options
477493

478494
If you have multiple commands that all use the same option, it might be both annoying and bad programming to redefine it multiple times.
479495

480-
Luckily, you can simply make your own decorators that themselves call `@slash_option()`:
496+
Luckily, if you use the option decorator method, you can simply make your own decorators that themselves call `@slash_option()`:
481497
```python
482498
def my_own_int_option():
483499
"""Call with `@my_own_int_option()`"""
@@ -499,26 +515,63 @@ async def my_command_function(ctx: SlashContext, integer_option: int):
499515
await ctx.send(f"You input {integer_option}")
500516
```
501517

518+
If you use `SlashCommandOption` objects directly, you can also use the same principle:
519+
```python
520+
my_own_int_option = SlashCommandOption(
521+
name="integer_option",
522+
description="Integer Option",
523+
required=True,
524+
type=OptionType.INTEGER
525+
)
526+
527+
@slash_command(name="my_command", options=[my_own_int_option])
528+
async def my_command_function(ctx: SlashContext, integer_option: int):
529+
await ctx.send(f"You input {integer_option}")
530+
```
531+
502532
The same principle can be used to reuse autocomplete options.
503533

504-
## Simplified Error Handling
534+
## Error Handling
535+
536+
By default, if an error occurs in a command, interactions.py will send the error to its default error listener, which will either:
537+
538+
- Send an appropriate error message to the user, if the error is meant to be shown to the user (IE cooldown or check errors).
539+
- Log the error and, if `send_command_tracebacks` is enabled in your Client (which it is by default), send the error as a response to the command.
540+
541+
To override this for a single command, you can use the `@error` decorator every command has:
542+
543+
```python
544+
@slash_command(name="my_command", ...)
545+
async def my_command_function(ctx: SlashContext):
546+
raise ValueError("Something went wrong!")
547+
548+
@my_command_function.error
549+
async def on_command_error(error: Exception, ctx: SlashContext):
550+
await ctx.send(f"Something went wrong: {error}")
551+
```
552+
553+
???+ note
554+
If you wish for an error handler for a group of commands, you may wish to check out [Extensions](20 Extensions.md), which allows you both to group commands together and add error handlers to the group.
505555

506556
If you want error handling for all commands, you can override the default error listener and define your own.
507-
Any error from interactions will trigger `CommandError`. That includes context menus.
557+
Any error from any command will trigger `CommandError` - note that this includes errors from context menus and (if enabled) prefixed commands.
508558

509559
In this example, we are logging the error and responding to the interaction if not done so yet:
510560
```python
511561
import traceback
512562
from interactions.api.events import CommandError
513563

514-
@listen(CommandError, disable_default_listeners=True) # tell the dispatcher that this replaces the default listener
564+
@listen(CommandError, disable_default_listeners=True)
515565
async def on_command_error(event: CommandError):
516566
traceback.print_exception(event.error)
517567
if not event.ctx.responded:
518568
await event.ctx.send("Something went wrong.")
519569
```
520570

521-
There also is `CommandCompletion` which you can overwrite too. That fires on every interactions usage.
571+
!!! warning
572+
If you override the default error listener, you will need to handle all errors yourself. This *includes* errors typically shown to the user, such as cooldowns and check errors.
573+
574+
There also is `CommandCompletion` which you can listen into too. That fires on every interactions usage.
522575

523576
## Custom Parameter Type
524577

0 commit comments

Comments
 (0)