Annotated slash commands¶
Annotated commands are quick to use but are more limited and may look complex with multiple subcommands,
for slash commands, the command method must be annotated with @JDASlashCommand.
@Command
class SlashPing {
// Default scope is global, guild-only (GUILD_NO_DM)
@JDASlashCommand(name = "ping", description = "Pong!")
suspend fun onSlashPing(event: GuildSlashEvent) {
event.deferReply(true).queue()
val ping = event.jda.getRestPing().await()
event.hook.editOriginal("Pong! $ping ms").await()
}
}
@Command
public class SlashPing {
// Default scope is global, guild-only (GUILD_NO_DM)
@JDASlashCommand(name = "ping", description = "Pong!")
public void onSlashPing(GuildSlashEvent event) {
event.deferReply(true).queue();
event.getJDA().getRestPing().queue(ping -> {
event.getHook().editOriginal("Pong! " + ping + " ms").queue();
});
}
}
Command configuration¶
You can configure properties which applies to different layers of a command,
we will use /ban temp users in our example:
- Top level (
/ban):@TopLevelSlashCommandData - Subcommand group (
/ban temp):@SlashCommandGroupData - Full command (
/ban temp users):@JDASlashCommand
Properties shared by nested commands
Some properties exist on all layers, you must to use the appropriate annotation for each layer. With our previous example, you can set the description for all levels with:
JDASlashCommand.description: Sets the description of/ban temp usersSlashCommandGroupData.description: Sets the description of/ban tempTopLevelSlashCommandData.description: Sets the description of/ban
Subcommands¶
To make a subcommand, set the name and subcommand on the annotation.
You will also need to add a @TopLevelSlashCommandData,
it must only be used once per top-level command, this allows you to set top-level attributes.
Example
@Command
class SlashTag {
// Data for /tag create
@JDASlashCommand(name = "tag", subcommand = "create", description = "Creates a tag")
// Data for /tag
@TopLevelSlashCommandData(description = "Manage tags")
fun onSlashTagCreate(event: GuildSlashEvent) {
// ...
}
// Data for /tag delete
@JDASlashCommand(name = "tag", subcommand = "delete", description = "Deletes a tag")
fun onSlashTagDelete(event: GuildSlashEvent) {
// ...
}
}
@Command
public class SlashTag {
// Data for /tag create
@JDASlashCommand(name = "tag", subcommand = "create", description = "Creates a tag")
// Data for /tag
@TopLevelSlashCommandData(description = "Manage tags")
public void onSlashTagCreate(GuildSlashEvent event) {
// ...
}
// Data for /tag delete
@JDASlashCommand(name = "tag", subcommand = "delete", description = "Deletes a tag")
public void onSlashTagDelete(GuildSlashEvent event) {
// ...
}
}
Note
You cannot have both subcommands and top-level commands (i.e., an annotation with only name set).
However, you can have both subcommand groups and subcommands groups containing subcommands.
Adding options¶
Options can be added with a parameter annotated with @SlashOption.
All supported types are documented under SlashParameterResolver, and other types can be added.
Example
@Command
class SlashSay {
@JDASlashCommand(name = "say", description = "Says something")
suspend fun onSlashSay(event: GuildSlashEvent, @SlashOption(description = "What to say") content: String) {
event.reply(content).await()
}
}
@Command
public class SlashSay {
@JDASlashCommand(name = "say", description = "Says something")
public void onSlashSay(GuildSlashEvent event, @SlashOption(description = "What to say") String content) {
event.reply(content).queue();
}
}
Inferred option names
Display names of options can be set on the annotation, but can also be deduced from the parameter name, this is natively supported in Kotlin, but for Java, you will need to enable parameter names on the Java compiler.
Using choices¶
There are two ways of setting choices for an option.
With a choice provider¶
This one is useful if the choices are local to the command's class and may differ from other similar options.
You must implement SlashOptionChoiceProvider in order to return a list of choices,
remember to check against the command path as well as the option's display name.
Making a choice provider
@Command
class SlashConvert : SlashOptionChoiceProvider {
override fun getOptionChoices(guild: Guild?, commandPath: CommandPath, optionName: String): List<Choice> {
if (commandPath.name == "convert") {
if (optionName == "from" || optionName == "to") {
return listOf(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS)
// The Resolvers class helps us by providing resolvers for any enum type.
// We're just using the helper method to change an enum value to a more natural name.
.map { Choice(Resolvers.toHumanName(it), it.name) }
}
}
return emptyList()
}
@JDASlashCommand(name = "convert", description = "Convert time to another unit")
suspend fun onSlashConvert(
event: GuildSlashEvent,
@SlashOption(description = "The time to convert") time: Long,
@SlashOption(description = "The unit to convert from") from: TimeUnit,
@SlashOption(description = "The unit to convert to") to: TimeUnit
) {
event.reply("${to.convert(time, from)} ${to.name.lowercase()}").await()
}
}
@Command
@NullMarked // Everything is non-null unless @Nullable
public class SlashConvert implements SlashOptionChoiceProvider {
@Override
public List<Choice> getOptionChoices(@Nullable Guild guild, CommandPath commandPath, String optionName) {
if (commandPath.getName().equals("convert")) {
if (optionName.equals("from") || optionName.equals("to")) {
return Stream.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS)
// The Resolvers class helps us by providing resolvers for any enum type.
// We're just using the helper method to change an enum value to a more natural name.
.map(u -> new Choice(Resolvers.toHumanName(u), u.name()))
.toList();
}
}
return List.of();
}
@JDASlashCommand(name = "convert", description = "Convert time to another unit")
public void onSlashConvert(
GuildSlashEvent event,
@SlashOption(description = "The time to convert") long time,
@SlashOption(description = "The unit to convert from") TimeUnit from,
@SlashOption(description = "The unit to convert to") TimeUnit to
) {
event.reply(to.convert(time, from) + " " + to.name().toLowerCase()).queue();
}
}
With choices predefined by the resolver¶
This one is useful if the choices are the same for all parameters of the same type.
After implementing SlashParameterResolver.getPredefinedChoices, you can enable them on your option with SlashOption.usePredefinedChoices.
Using the predefined choices
@Command
class SlashConvertSimplified {
@JDASlashCommand(name = "convert", description = "Convert time to another unit")
suspend fun onSlashConvertSimplified(
event: GuildSlashEvent,
@SlashOption(description = "The time to convert") time: Long,
@SlashOption(description = "The unit to convert from", usePredefinedChoices = true) from: TimeUnit,
@SlashOption(description = "The unit to convert to", usePredefinedChoices = true) to: TimeUnit
) {
event.reply("${to.convert(time, from)} ${to.name.lowercase()}").await()
}
}
@Command
public class SlashConvertSimplified {
@JDASlashCommand(name = "convert", description = "Convert time to another unit")
public void onSlashTimeInSimplified(
GuildSlashEvent event,
@SlashOption(description = "The time to convert") long time,
@SlashOption(description = "The unit to convert from", usePredefinedChoices = true) TimeUnit from,
@SlashOption(description = "The unit to convert to", usePredefinedChoices = true) TimeUnit to
) {
event.reply(to.convert(time, from) + " " + to.toString().toLowerCase()).queue();
}
}
Using autocomplete¶
Learn how to create an autocomplete handler here
Enabling autocompletion for an option is done by referencing an existing handler,
in the autocomplete property of your @SlashOption.
Example
Using the autocomplete handler we made "Creating autocomplete handlers":
@Command
class SlashWord {
@JDASlashCommand(name = "word", description = "Autocompletes a word")
suspend fun onSlashWord(
event: GuildSlashEvent,
@SlashOption(description = "The word", autocomplete = SlashWordAutocomplete.WORD_AUTOCOMPLETE_NAME) word: String,
) {
event.reply_("Your word was $word", ephemeral = true).await()
}
}
@Command
public class SlashWord {
@JDASlashCommand(name = "word", description = "Autocompletes a word")
public void onSlashWord(GuildSlashEvent event,
@SlashOption(description = "The word", autocomplete = SlashWordAutocomplete.WORD_AUTOCOMPLETE_NAME) String word) {
event.reply("Your word was " + word).setEphemeral(true).queue();
}
}
Generated values¶
Generated values are parameters that get their values from a lambda everytime a command is run.
You must give one by implementing ApplicationGeneratedValueSupplierProvider.
As always, make sure to check against the command path as well as the option's display name.
Example
@Command
class SlashCreateTime : ApplicationGeneratedValueSupplierProvider {
override fun getGeneratedValueSupplier(
guild: Guild?,
commandId: String?,
commandPath: CommandPath,
optionName: String,
parameterType: ParameterType
): ApplicationGeneratedValueSupplier {
if (commandPath.name == "create_time") {
if (optionName == "timestamp") {
// Create a snapshot of the instant the command was created
val now = Instant.now()
// Give back the instant snapshot, as this will be called every time the command runs
return ApplicationGeneratedValueSupplier { now }
}
}
error("Unknown generated option: $optionName")
}
@JDASlashCommand(name = "create_time", description = "Shows the creation time of this command")
suspend fun onSlashCreateTime(
event: GuildSlashEvent,
@GeneratedOption timestamp: Instant
) {
event.reply("I was created on ${TimeFormat.DATE_TIME_SHORT.format(timestamp)}").await()
}
}
@Command
@NullMarked // Everything is non-null unless @Nullable
public class SlashCreateTime implements ApplicationGeneratedValueSupplierProvider {
@Override
public ApplicationGeneratedValueSupplier getGeneratedValueSupplier(
@Nullable Guild guild,
@Nullable String commandId,
CommandPath commandPath,
String optionName,
ParameterType parameterType
) {
if (commandPath.getName().equals("create_time")) {
if (optionName.equals("timestamp")) {
// Create a snapshot of the instant the command was created
final Instant now = Instant.now();
// Give back the instant snapshot, as this will be called every time the command runs
return event -> now;
}
}
throw new IllegalArgumentException("Unknown generated option: " + optionName);
}
@JDASlashCommand(name = "create_time", description = "Shows the creation time of this command")
public void onSlashTimeIn(
GuildSlashEvent event,
@GeneratedOption Instant timestamp
) {
event.reply("I was created on " + TimeFormat.DATE_TIME_SHORT.format(timestamp)).queue();
}
}
Rate limiting¶
This lets you reject application commands if the user tries to use them too often, see "Using rate limiters in commands".