Creating slash commands¶
Slash commands are the new way of defining commands, even though there are limitations with them, we do have some advantages such as being easier to fill in, choices and auto-completion.
If you wish to handle application commands yourself, you can disable them by disabling BApplicationConfigBuilder.enable
.
Defining the command method¶
Tip
Make sure you read the common command requirements first!
In addition to the common requirements, the first parameter must be GlobalSlashEvent
for global commands
or GuildSlashEvent
for guild commands, or guild-only global commands (default).
Annotated commands¶
Annotated command methods must be annotated with @JDASlashCommand
,
where you can set the scope, name, description, etc...,
while the declaring class must extend ApplicationCommand
.
Why do I need to extend ApplicationCommand
?
As a limitation of annotated commands, you are required to extend this class as it allows the framework to ask your commands for stuff, like what guilds a command should be pushed to, getting a value generator for one of their options, and also getting choices.
Example
@Command
class SlashPingKotlin : ApplicationCommand() {
// 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 extends ApplicationCommand {
// 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();
});
}
}
Top level configuration¶
You can configure properties which only applies to top-level commands, using @TopLevelSlashCommandData
,
this includes elements such as the scope
(guild, global or global without DMs), description
and whether the command is locked to admins by default.
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 : ApplicationCommand() {
// 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 extends ApplicationCommand {
// 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 ParameterResolver
, and other types can be added.
Example
@Command
class SlashSayKotlin : ApplicationCommand() {
@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 extends ApplicationCommand {
@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¶
You must override getOptionChoices
in order to return a list of choices,
be careful to check against the command path as well as the option's display name.
Example
@Command
class SlashConvertKotlin : ApplicationCommand() {
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 super.getOptionChoices(guild, commandPath, optionName)
}
@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
public class SlashConvert extends ApplicationCommand {
@NotNull
@Override
public List<Choice> getOptionChoices(@Nullable Guild guild, @NotNull CommandPath commandPath, @NotNull 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 super.getOptionChoices(guild, commandPath, optionName);
}
@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();
}
}
As you can see, despite the short choice list, the method is quite lengthy and causes duplications with multiple commands. This issue is solved with predefined choices.
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 : ApplicationCommand() {
@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 extends ApplicationCommand {
@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 overriding ApplicationCommand.getGeneratedValueSupplier
,
similarly to adding choices.
As always, make sure to check against the command path as well as the option's display name.
Example
@Command
class SlashCreateTimeKotlin : ApplicationCommand() {
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 }
}
}
return super.getGeneratedValueSupplier(guild, commandId, commandPath, optionName, parameterType)
}
@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
public class SlashCreateTime extends ApplicationCommand {
@NotNull
@Override
public ApplicationGeneratedValueSupplier getGeneratedValueSupplier(
@Nullable Guild guild,
@Nullable String commandId,
@NotNull CommandPath commandPath,
@NotNull String optionName,
@NotNull 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;
}
}
return super.getGeneratedValueSupplier(guild, commandId, commandPath, optionName, parameterType);
}
@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.
Using an (anonymous) rate limiter¶
Use @RateLimit
or @Cooldown
to define one on an application command.
@Command
class SlashRateLimit : ApplicationCommand() {
// A rate limit for this slash command only
@RateLimit(
// Apply to each user, regardless of channel/guild
scope = RateLimitScope.USER,
// Delete the message telling the user about the remaining rate limit after it expires
deleteOnRefill = true,
// At least one of those needs to be empty for the command to be rejected
bandwidths = [
// 5 uses, 5 tokens gets added gradually over an hour (so, 1 token every 12 minutes)
Bandwidth(
capacity = 5, // 5 uses
Refill(
type = RefillType.GREEDY, // Gradually
tokens = 5, // gives 5 tokens
period = 1, // every 1
periodUnit = ChronoUnit.HOURS // hour
)
),
// 2 uses, 2 tokens gets added at once after 2 minutes
// This is to prevent the user from spamming the command
Bandwidth(
capacity = 2, // 2 uses
Refill(
type = RefillType.INTERVAL, // At once,
tokens = 2, // give 2 tokens
period = 2, // every 2
periodUnit = ChronoUnit.MINUTES // minutes
)
),
]
)
@JDASlashCommand(name = "rate_limit")
suspend fun onSlashRateLimit(event: GuildSlashEvent) {
event.reply("Hello world!").await()
}
}
@Command
public class SlashRateLimit extends ApplicationCommand {
// A rate limit for this slash command only
@RateLimit(
// Apply to each user, regardless of channel/guild
scope = RateLimitScope.USER,
// Delete the message telling the user about the remaining rate limit after it expires
deleteOnRefill = true,
// At least one of those needs to be empty for the command to be rejected
bandwidths = {
// 5 uses, 5 tokens gets added gradually over an hour (so, 1 token every 12 minutes)
@Bandwidth(
capacity = 5, // 5 uses
refill = @Refill(
type = RefillType.GREEDY, // Gradually
tokens = 5, // gives 5 tokens
period = 1, // every 1
periodUnit = ChronoUnit.HOURS // hour
)
),
// 2 uses, 2 tokens gets added at once after 2 minutes
// This is to prevent the user from spamming the command
@Bandwidth(
capacity = 2, // 2 uses
refill = @Refill(
type = RefillType.INTERVAL, // At once,
tokens = 2, // give 2 tokens
period = 2, // every 2
periodUnit = ChronoUnit.MINUTES // minutes
)
),
}
)
@JDASlashCommand(name = "rate_limit")
public void onSlashRateLimit(GuildSlashEvent event) {
event.reply("Hello world!").queue();
}
}
Using an existing rate limiter¶
Nothing as simple as using @RateLimitReference
with the group
of a rate limiter defined in a RateLimitProvider
.
Learn how to create a rate limiter with "Defining a rate limit"
@BService
class WikiRateLimitProvider : RateLimitProvider {
override fun declareRateLimit(manager: RateLimitManager) {
// Lets the user use the command 5 times in an hour, but also 2 times in 2 minutes to prevent spam
val bucketConfiguration = Buckets.spikeProtected(
capacity = 5, // 5 uses
duration = 1.hours, // Gives 5 tokens gradually, during an hour (1 token every 12 minutes)
spikeCapacity = 2, // 2 uses
spikeDuration = 2.minutes // Give 2 tokens every 2 minutes
)
val rateLimiter = RateLimiter.createDefault(
// Apply to each user, regardless of channel/guild
RateLimitScope.USER,
// Delete the message telling the user about the remaining rate limit after it expires
deleteOnRefill = true,
// Give our constant bucket configuration
configurationSupplier = bucketConfiguration.toSupplier()
)
// Register, any command can use it
manager.rateLimit(RATE_LIMIT_GROUP, rateLimiter)
}
companion object {
// The name of the rate limit, so you can reference it in your commands/components
const val RATE_LIMIT_GROUP = "Wiki"
}
}
@Command
class SlashRateLimitExisting : ApplicationCommand() {
// A rate limit for this slash command only
@RateLimitReference(WikiRateLimitProvider.RATE_LIMIT_GROUP)
@JDASlashCommand(name = "rate_limit_existing")
suspend fun onSlashRateLimit(event: GuildSlashEvent) {
event.reply("Hello world!").await()
}
}
@BService
public class WikiRateLimitProvider implements RateLimitProvider {
// The name of the rate limit, so you can reference it in your commands/components
public static final String RATE_LIMIT_GROUP = "Wiki";
@Override
public void declareRateLimit(@NotNull RateLimitManager rateLimitManager) {
// Lets the user use the command 5 times in an hour, but also 2 times in 2 minutes to prevent spam
final var bucketConfiguration = Buckets.createSpikeProtected(
5, // 5 uses
Duration.ofHours(1), // Gives 5 tokens gradually, during an hour (1 token every 12 minutes)
2, // 2 uses
Duration.ofMinutes(2) // Give 2 tokens every 2 minutes
);
final var rateLimiter = RateLimiter.createDefault(
// Apply to each user, regardless of channel/guild
RateLimitScope.USER,
// Give our constant bucket configuration
BucketConfigurationSupplier.constant(bucketConfiguration),
// Delete the message telling the user about the remaining rate limit after it expires
true
);
// Register
rateLimitManager.rateLimit(RATE_LIMIT_GROUP, rateLimiter);
}
}
@Command
public class SlashRateLimitExisting extends ApplicationCommand {
// A rate limit for this slash command only
@RateLimitReference(WikiRateLimitProvider.RATE_LIMIT_GROUP)
@JDASlashCommand(name = "rate_limit_existing")
public void onSlashRateLimit(GuildSlashEvent event) {
event.reply("Hello world!").queue();
}
}
Cooldown¶
A cooldown is a rate limit, but with fewer parameters, it can be used as @Cooldown(5, ChronoUnit.SECONDS /* also scope and deleteOnRefill */)
.
Filtering commands¶
You can use @DeclarationFilter
if you wish to declare a guild command conditionally.
Note
There is no equivalent for DSL commands as you can check and return early with your own code.
Creating the filter¶
Create a service implementing CommandDeclarationFilter
,
and make the method return true
if the command can be declared.
All filters must return true
for the command to be declared.
Example
@BService
class BotOwnerIsGuildOwnerDeclarationFilter(
private val botOwners: BotOwners, // Provided by the framework
) : CommandDeclarationFilter {
override fun filter(guild: Guild, path: CommandPath, commandId: String?): Boolean {
// Only allow this command to be in guilds owned by the bot owner
return guild.ownerIdLong in botOwners.ownerIds
}
}
@BService
public class BotOwnerIsGuildOwnerDeclarationFilter implements CommandDeclarationFilter {
private final BotOwners botOwners; // Provided by the framework
public BotOwnerIsGuildOwnerDeclarationFilter(BotOwners botOwners) {
this.botOwners = botOwners;
}
@Override
public boolean filter(@NotNull Guild guild, @NotNull CommandPath commandPath, @Nullable String s) {
// Only allow this command to be in guilds owned by the bot owner
return botOwners.getOwnerIds().contains(guild.getOwnerIdLong());
}
}
Using the filter¶
Add a @DeclarationFilter
on your command and reference your filter inside it.
Example
@Command
class SlashBotOwnerIsGuildOwner : ApplicationCommand() {
// All filters must return 'true' for the command to be declared
@DeclarationFilter(BotOwnerIsGuildOwnerDeclarationFilter::class)
// The command needs to be registered on guilds for it to be filtered
@TopLevelSlashCommandData(scope = CommandScope.GUILD)
@JDASlashCommand(name = "bot_owner_is_guild_owner")
suspend fun onSlashBotOwnerIsGuildOwner(event: GuildSlashEvent) {
event.reply_("You are the owner of this bot and guild!", ephemeral = true).await()
}
}
@Command
public class SlashBotOwnerIsGuildOwner extends ApplicationCommand {
// All filters must return 'true' for the command to be declared
@DeclarationFilter(BotOwnerIsGuildOwnerDeclarationFilter.class)
// The command needs to be registered on guilds for it to be filtered
@TopLevelSlashCommandData(scope = CommandScope.GUILD)
@JDASlashCommand(name = "bot_owner_is_guild_owner")
public void onSlashBotOwnerIsGuildOwner(GuildSlashEvent event) {
event.reply("You are the owner of this bot and guild!")
.setEphemeral(true)
.queue();
}
}
DSL commands (Kotlin)¶
Commands can be DSL-declared by either implementing:
GlobalApplicationCommandProvider
(for global / guild-only global application commands), or,GuildApplicationCommandProvider
(for guild-specific application commands)
You can then use the slashCommand
method on the manager
, give it the command name, the command method,
and then configure your command.
Tip
You are allowed to not add any command at all, for example,
if the guild
in GuildApplicationCommandManager
isn't a guild you want the command to appear in.
Example
@Command
class SlashPingKotlinDsl : GlobalApplicationCommandProvider {
suspend fun onSlashPing(event: GuildSlashEvent) {
event.deferReply(true).queue()
val ping = event.jda.getRestPing().await()
event.hook.editOriginal("Pong! $ping ms").await()
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
// Default scope is global, guild-only (GUILD_NO_DM)
manager.slashCommand("ping", function = ::onSlashPing) {
description = "Pong!"
}
}
}
Subcommands¶
As top-level commands cannot be made alongside subcommands, the top-level function
must be null
.
You can then add a subcommand by using subcommand
, where each subcommand is its own function.
Example
@Command
class SlashTagDsl : GlobalApplicationCommandProvider {
fun onSlashTagCreate(event: GuildSlashEvent) {
// ...
}
fun onSlashTagDelete(event: GuildSlashEvent) {
// ...
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
// Pass a null function as this is not a top-level command
manager.slashCommand("tag", function = null) {
description = "Manage tags"
subcommand("create", ::onSlashTagCreate) {
description = "Creates a tag"
}
subcommand("delete", ::onSlashTagDelete) {
description = "Deletes a tag"
}
}
}
}
Info
You can still create both subcommands, and subcommand groups containg subcommands.
Adding options¶
Options can be added with a parameter and declaring it using option
in your command builder,
where the declaredName
is the name of your parameter, the block will let you change the description, choices, etc.
All supported types are documented under ParameterResolver
, and other types can be added.
Example
@Command
class SlashSayKotlinDsl : GlobalApplicationCommandProvider {
suspend fun onSlashSay(event: GuildSlashEvent, content: String) {
event.reply(content).await()
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
manager.slashCommand("say", function = ::onSlashSay) {
description = "Says something"
option("content") {
description = "What to say"
}
}
}
}
Tip
You can override the option name by setting optionName
in the option declaration:
option("content", optionName = "sentence") {
...
}
Using choices¶
Adding choices is very straight forward, you only have to give a list of choices to the choice
property.
Example
@Command
class SlashConvertKotlinDsl : GlobalApplicationCommandProvider {
suspend fun onSlashConvert(event: GuildSlashEvent, time: Long, from: TimeUnit, to: TimeUnit) {
event.reply("${to.convert(time, from)} ${to.name.lowercase()}").await()
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
manager.slashCommand("convert", function = ::onSlashConvert) {
description = "Convert time to another unit"
option("time") {
description = "The time to convert"
}
option("from") {
description = "The unit to convert from"
choices = 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) }
}
option("to") {
description = "The unit to convert to"
choices = 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) }
}
}
}
}
As you can see, despite the short choice list, this causes duplications with multiple commands. This issue is solved with predefined choices.
Using autocomplete¶
Learn how to create an autocomplete handler here
Enabling autocompletion for an option is done by referencing an existing handler,
either using autocompleteByFunction
or autocompleteByName
.
Tip
I recommend using autocompleteByFunction
as it avoids typing the name twice.
Example
Using the autocomplete handler we made "Creating autocomplete handlers":
@Command
class SlashWordDsl : GlobalApplicationCommandProvider {
suspend fun onSlashWord(event: GuildSlashEvent, word: String) {
event.reply_("Your word was $word", ephemeral = true).await()
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
manager.slashCommand("word", function = ::onSlashWord) {
description = "Autocompletes a word"
option("word") {
description = "The word"
// Use an existing autocomplete declaration
autocompleteByFunction(SlashWordAutocompleteDsl::onWordAutocomplete)
}
}
}
}
Rate limiting¶
This lets you reject application commands if the user tries to use them too often.
Learn how to create a rate limiter with "Defining a rate limit"
Using an (anonymous) rate limiter¶
@Command
class SlashRateLimitDsl : GlobalApplicationCommandProvider {
suspend fun onSlashRateLimit(event: GuildSlashEvent) {
event.reply("Hello world!").await()
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
manager.slashCommand("rate_limit_dsl", function = ::onSlashRateLimit) {
// Lets the user use the command 5 times in an hour, but also 2 times in 2 minutes to prevent spam
val bucketConfiguration = Buckets.spikeProtected(
capacity = 5, // 5 uses
duration = 1.hours, // Gives 5 tokens gradually, during an hour (1 token every 12 minutes)
spikeCapacity = 2, // 2 uses
spikeDuration = 2.minutes // Give 2 tokens every 2 minutes
)
val rateLimiter = RateLimiter.createDefault(
// Apply to each user, regardless of channel/guild
RateLimitScope.USER,
// Delete the message telling the user about the remaining rate limit after it expires
deleteOnRefill = true,
// Give our constant bucket configuration
configurationSupplier = bucketConfiguration.toSupplier()
)
rateLimit(rateLimiter)
}
}
}
Using an existing rate limiter¶
Nothing as simple as using rateLimitReference
with the group
of a rate limiter defined in a RateLimitProvider
.
@BService
class WikiRateLimitProvider : RateLimitProvider {
override fun declareRateLimit(manager: RateLimitManager) {
// Lets the user use the command 5 times in an hour, but also 2 times in 2 minutes to prevent spam
val bucketConfiguration = Buckets.spikeProtected(
capacity = 5, // 5 uses
duration = 1.hours, // Gives 5 tokens gradually, during an hour (1 token every 12 minutes)
spikeCapacity = 2, // 2 uses
spikeDuration = 2.minutes // Give 2 tokens every 2 minutes
)
val rateLimiter = RateLimiter.createDefault(
// Apply to each user, regardless of channel/guild
RateLimitScope.USER,
// Delete the message telling the user about the remaining rate limit after it expires
deleteOnRefill = true,
// Give our constant bucket configuration
configurationSupplier = bucketConfiguration.toSupplier()
)
// Register, any command can use it
manager.rateLimit(RATE_LIMIT_GROUP, rateLimiter)
}
companion object {
// The name of the rate limit, so you can reference it in your commands/components
const val RATE_LIMIT_GROUP = "Wiki"
}
}
@Command
class SlashRateLimitExistingDsl : GlobalApplicationCommandProvider {
suspend fun onSlashRateLimit(event: GuildSlashEvent) {
event.reply("Hello world!").await()
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
manager.slashCommand("rate_limit_existing_dsl", function = ::onSlashRateLimit) {
// Use the rate limiter we defined in [[WikiRateLimitProvider]]
rateLimitReference(WikiRateLimitProvider.RATE_LIMIT_GROUP)
}
}
}
Cooldown¶
A cooldown is a rate limit, but with fewer parameters, it can be used as cooldown(5.seconds /* also scope and deleteOnRefill */)
.
Generated values¶
Generated values are a command parameter that gets their values computed by the given block everytime the command run.
Contrary to the annotated commands, no checks are required, as this is tied to the currently built command.
Example
@Command
class SlashCreateTimeKotlinDsl : GlobalApplicationCommandProvider {
suspend fun onSlashCreateTime(event: GuildSlashEvent, timestamp: Instant) {
event.reply("I was created on ${TimeFormat.DATE_TIME_SHORT.format(timestamp)}").await()
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
manager.slashCommand("create_time", function = ::onSlashCreateTime) {
description = "Shows the creation time of this command"
// Create a snapshot of the instant the command was created
val now = Instant.now()
generatedOption("timestamp") {
// Give back the instant snapshot, as this will be called every time the command is run
return@generatedOption now
}
}
}
}
Default description¶
You can avoid setting the (non-localized) descriptions of your commands and options
by putting them in a localization file, using the root locale (i.e., no locale suffix),
and have your localization bundle registered with BApplicationConfigBuilder#addLocalizations
.
The same commands as before, but without the descriptions
@Command
class SlashSayDefaultDescriptionKotlin : ApplicationCommand() {
@JDASlashCommand(name = "say_default_description")
suspend fun onSlashSayDefaultDescription(event: GuildSlashEvent, @SlashOption content: String) {
event.reply(content).await()
}
}
@Command
class SlashSayDefaultDescriptionKotlinDsl : GlobalApplicationCommandProvider {
suspend fun onSlashSayDefaultDescription(event: GuildSlashEvent, content: String) {
event.reply(content).await()
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
manager.slashCommand("say_default_description", function = ::onSlashSayDefaultDescription) {
option("content")
}
}
}
@Command
public class SlashSayDefaultDescription extends ApplicationCommand {
@JDASlashCommand(name = "say_default_description")
public void onSlashSayDefaultDescription(GuildSlashEvent event, @SlashOption String content) {
event.reply(content).queue();
}
}
Adding the root localization bundle
For the given resource bundle:
{
"say_default_description": {
"description": "Says something",
"options": {
"content.description": "What to say"
}
}
}
You can add the bundle by calling BApplicationConfigBuilder#addLocalizations("Commands")
.
Using predefined choices¶
If your choices stay the same for every command, you can improve re-usability and avoid extra code by using choices on the resolver's level, that is, the resolver will return the choices used for every option of their type.
All you now need to do is enable usePredefinedChoices
on your option.
Example
Here, the resolver for TimeUnit
is already defined and will be explained in Adding option resolvers.
@Command
class SlashConvertSimplifiedKotlin : ApplicationCommand() {
@JDASlashCommand(name = "convert_simplified", 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
class SlashConvertSimplifiedKotlinDsl : GlobalApplicationCommandProvider {
suspend fun onSlashConvertSimplified(event: GuildSlashEvent, time: Long, from: TimeUnit, to: TimeUnit) {
event.reply("${to.convert(time, from)} ${to.name.lowercase()}").await()
}
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
manager.slashCommand("convert_simplified", function = ::onSlashConvertSimplified) {
description = "Convert time to another unit"
option("time") {
description = "The time to convert"
}
option("from") {
description = "The unit to convert from"
usePredefinedChoices = true
}
option("to") {
description = "The unit to convert to"
usePredefinedChoices = true
}
}
}
}
@Command
public class SlashConvertSimplified extends ApplicationCommand {
@JDASlashCommand(name = "convert_simplified", 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();
}
}
Update logs¶
You can optionally get more info on what changed in your application commands,
by enabling the TRACE
logs on io.github.freya022.botcommands.internal.commands.application.diff.DiffLogger
,
or any package it is in.
Examples¶
You can take a look at more examples here.