Skip to content

SlashCommands in JDA Chewtils

Olivia edited this page May 18, 2022 · 26 revisions

JDA-Chewtils 1.20 added support for Discord's inbuilt Slash commands, adding an easy-to-use framework for the otherwise complicated command system.

Please note that this Guide is a deeper breakdown of Slash commands. For a simple migration Guide from JDA-Utilities Command-System to this one, check out the Migration page.

Table of contents

Getting Started

Before getting started, if you plan to make server-specific slash commands, be sure to add the applications.commands scope when inviting it.

Representation of the where you can add the scope

To get started, should you create a new class for your command. In our example, we will create a /ping command and create a PingCommand class for it.

Once created, you will need to extend the SlashCommand class from JDA-Chewtils and add the execute void.

If you've done everything correctly, should your class look something like this now:

public class PingCommand extends SlashCommand {
    
    @Override
    public void execute(SlashCommandEvent event) {
        
    }
}

Constructor

You now have the class set up, but we haven't set a command name, description, or other information yet.
You will need to set up a constructor and set the different values using this.x where x is the option you want to override.

Example of setting a name and description:

public class PingCommand extends SlashCommand {
    
    public PingCommand() {
        this.name = "ping"; // This has to be lowercase
        this.help = "Performs a ping to see the bot's delay"; // Describe the command here
    }
    
    @Override
    public void execute(SlashCommandEvent event) {
        
    }
}

The above would already be enough to count as a valid command, but many more options are available, which are listed below.

Option Type Description Default
children SlashCommand[] Array of SlashCommand instances count as children/subcommands of the main one. Sub-commands may not have their own children set. new SlashCommand[0]
options List<OptionData> List of OptionData to use for the commands. For a normal command would this be /command <optiondata> and for subcommands /command <subcommand> <optiondata> new ArrayList<>()
subcommandGroup SubcommandGroupData Subcommand/child group the command is associated with. This only works in a child/subcommand. null

Keep in mind that the above list only contains the protected fields of the SlashCommand class. The class extends Commands, meaning you can also set and use values from the Command class. There is no guarantee that the class will support all options.

Handling the command

Slash commands are a bit special in that they need to be "acknowledged" by the bot within 3 seconds after receiving it. Otherwise, the command will be treated as "failed" by Discord and result in an error message for the Client.

To acknowledge a Slashcommand are their various options from JDA that can be used to do this.
If you, for example, just want to give a simple response to a command you can use reply(String).

Example:

public class PingCommand extends SlashCommand {
    
    public PingCommand() {
        this.name = "ping"; // This has to be lowercase
        this.help = "Performs a ping to see the bot's delay";
    }
    
    @Override
    public void execute(SlashCommandEvent event) {
        event.reply("Pong!").queue();
    }
}

If you for whatever reason need to delay your response but don't want the command to fail you can use one of the deferX methods to "pretend" sending a response.

Example:

public class PingCommand extends SlashCommand {
    
    public PingCommand() {
        this.name = "ping"; // This has to be lowercase
        this.help = "Performs a ping to see the bot's delay";
    }
    
    @Override
    public void execute(SlashCommandEvent event) {
        // Sends a "<bot> is thinking..." response and allows you a delayed response.
        event.deferReply().queue(
            hook -> hook.editOriginal("Pong!").queueAfter(5, TimeUnit.SECONDS)
        );
    }
}

Registering the Command

Now that your command is properly set up you need to tell JDA-Chewtils to load and register it in Discord.
To do this will you need to create an instance of the CommandClientBuilder first, add the Commands using addSlashCommand or addSlashCommands and finally build it and register it as an event listener in JDA to work.

Example:

CommandClientBuilder builder = new CommandClientBuilder();

// Load the Slash commands
builder.addSlashCommand(new PingCommand());

// Build the CommandClient instance
CommandClient commandClient = builder.build();

// Add it as an event listener to JDA
JDA jda = JDABuilder.createDefault("my.secret.token")
    .addEventListeners(
        commandClient,
        // Any other events you have
    ).build();

By default are Slash commands global, which can take up to an hour for them to be loaded and/or updated.
If you only use the bot and its command in a single server or would like to test your bot, you should consider using the forceGuildId method to set the command as Guild-specific. Guild-specific commands are loaded and updated nearly instantly.

Development

Developing slash commands is a lot different from testing normal commands. Because they need to be upserted, it's highly suggested to utilize the forceGuildId(String guildId) method in the CommandClientBuilder.

Forcing Upsertion to a Single Server

In development, you should always use the forceGuildId(String guildId) method in CommandClientBuilder. Upserting commands to a specific server will update the commands cache instantly, instead of taking up to an hour on global commands.

Example:

CommandClientBuilder builder = new CommandClientBuilder();

// ...

builder.forceGuildId("134445052805120001");

TIP: Also keep this on for server-specific bots in production so updates can take immediately.

Advanced

Here are some more advanced explanations regarding Slash Commands.

Custom Options

You may want to allow users to put their own values in a command to execute, like f.e. the text in a say command.
For that does JDA-Chewtils offer the options field that can be changed through the Constructor.

options is a List of OptionData instances, so you have to provide an ArrayList of those.

Example:

public class SayCommand extends SlashCommand {
    
    public SayCommand() {
        this.name = "say";
        this.help = "Makes the bot say something";
        
        List<OptionData> options = new ArrayList<>();
        options.add(new OptionData(OptionType.STRING, "text", "The text to say.").setRequired(true))
        
        this.options = options;
    }
    
    @Override
    public void execute(SlashCommandEvent event) {
        
    }
}

The above example would work fine, but making a new ArrayList for a single entry is a bit of overkill here.
Fortunately, Java does offer a Collections.singletonList(T) method which allows having a list with a single entry, making the above code look like this now:

public class SayCommand extends SlashCommand {
    
    public SayCommand() {
        this.name = "say";
        this.help = "Makes the bot say something";
        
        this.options = Collections.singletonList(new OptionData(OptionType.STRING, "text", "The text to say.").setRequired(true));
    }
    
    @Override
    public void execute(SlashCommandEvent event) {
        
    }
}

Now comes the question: How do we use the provided value?
This is simple. JDA offers a getOption(String) method to get an OptionMapping instance with the provided key name.

The returned OptionMapping instance can then, after doing some null checks, be converted into whatever value was previously set to be allowed.

Example:

public class SayCommand extends SlashCommand {
    
    public SayCommand() {
        this.name = "say";
        this.help = "Makes the bot say something";
        
        this.options = Collections.singeltonList(new OptionData(OptionType.STRING, "text", "The text to say.").setRequired(true))
    }
    
    @Override
    public void execute(SlashCommandEvent event) {
        OptionMapping option = event.getOption("text"); // string must match the name we set above for the option.
        
        // getOption is nullable!
        if (option == null) {
            // Send a response only the command executor can see.
            event.reply("Could not execute say command. Text option was null!").setEphemeral(true).queue();
            return;
        }
        
        event.reply(option.getAsString()).queue();
    }
}

Sometimes you may want to simplify things, or have some default value used when the option couldn't be found.
For that does JDA-Chewtils offer the OptionHelper class. This class has many useful methods to get a specific option as a particular value (i.e. String) or get a default returned when it couldn't be found.

Using the OptionHelper class would turn the above example into this:

public class SayCommand extends SlashCommand {
    
    public SayCommand() {
        this.name = "say";
        this.help = "Makes the bot say something";
        
        this.options = Collections.singeltonList(new OptionData(OptionType.STRING, "text", "The text to say.").setRequired(true))
    }
    
    @Override
    public void execute(SlashCommandEvent event) {
        String message = OptionHelper.optString(event, "text"); // Would return null if the option was not found.
        
        // message may be null.
        if (message == null) {
            // Send a response only the command executor can see.
            event.reply("Could not execute say command. Text option was null!").setEphemeral(true).queue();
            return;
        }
        
        event.reply(message).queue();
    }
}

With the above setup would the bot now respond with whatever text you provided.
For example, would /say I am a bot result in the bot saying I am a bot

Subcommands

Sometimes you may want to have a subcommand. For example a add subcommand for your /tag command to add new tags.
For such a case you can use the children option of SlashCommand. This option allows you to set an array of classes, which extend the SlashCommand class, to be added and treated as subcommands.

Notes

  • Once a child is added, the execute void of the main command will no longer be executable.
  • Classes that are the child of a command cannot use the children option.
  • If you have children, you cannot use this.options in the main command, and vice versa.

The below example creates a /tag command with an add subcommand:

public class TagCommand extends SlashCommand {
    
    public TagCommand() {
        this.name = "tag";
        this.help = "Let's you set new tags";
        
        this.children = new SlashCommand[]{new Add()};
    }
    
    // This void won't be executed anymore.
    @Override
    public void execute(SlashCommandEvent event) {
    }
    
    // It's recommended to keep subcommands as nested classes.
    private static class Add extends SlashCommand {
        
        public Add() {
            this.name = "add"; // Will be used as the sub command.
            this.help = "Adds a new tag";
            
            // Creating options name and text for /tag add <name> <text>
            List<OptionData> options = new ArrayList<>();
            options.add(new OptionData(DataType.STRING, "name", "The name of the new tag").setRequired(true));
            options.add(new OptionData(DataType.STRING, "text", "The text of the new tag").setRequired(true));
            
            this.options = options;
        }
        
        @Override
        public void execute(SlashCommandEvent event) {
            // Handle the command as described before.
        } 
}

This simple setup would now result in /tag add <name> <text> being created.