Skip to content

Command to SlashCommand Migration

Olivia edited this page Jul 13, 2021 · 9 revisions

With the introduction of SlashCommands, it's all the new hype and everyone wants to do them. How do you convert your existing bot over?

Because SlashCommand extends Command, you are still able to support both kinds of replies. You can do this by having two methods in your command class:

protected void execute(CommandEvent event) {}
protected void execute(SlashCommandEvent event) {}

Depending on how it was executed, it will run one or the other.

If you want to support both, it's recommended to do all of your logic in a different method, and just return a valid response and only do replying in your execute methods.

There are a few things to keep in mind regarding the SlashCommand class, though.

A few methods are unsupported and have no effect as a SlashCommand. These being:

  • this.aliases and getAliases() - Slash Commands don't support aliases.
  • this.arguments and getArguments() - These don't have any effect, and you will need to use this.options to properly handle arguments.
  • event.getArgs() - This command doesn't exist in SlashCommandEvent, so you will not be able to use it. You MUST use Options.

A few methods had their functionality change, as well:

  • this.guildOnly - This now specifies whether or not to mark this as a Global command instead of a Server command.
  • this.guildId - Has been added as a helper for this.guildOnly. This accepts a String and is used for specifying the Server to add this command to. In order for this to matter, guildOnly MUST be true.

A sample migration may end up looking like this: https://github.com/Chew/ChanServ/commit/00314934bd4cbf8e045672f731b1be5bfe8296a0

Command class conversion

The first thing you will need to is to update your JDA-Chewtils to 1.20 or later, then you need to make your Command class extend com.jagrosh.jdautilities.command.SlashCommand, not com.jagrosh.jdautilities.command.Command.

If you're wanting to scrap normal responses entirely in favor of SlashCommand, do the following steps below.

  1. Change the protected void execute(CommandEvent commandEvent) { to say protected void execute(SlashCommandEvent commandEvent) {
  2. Append .queue(); to any event.reply()s you do.
  3. Remove any of the this.x methods mentioned above.
  4. If you don't already have a this.help = "";, you should add one since it shows up in the client to users. They would otherwise get "no help available."

If you have no errors, great! You are done with the initial command class conversion, but you may run into even more issues.

Because getArgs() is gone, you will need to convert any arguments you plan on handing over to the new this.options and OptionData included in JDA.

For example, if you accept one argument, such as an input for an 8ball command, you would have something like this:

public class EightBallCommand extends Command {
    public EightBallCommand() {
        this.name = "8ball";
        this.help = "Ask the 8 ball a question!";
        this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
        this.guildOnly = false;
    }

    @Override
    protected void execute(CommandEvent commandEvent) {
        // Get the question, doesn't matter what they said but we'll send it back to them
        String question = commandEvent.getArgs();
        // Pick a number between 0 and 2 inclusive
        int response = rand.nextInt(3);
        EmbedBuilder e = new EmbedBuilder();
        e.setTitle(":question: Question");
        e.setDescription(question);
        String answer = null;
        // Finish and send embed
        e.addField(":8ball: 8ball says", "My Response Here", false);
        commandEvent.reply(e.build());
    }
}

(Full code found here: https://github.com/Chewbotcca/Discord/blob/a9e585a672224306d7e7812ea433fc4990e7a8f8/src/main/java/pw/chew/chewbotcca/commands/fun/EightBallCommand.java)

As you can tell, we use commandEvent.getArgs() to get the argument. This won't work anymore, instead, we will need to instantiate an OptionData object and set the details there.

OptionData data = new OptionData(OptionType.STRING, "input", "the input to ask the eight ball")
    .setRequired(true);

This adds a new input called "input" and with the description that will show below the input. Additionally, it makes it required, not to let the user use the command without providing input.

To add this, we would put it in the constructor:

...
    public EightBallCommand() {
        this.name = "8ball";
        this.help = "Ask the 8 ball a question!";
        this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
        this.guildOnly = false;

        this.options = Collections.singletonList(
            new OptionData(OptionType.STRING, "input", "the input to ask the eight ball")
                .setRequired(true)
        );
    }
...

Then, later on in our code, we can grab the input.

        String question = commandEvent.getArgs();

becomes:

        String question = commandEvent.getOption("input").getAsString();

Then the rest of the command function would work fine.

Our final code should look like this:

public class EightBallCommand extends SlashCommand {
    public EightBallCommand() {
        this.name = "8ball";
        this.help = "Ask the 8 ball a question!";
        this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
        this.guildOnly = false;

        this.options = Collections.singletonList(
            new OptionData(OptionType.STRING, "input", "the input to ask the eight ball")
                .setRequired(true)
        );
    }

    @Override
    protected void execute(CommandEvent commandEvent) {
        // Get the question, doesn't matter what they said but we'll send it back to them
        String question = commandEvent.getOption("input").getAsString();
        // Pick a number between 0 and 2 inclusive
        int response = rand.nextInt(3);
        EmbedBuilder e = new EmbedBuilder();
        e.setTitle(":question: Question");
        e.setDescription(question);
        String answer = null;
        // Finish and send embed
        e.addField(":8ball: 8ball says", "My Response Here", false);
        commandEvent.reply(e.build()).queue();
    }
}

Telling JDA-Chewtils about our SlashCommands

You will need to add your Commands to a new method if you want them to be treated as Slash Commands.

Instead of doing something like this:

client.addCommand(new EightBallCommand());

We need to add it using the new addSlashCommand function:

client.addSlashCommand(new EightBallCommand());

This will upsert the command on boot.

If you end up doing both CommandEvent and SlashCommandEvent, you must add your command to both of these.

CommandEvent to SlashCommandEvent

Because the raw event from JDA is no longer wrapped, you lose all of the methods provided by the CommandEvent class. See the table below for conversion

CommandEvent Method Alternative
getArgs() Deprecated as shown above. See above for alternatives.
getEvent() No longer needed because this would've returned the raw event, just use the parameter
getClient() This is now included in the command itself, so remove the event. part of the call.
linkId() No alternatives yet.
Any reply() You should be able to just append .queue() to resolve these issues.
async() No alternatives yet.
getSelfUser() Replace with event.getJDA().getSelfUser();
getSelfMember() Replace with event.getGuild() == null ? null : event.getGuild().getSelfMember();
isOwner() You will need to implement this manually with getClient()

All of the other methods call event.method() anyway, which should work because of how the event works, except for getMessage().