Skip to content

Arguments

RyanLandDev edited this page Jul 26, 2022 · 1 revision

This guide will show you how to use command arguments.

Table of Contents

Using arguments

Let's create a ban command. For this command, we will need two arguments: the member to ban and the reason.

Colossus provides an argument system. In this case, we will use the MemberArgument for the member and the EndlessStringArgument for the reason.
The endless string will be a regular string argument for slash commands, but for message commands, it will get everything following the argument.

All arguments must have a name and description. We are also going to make the reason argument have a maximum length of 500 characters, and the argument will be optional, returning "No reason provided" if the user didn't provide anything.

@CommandBuilder(
    name = "ban",
    description = "Ban someone."
)
public class BanCommand extends BaseCommand implements CombinedCommand {
    @Override
    public ArgumentSet getArguments() {
        return new ArgumentSet().addArguments(
            new MemberArgument()
                .name("member")
                .description("The member to ban"),
            new EndlessStringArgument()
                .setMaximum(500) // maximum reason length
                .name("reason")
                .description("The reason")
                .optional(event -> "No reason provided") // optional value
        );
    }

    @Override
    public void execute(CommandEvent event) throws CommandException {
        // ...
    }
}

The argument's values can be retrieved using the event.getArgument("name") method.
If the user provides no reason, this method will return "No reason provided" as we defined earlier.

    @Override
    public void execute(CommandEvent event) throws CommandException {
        Member member = event.getArgument("member"); // member argument
        String reason = event.getArgument("reason"); // reason argument

        event.getGuild().ban(member, 0, reason).queue(); // ban the member
    }

Creating custom argument types

You can create your own argument types by extending the Argument<T> class, T being the argument return type.

For message commands, arguments are given a Deque<String> of the remaining parameters for the command (parameters split by " ").
For slash commands, arguments are given a Deque<OptionMapping> of the remaining parameters.

Arguments must return CompleteableFuture<T> by default.

There are other subclasses to extend from instead to make life easier:

  • SingleArgument - argument only demanding one value from the argument queue in the parser, returning args.pop()
  • FutureSingleArgument - same as SingleArgument, except for the return type being a CompleteableFuture<T>
  • NumberArgument - extends SingleArgument - catches NumberFormatExceptions in resolvers, then sends an error
  • ArgumentStringResolver - extends SingleArgument - combines the message and slash command resolver for strings
  • FutureArgumentStringResolver - extends FutureSingleArgument - same as ArgumentStringResolver, except for the return type being a CompleteableFuture<T>

Let's create an argument that returns the amount of +'s provided. For this we should extend ArgumentStringResolver<Integer>.

Throwing an ArgumentException in a resolver method will automatically make the bot send a detailed error message, along with the exception message.

public class PlusesArgument extends ArgumentStringResolver<Integer> {
    
    @Override
    public Integer resolve(String arg, CommandEvent event) throws ArgumentException {
        if (!arg.contains("+")) { // if the argument contains characters other than '+', error
            throw new MalformedArgumentException("Only expected pluses (+)");
        }
        if (arg.length() > 5) { // if the argument has more than 5 '+', error
            throw new MalformedArgumentException("You cannot specify more than 5 pluses");
        }
        return arg.length(); // return the amount of '+'
    }
}

Instead of this approach, we could also give the user some options, if they are using a slash command. Because we want different behaviour for slash and message commands, we're going to use a SingleArgument<Integer>.

You can add argument options by overriding the getArgumentOptionData() method. With this method, you can set various types of properties about a slash command option, such as its type, minimum value, etc.

Result:

public class PlusesArgument extends SingleArgument<Integer> {

    @Override
    public ArgumentOptionData getArgumentOptionData() {
        return (ArgumentOptionData) new ArgumentOptionData(OptionType.INTEGER)
            .addChoice("+", 1)
            .addChoice("++", 2)
            .addChoice("+++", 3)
            .addChoice("++++", 4)
            .addChoice("+++++", 5);
    }

    @Override
    public Integer resolveSlashCommandArgument(OptionMapping arg, SlashCommandEvent event) throws ArgumentException {
        return arg.getAsInt();
    }

    @Override
    public Integer resolveMessageCommandArgument(String arg, MessageCommandEvent event) throws ArgumentException {
        if (!arg.contains("+")) {
            throw new MalformedArgumentException("Only expected pluses (+)");
        }
        if (arg.length() > 5) {
            throw new MalformedArgumentException("You cannot specify more than 5 pluses");
        }
        return arg.length();
    }
}

One final, more complicated approach we could take for a message command, is presenting the user with a menu with buttons to pick an amount of pluses.

For this, we need to use futures. We will extend Argument<Integer>, and the slash command should keep the same behaviour.

public class PlusesArgument extends Argument<Integer> {

    @Override
    public ArgumentOptionData getArgumentOptionData() {
        return (ArgumentOptionData) new ArgumentOptionData(OptionType.INTEGER)
            .addChoice("+", 1)
            .addChoice("++", 2)
            .addChoice("+++", 3)
            .addChoice("++++", 4)
            .addChoice("+++++", 5);
    }

    @Override
    public CompletableFuture<Integer> resolveSlashCommandArgument(Deque<OptionMapping> args, SlashCommandEvent event) throws ArgumentException {
        CompletableFuture<Integer> future = new CompletableFuture<>();
        future.complete(args.pop().getAsInt());
        return future;
    }

    @Override
    public CompletableFuture<Integer> resolveMessageCommandArgument(Deque<String> args, MessageCommandEvent event) throws ArgumentException {
        // ...
    }
}

We don't need a command input from the user beforehand, so we will not touch the args queue.
Because there is no input, we should ignore an MissingArgumentException by overriding the ignoreMissingException() method.

We'll create a simple menu using PresetBuilder. In our button consumer, we'll complete the future and set the RepliableEvent for the command event to our button click event: so we can reply to our command correctly.

Result:

    @Override
    public boolean ignoreMissingException() {
        return true;
    }

    @Override
    public CompletableFuture<Integer> resolveMessageCommandArgument(Deque<String> args, MessageCommandEvent event) throws ArgumentException {
        CompletableFuture<Integer> future = new CompletableFuture<>();
        long userId = event.getUser().getIdLong();

        PresetBuilder message = new PresetBuilder("Pick an amount of pluses")
            .addButtons(
                // when a button is clicked, the future is completed
                BaseButton.user(userId, Button.secondary("1", "+"), clickEvent -> {
                    event.setRepliableEvent(clickEvent); // important to set the RepliableEvent before the future is completed
                    future.complete(1); // complete the future + execute the command with this value
                }),
                BaseButton.user(userId, Button.secondary("2", "++"), clickEvent -> {
                    event.setRepliableEvent(clickEvent);
                    future.complete(2);
                }),
                BaseButton.user(userId, Button.secondary("3", "+++"), clickEvent -> {
                    event.setRepliableEvent(clickEvent);
                    future.complete(3);
                }),
                BaseButton.user(userId, Button.secondary("4", "++++"), clickEvent -> {
                    event.setRepliableEvent(clickEvent);
                    future.complete(4);
                }),
                BaseButton.user(userId, Button.secondary("5", "+++++"), clickEvent -> {
                    event.setRepliableEvent(clickEvent);
                    future.complete(5);
                })
            );

        event.reply(message);
        return future;
    }

List of arguments

Below is a list of default argument types provided by Colossus

Name Slash command type Slash command description Message command description Extra parameters Return type
Primitive arguments
BooleanArgument Boolean Returns true if 'true', otherwise returns false Boolean
IntegerArgument Integer Parses String as Integer min, max Integer
DoubleArgument Number Parses String as Double min, max Double
FloatArgument Number Casts Double to Float Parses String as Float min, max Float
LongArgument Number Parses String as Long min, max Long
String arguments
StringArgument String min, max String
QuoteStringArgument String Normal string Returns all text until a closing " min, max String
EndlessStringArgument String Returns all text in the following arguments min, max String
Snowflake arguments
UserArgument User Gets the user using a mention or ID User
MemberArgument User Throws an exception if the provided user is not a member of this server Gets the member using a mention or ID Member
RoleArgument Role Gets the role using a mention or ID Role
GuildChannelArgument Channel Throws an exception if the provided channel is not permitted permittedChannelTypes GuildChannel
GuildArgument String Gets the guild using an ID Gets the guild using an ID Guild
AttachmentArgument Attachment Gets the message attachment, multiple are supported Attachment
Command arguments
BasicCommandArgument String Same as message command, except has all command names as autocompleteable options Gets a BasicCommand (regular or context) using its name, and if multiple results are found, show a menu with choices BasicCommand
CommandArgument String Same as message command, except has all regular command names as autocompleteable options Gets a regular Command using its name Command
ContextCommandArgument String Same as message command, except has all context command names as autocompleteable options Gets a ContextCommand (user or message) using its name, and if multiple results are found, show a menu with choices ContextCommand<?>
UserContextCommandArgument String Same as message command, except has all user context command names as autocompleteable options Gets a user ContextCommand using its name ContextCommand<User>
MessageContextCommandArgument String Same as message command, except has all message context command names as autocompleteable options Gets a message ContextCommand using its name ContextCommand<Message>