Skip to content

SlashCommands in JDA Chewtils

Olivia edited this page Jun 1, 2021 · 26 revisions

With the recent support for SlashCommands added in JDA-Chewtils 1.20, they're all the new rave. Ever wanted to write your own? Here's how.

Note: This guide is intended for deeper breakdowns of SlashCommands. If you're wanting to do a simple migration from Commands to SlashCommands, use this page.

The following tutorials will guide us through creating some basic commands accepting different types of things.

Table of contents:

Installing JDA-Chewtils with SlashCommands

Because Slash commands are currently a draft, you need to use my repo and its dependency.

For Maven:

Add this repo:

        <repository>
            <id>chew-m2</id>
            <url>https://m2.chew.pro/snapshots/</url>
        </repository>

Then change your current JDA-Utilities dependency to my dependency:

        <dependency>
            <groupId>pw.chew</groupId>
            <artifactId>jda-chewtils</artifactId>
            <version>1.20-SNAPSHOT</version>
            <scope>compile</scope>
            <type>pom</type>
        </dependency>

You are now ready!

Creating a simple Ping SlashCommand

You can create a SlashCommand class like you would a normal Command. Let's make a simple Ping Command.

public class PingCommand extends SlashCommand {
    public PingCommand() {
        this.name = "ping";
        this.help = "Ping the bot to see if it is alive.";
    }

    @Override
    protected void execute(SlashCommandEvent event) {
    }
}

By default, this will make it a global slash command.

To add it, we will need to add it to our client:

// Initialize the client
CommandClientBuilder client = new CommandClientBuilder();
client.addSlashCommand(new PingCommand());

Once we run the bot, it will appear in any server the bot is in when you type /ping. However, we never gave it a response! It will end up saying the interaction failed because we never replied to the event.

Inside our execute method, we can add a simple response:

   @Override
   protected void execute(SlashCommandEvent event) {
       event.reply("Pong!").queue();
   }

Now when we run our ping command, it will reply with "Pong!" however, it will be displayed to everyone.

We can instead make it ephemeral so it only shows its response to the user:

   @Override
   protected void execute(SlashCommandEvent event) {
       event.reply("Pong!").setEphemeral(true).queue();
   }

Now, when a user runs /ping, only they will see the response.

Accepting an Input

Sometimes we need to parse the user input and get a response based on it.

For this, we need to use OptionData (this is the OptionData from net.dv8tion.jda.api.interactions.commands.build.OptionData).

There currently aren't any OptionData docs, so here's mine for now:

Accepting a required, string input

Let's make a simple command to repeat what the user gave us back to them.

For this, we need to instantiate a new OptionData object and use its builder to create our data.

OptionData data = new OptionData(OptionType.STRING, "input", "The input for the bot to repeat.").setRequired(true);

Let's break this down a bit.

The OptionData constructor takes 3 arguments: an OptionType, the name (shown in Discord, must be lowercase), and a description.

We're basically saying accept a string input named "input" and describe it as "The input for the bot to repeat," to the user.

Additionally, the setRequried(true) forces them to input it.

We can tell JDA-Chewtils to use this OptionData by setting this.options in our command constructor.

List<OptionData> data = new ArrayList<>();
data.add(new OptionData(OptionType.STRING, "input", "The input for the bot to repeat.").setRequired(true));
this.options = data;`

Finally, we actually need to get the input. In our execute method, we can get the option by doing event.getOption(option_name), in this case, event.getOption("input"); However, this only gets the option itself. Because it is a string, we also need to do getAsString();

We could do something like this:

String input = event.getOption("input").getAsString();

Your IDE may yell at you about event.getOption() potentially being null. This is safe to ignore because you required the input, but you should take this into consideration when you don't make an argument required.

Our final command may look like this:

public class SayCommand extends SlashCommand {
    public SayCommand() {
        this.name = "say";
        this.help = "Make the bot say what you say!";

        List<OptionData> data = new ArrayList<>();
        data.add(new OptionData(OptionType.STRING, "input", "The input for the bot to repeat.").setRequired(true));
        this.options = data;
    }

    @Override
    protected void execute(SlashCommandEvent event) {
        String input = event.getOption("input").getAsString();
        event.reply(input).queue();
    }
}

Accepting multiple inputs (ex. User and String)

You may end up needing multiple inputs. For this, let's make a simple kick command.

We will need two options:

  • A user to kick (required)
  • A reason for the kick (optional)

The response can be ephemeral, as it will show up in our imaginary audit log.

First, let's define our OptionData as normal:

OptionData userOption = new OptionData(OptionType.USER, "user", "The user to kick").setRequired(true);

This simply adds a required option of type "User" (meaning, only users will be accepted), with a name of "user" and a description of "The user to kick."

However, we need to add a second argument for a reason.

OptionData userOption = new OptionData(OptionType.STRING, "reason", "The reason for the kick");

Because it's optional, and required is set to false by default, we don't need to append .setRequired();

Here's what our constructor would look like:

public KickCommand() {
    this.name = "kick";
    this.help = "Kick a user. Goodbye!";

    // Make sure the bot and user can kick
    this.botPermissions = new Permission[]{Permission.KICK_MEMBERS};
    this.userPermissions = new Permission[]{Permission.KICK_MEMBERS};

    // Only work inside of a server
    this.guildOnly = true;

    List<OptionData> data = new ArrayList<>();
    data.add(new OptionData(OptionType.USER, "user", "The user to kick").setRequired(true));
    data.add(new OptionData(OptionType.STRING, "reason", "The reason for the kick"));
    this.options = data;
}

Now, we need to handle the fact that the reason isn't required. This means we need to have a backup ready, just in case.

The following code checks to see if the option is null and, if so, lets us put a default reason:

String reason = event.getOption("reason") == null ? "*No reason provided*" : event.getOption("reason").getAsString();

(In English: Is the reason null? If so, put no reason provided. Otherwise, get the response as a string)

To get the user, we specifically need the member. The member has the Guild info baked in and ensures they are both on the Guild, and kickable.

Since we specified guildOnly and permissions above, the command will never execute when it can't kick. We can also reasonably trust that getAsMember() won't be null.

To get the member, we can do this:

Member member = event.getOption("user").getAsMember();

Then we can kick as normal.

Our final command class may look like this:

public class KickCommand extends SlashCommand {
    public KickCommand() {
        this.name = "kick";
        this.help = "Kick a user. Goodbye!";

        // Make sure the bot and user can kick
        this.botPermissions = new Permission[]{Permission.KICK_MEMBERS};
        this.userPermissions = new Permission[]{Permission.KICK_MEMBERS};

        // Only work inside of a server
        this.guildOnly = true;

        List<OptionData> data = new ArrayList<>();
        data.add(new OptionData(OptionType.USER, "user", "The user to kick").setRequired(true));
        data.add(new OptionData(OptionType.STRING, "reason", "The reason for the kick"));
        this.options = data;
    }

    @Override
    protected void execute(SlashCommandEvent event) {
        Member member = event.getOption("user").getAsMember();
        String reason = event.getOption("reason") == null ? "*No reason provided*" : event.getOption("reason").getAsString();

        member.kick(reason).queue(u -> event.reply("Kicked").queue());
    }
}

Adding choices to an option

Sometimes you need to specify choices for the user to do. Let's make a simple command to assign self-assignable roles to people.

Say we have 3 rolls users can assign themselves:

  • "Gamer" with an ID of 543985345346548785
  • "Rory Fan" with an ID of 436597835496549665
  • "Meme Connoisseur" with an ID of 345698548965464598

The IDs are important here, as they are what we will be looking for to assign the role. Let's make a simple input with these choices using the addChoice(String name, String description) method:

OptionData roles = new OptionData(OptionType.STRING, "role", "The role to assign yourself").setRequired(true)
    .addChoice("Ganer", "543985345346548785")
    .addChoice("Rory Fan", "436597835496549665")
    .addChoice("Meme Connoisseur", "345698548965464598");

The addChoice method has 2 inputs. The first is what gets shown to the users, and the 2nd is for us to use in our code.

This isn't intended for private data. We can sneak in with an ID because it's public info. The invoker (always) or other users (if not ephemeral) can see the 2nd input by clicking the slash command. They'll see User ran /command role:ID HERE for example.

In our code, we can get their input as we would normally:

String roleId = event.getOption("role").getAsString();