Skip to content

SlashCommands in JDA Chewtils

Andre_601 edited this page May 19, 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 Command, 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 "no response" by Discord and result in an "bot did not reply" in the client.

To acknowledge a Slash Command, there are various options from JDA that you can use.
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, you will need to create an instance of the CommandClientBuilder, add then 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 command
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, Slash commands are global, which can take up to an hour for them to be loaded and/or updated for all clients. If you only use the bot and its commands in a single server, or would like to test your bot, you should consider using the forceGuildOnly method to set the command as server-specific. Server-specific commands are loaded and updated nearly instantly, but only for that server.

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 forceGuildOnly(String guildId) method in the CommandClientBuilder.

Forcing Upsert to a Single Server

In development, you should always use the forceGuildOnly(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.forceGuildOnly("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 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 exist two options, depending on what version of JDA you are using.

Using JDA 4.x

When using JDA v4 can you use the OptionHelper class provided by JDA-Chewtils to get an option as a particular value (i.e. a String) or return a default value if the option couldn't be found.

Example with OptionHelper:

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();
    }
}

JDA 5.x

JDA v5 provides its own set of methods to get the value of an option as a particular type, or a default value if the option couldn't be found.

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 = event.optString("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();
    }
}

Subcommands

Sometimes you may want to have a sub-command. For example an add subcommand for your /tag command to add new tags.
In this 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 sub-commands.

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.