Slash Commands¶
Slash commands can significantly simplify the user’s experience with your bot. Once “/” is pressed on the keyboard, the list of slash commands appears. You can fill the options and read the hints at the same time, while types are validated instantly. This library allows to make such commands in several minutes, regardless of their complexity.
Getting started¶
You have probably noticed that slash commands have really interactive user interface, as if each slash command was built-in. This is because each slash command is registered in Discord before people can see it. This library handles registration for you, but you can still manage it.
By default, the registration is global. This means that your slash commands will be visible everywhere, including bot DMs, though you can disable them in DMs by setting the appropriate permissions. You can also change the registration to be local, so your slash commands will only be visible in several guilds.
This code sample shows how to set the registration to be local:
from disnake.ext import commands
bot = commands.Bot(
command_prefix='!',
test_guilds=[123456789],
# In the list above you can specify the IDs of your test guilds.
# Why is this kwarg called test_guilds? This is because you're not meant to use
# local registration in production, since you may exceed the rate limits.
)
For global registration, don’t specify this parameter.
In order to configure specific properties about command sync, there’s a configuration
class which may be passed to the Bot, CommandSyncFlags
.
Setting CommandSyncFlags.sync_commands_debug
to True
, will print debug messages related to the
app command registration to the console (or logger if enabled).
This is useful if you want to figure out some registration details:
from disnake.ext import commands
command_sync_flags = commands.CommandSyncFlags.default()
command_sync_flags.sync_commands_debug = True
bot = commands.Bot(
command_prefix='!',
test_guilds=[123456789], # Optional
command_sync_flags=command_sync_flags,
)
If you want to disable the automatic registration, set CommandSyncFlags.sync_commands
to False
, or use CommandSyncFlags.none()
from disnake.ext import commands
command_sync_flags = commands.CommandSyncFlags.none()
command_sync_flags.sync_commands = False
bot = commands.Bot(
command_prefix='!',
command_sync_flags=command_sync_flags,
)
Basic Slash Command¶
Make sure that you’ve read Getting started, it contains important information about command registration.
Here’s an example of a slash command:
@bot.slash_command(description="Responds with 'World'")
async def hello(inter):
await inter.response.send_message("World")
A slash command must always have at least one parameter, inter
, which is the ApplicationCommandInteraction
as the first one.
I can’t see my slash command, what do I do? Read Getting started.
Parameters¶
You may want to define a couple of options for your slash command. In disnake, the definition of options is based on annotations.
Here’s an example of a command with one integer option (without a description):
@bot.slash_command(description="Multiplies the number by 7")
async def multiply(inter, number: int):
await inter.response.send_message(number * 7)
The result should look like this:
You can of course set a default for your option by giving a default value:
@bot.slash_command(description="Multiplies the number by a multiplier")
async def multiply(inter, number: int, multiplier: int = 7):
await inter.response.send_message(number * multiplier)
You may have as many options as you want but the order matters, an optional option cannot be followed by a required one.
Option Types¶
You might already be familiar with discord.py’s converters, slash commands have a very similar equivalent in the form of option types. Discord itself supports only a few built-in types which are guaranteed to be enforced:
All the other types may be converted implicitly, similarly to Basic Converters
@bot.slash_command()
async def multiply(
interaction,
string: str,
integer: int,
number: float,
user: disnake.User,
emoji: disnake.Emoji,
message: disnake.Message
):
...
Note
* All channel subclasses and unions (e.g. Union[TextChannel, StageChannel]
) are also supported.
See ParamInfo.channel_types
for more fine-grained control.
- ** Some combinations of types are also allowed, including:
Union[User, Member]
(results inOptionType.user
)Union[Member, Role]
(results inOptionType.mentionable
)Union[User, Role]
(results inOptionType.mentionable
)Union[User, Member, Role]
(results inOptionType.mentionable
)
Note that a User
annotation can also result in a Member
being received.
*** Corresponds to any mentionable type, currently equivalent to Union[User, Member, Role]
.
Number Ranges¶
int
and float
parameters support minimum and maximum allowed
values using the lt
, le
, gt
, ge
parameters on Param
.
For instance, you could restrict an option to only accept positive integers:
@bot.slash_command()
async def command(
inter: disnake.ApplicationCommandInteraction,
amount: int = commands.Param(gt=0),
):
...
Instead of using Param()
, you can also use a Range
annotation.
The range bounds are both inclusive; using ...
as a bound indicates that this end of the range is unbounded.
The type of the option is specified by the first type argument, which can be either int
or float
.
@bot.slash_command()
async def ranges(
inter: disnake.ApplicationCommandInteraction,
a: commands.Range[int, 0, 10], # 0 - 10 int
b: commands.Range[float, 0, 10.0], # 0 - 10 float
c: commands.Range[int, 1, ...], # positive int
):
...
String Lengths¶
str
parameters support minimum and maximum allowed lengths
using the min_length
and max_length
parameters on Param()
.
For instance, you could restrict an option to only accept a single character:
@bot.slash_command()
async def charinfo(
inter: disnake.ApplicationCommandInteraction,
character: str = commands.Param(max_length=1),
):
...
Or restrict a tag command to limit tag names to 20 characters:
@bot.slash_command()
async def tags(
inter: disnake.ApplicationCommandInteraction,
tag: str = commands.Param(max_length=20)
):
...
Instead of using Param()
, you can also use a String
annotation.
The length bounds are both inclusive; using ...
as a bound indicates that this end of the string length is unbounded.
The first type argument should always be str
.
@bot.slash_command()
async def strings(
inter: disnake.ApplicationCommandInteraction,
a: commands.String[str, 0, 10], # a str no longer than 10 characters.
b: commands.String[str, 10, 100], # a str that's at least 10 characters but not longer than 100.
c: commands.String[str, 50, ...], # a str that's at least 50 characters.
):
...
Note
There is a max length of 6000 characters, which is enforced by Discord.
Docstrings¶
If you have a feeling that option descriptions make the parameters of your function look overloaded, use docstrings. This feature allows to describe your command and options in triple quotes inside the function, following the RST markdown.
In order to describe the parameters, list them under the Parameters
header, underlined with dashes:
@bot.slash_command()
async def give_cookies(
inter: disnake.ApplicationCommandInteraction,
user: disnake.User,
amount: int = 1
):
"""
Give several cookies to a user
Parameters
----------
user: The user to give cookies to
amount: The amount of cookies to give
"""
...
Note
In the above example we’re using a simplified RST markdown.
If you prefer the real RST format, you can still use it:
@bot.slash_command()
async def give_cookies(
inter: disnake.ApplicationCommandInteraction,
user: disnake.User,
amount: int = 1
):
"""
Give several cookies to a user
Parameters
----------
user: :class:`disnake.User`
The user to give cookies to
amount: :class:`int`
The amount of cookies to give
"""
...
Parameter Descriptors¶
Python has no truly clean way to provide metadata for parameters, so disnake uses the same approach as fastapi using
parameter defaults. At the current time there’s only Param
.
With this you may set the name, description, custom converters, Autocompleters, and more.
@bot.slash_command()
async def math(
interaction: disnake.ApplicationCommandInteraction,
a: int = commands.Param(le=10),
b: int = commands.Param(le=10),
op: str = commands.Param(name="operator", choices=["+", "-", "/", "*"])
):
"""
Perform an operation on two numbers as long as both of them are less than or equal to 10
"""
...
@bot.slash_command()
async def multiply(
interaction: disnake.ApplicationCommandInteraction,
clean: str = commands.Param(converter=lambda inter, arg: arg.replace("@", "\\@")
):
...
Note
The converter parameter only ever takes in a function, not a Converter class. Converter classes are completely unusable in disnake due to their inconsistent typing.
Choices¶
Some options can have a list of choices, so the user doesn’t have to manually fill the value. The most elegant way of defining the choices is by using enums. These enums must inherit from the type of their value if you want them to work with linters.
For example:
from enum import Enum
class Animal(str, Enum):
Dog = 'dog'
Cat = 'cat'
Penguin = 'peng'
@bot.slash_command()
async def blep(inter: disnake.ApplicationCommandInteraction, animal: Animal):
await inter.response.send_message(animal)
Note
The animal
arg will receive one of the enum values.
You can define an enum in one line:
Animal = commands.option_enum({"Dog": "dog", "Cat": "cat", "Penguin": "penguin"})
@bot.slash_command()
async def blep(inter: disnake.ApplicationCommandInteraction, animal: Animal):
await inter.response.send_message(animal)
Or even forget about values and define the enum from list:
Animal = commands.option_enum(["Dog", "Cat", "Penguin"])
@bot.slash_command()
async def blep(inter: disnake.ApplicationCommandInteraction, animal: Animal):
await inter.response.send_message(animal)
Or you can simply list the choices in commands.Param
:
@bot.slash_command()
async def blep(
inter: disnake.ApplicationCommandInteraction,
animal: str = commands.Param(choices={"Dog": "dog", "Cat": "cat", "Penguin": "penguin"})
):
await inter.response.send_message(animal)
# Or define the choices in a list
@bot.slash_command()
async def blep(
inter: disnake.ApplicationCommandInteraction,
animal: str = commands.Param(choices=["Dog", "Cat", "Penguin"])
):
await inter.response.send_message(animal)
Autocompleters¶
Slash commands support interactive autocompletion. You can define a function that will constantly suggest autocomplete options while the user is typing. So basically autocompletion is roughly equivalent to dynamic choices.
In order to build an option with autocompletion, define a function that takes 2 parameters - ApplicationCommandInteraction
instance,
representing an autocomp interaction with your command, and a str
instance, representing the current user input. The function
should return a list of strings or a mapping of choice names to values.
For example:
LANGUAGES = ["python", "javascript", "typescript", "java", "rust", "lisp", "elixir"]
async def autocomp_langs(inter: disnake.ApplicationCommandInteraction, user_input: str):
return [lang for lang in LANGUAGES if user_input.lower() in lang]
@bot.slash_command()
async def example(
inter: disnake.ApplicationCommandInteraction,
language: str = commands.Param(autocomplete=autocomp_langs)
):
...
In case you need don’t want to use Param
or need to use self
in a cog you may
create autocomplete options with the autocomplete
decorator:
@bot.slash_command()
async def languages(inter: disnake.ApplicationCommandInteraction, language: str):
pass
@languages.autocomplete("language")
async def language_autocomp(inter: disnake.ApplicationCommandInteraction, string: str):
string = string.lower()
return [lang for lang in LANGUAGES if string in lang.lower()]
...
Subcommands And Groups¶
Groups of commands work differently in terms of slash commands. Instead of defining a group, you should still define a slash command and then nest some sub-commands or sub-command-groups there via special decorators.
For example, here’s how you make a /show user
command:
@bot.slash_command()
async def show(inter):
# Here you can paste some code, it will run for every invoked sub-command.
pass
@show.sub_command()
async def user(inter, user: disnake.User):
"""
Description of the subcommand
Parameters
----------
user: Enter the user to inspect
"""
...
Note
After being registered this command will be visible as /show user
in the list, not allowing you to invoke /show
without
any sub-commands. This is an API limitation.
You can implement double nesting and build commands like /parent group subcmd
:
@bot.slash_command()
async def parent(inter):
pass
@parent.sub_command_group()
async def group(inter):
pass
@group.sub_command()
async def subcmd(inter):
# Some stuff
pass
Note
This is the deepest nesting available.
Injections¶
We have them, look at this example for more information ✨
Localizations¶
The names and descriptions of commands and options, as well as the names of choices (for use with fixed choices or autocompletion), support localization for a fixed set of locales.
For currently supported locales, see Locale
.
Note
You can supply your own custom localization provider by implementing LocalizationProtocol
and using the client’s/bot’s localization_provider
parameter to Bot
.
The .json
handling mentioned in this section, as well as the Strict Localization section below only
apply to the default implementation, LocalizationStore
.
The preferred way of adding localizations is to use <locale>.json
files,
containing mappings from user-defined keys to localized/translated strings,
and referencing these keys in the commands’ docstrings.
As an example, consider this command:
@bot.slash_command()
async def add_5(inter: disnake.ApplicationCommandInteraction, num: int):
"""
Adds 5 to a number. {{ADD_NUM}}
Parameters
----------
num: Some number {{ COOL_NUMBER }}
"""
await inter.response.send_message(f"{num} + 5 = {num + 5}")
The keys {{XYZ}}
are automatically extracted from the docstrings,
and used for looking up names XYZ_NAME
and descriptions XYZ_DESCRIPTION
.
Note that whitespace is ignored, and the positioning inside the line doesn’t matter.
For instance, to add German localizations, create a locale/de.json
file;
the directory name/path can be changed arbitrarily, locale
is just the one used here:
{
"ADD_NUM_NAME": "addiere_5",
"ADD_NUM_DESCRIPTION": "Addiere 5 zu einer anderen Zahl.",
"COOL_NUMBER_NAME": "zahl",
"COOL_NUMBER_DESCRIPTION": "Eine Zahl"
}
To load a directory or file containing localizations, use bot.i18n.load(path)
:
...
bot.i18n.load("locale/") # loads all files in the "locale/" directory
bot.run(...)
Note
If Bot.reload
is True
, all currently loaded localization files
are reloaded when an extension gets automatically reloaded.
Strict Localization¶
By default, missing keys that couldn’t be found are silently ignored.
To instead raise an exception when a key is missing, pass the strict_localization=True
parameter to the client/bot constructor
(see the docs for the strict_localization
parameter to Bot
).
Customization¶
If you want more customization or low-level control over localizations, you can specify arbitrary keys for the commands/options directly.
Localized
takes the non-localized string (optional depending on target type) to keep the ability of e.g.
overwriting name
in the command decorator, and either a key
or data
parameter.
This would create the same command as the code above, though you’re free to change the keys like ADD_NUM_DESCRIPTION
however you want:
@bot.slash_command(name=Localized("add_5", key="ADD_NUM_NAME"), description=Localized(key="ADD_NUM_DESCRIPTION"))
async def _add_5_slash(
inter: disnake.ApplicationCommandInteraction,
num: int = commands.Param(
name=Localized(key="COOL_NUMBER_NAME"),
description=Localized(key="COOL_NUMBER_DESCRIPTION")
),
):
"""
Adds 5 to a number.
Parameters
----------
num: Some number
"""
await inter.response.send_message(f"{num} + 5 = {num + 5}")
While not recommended, it is also possible to avoid using .json
files at all, and instead specify localizations directly in the code:
@bot.slash_command(
name=Localized(data={Locale.de: "addiere_5"}),
description=Localized(data={Locale.de: "Addiere 5 zu einer anderen Zahl."}),
)
async def add_5(
inter: disnake.ApplicationCommandInteraction,
num: int = commands.Param(
name=Localized(data={Locale.de: "zahl"}),
description=Localized(data={Locale.de: "Eine Zahl"}),
),
):
...
Choices/Autocomplete¶
Option choices and autocomplete items can also be localized, through the use of OptionChoice
:
@bot.slash_command()
async def example(
inter: disnake.ApplicationCommandInteraction,
animal: str = commands.Param(choices=[
# alternatively:
# OptionChoice(Localized("Cat", key="OPTION_CAT"), "Cat")
Localized("Cat", key="OPTION_CAT"),
Localized("Dolphin", key="OPTION_DOLPHIN"),
]),
language: str = commands.Param(autocomplete=autocomp_langs),
):
...
@example.autocomplete("language")
async def language_autocomp(inter: disnake.ApplicationCommandInteraction, user_input: str):
languages = ("english", "german", "spanish", "japanese")
return [
# alternatively:
# `OptionChoice(Localized(lang, key=f"AUTOCOMP_{lang.upper()}"), lang)`
Localized(lang, key=f"AUTOCOMP_{lang.upper()}")
for lang in languages
if user_input.lower() in lang
]
Yet again, with a file like locale/de.json
containing localizations like this:
{
"OPTION_CAT": "Katze",
"OPTION_DOLPHIN": "Delfin",
"AUTOCOMP_ENGLISH": "Englisch",
"AUTOCOMP_GERMAN": "Deutsch",
"AUTOCOMP_SPANISH": "Spanisch",
"AUTOCOMP_JAPANESE": "Japanisch"
}
Permissions¶
Default Member Permissions¶
These commands will not be enabled/visible for members who do not have all the required guild permissions.
In this example both the manage_guild
and the moderate_members
permissions would be required:
@bot.slash_command()
@commands.default_member_permissions(manage_guild=True, moderate_members=True)
async def command(inter: disnake.ApplicationCommandInteraction):
...
Or use the default_member_permissions
parameter in the application command decorator:
@bot.slash_command(default_member_permissions=disnake.Permissions(manage_guild=True, moderate_members=True))
async def command(inter: disnake.ApplicationCommandInteraction):
...
This can be overridden by moderators on a per-guild basis; default_member_permissions
may be
ignored entirely once a permission override — application-wide or for this command in particular — is configured
in the guild settings, and will be restored if the permissions are re-synced in the settings.
Note that default_member_permissions
and dm_permission
cannot be configured for a slash subcommand or
subcommand group, only in top-level slash commands and user/message commands.
DM Permissions¶
Using this, you can specify if you want a certain slash command to work in DMs or not:
@bot.slash_command(dm_permission=False)
async def config(inter: disnake.ApplicationCommandInteraction):
...
This will make the config
slash command invisible in DMs, while it will remain visible in guilds.