Skip to content

Option resolvers

These are to create arguments (such as TimeUnit) from inputs of your command options (like a slash command's string option with choices).

Basics

Classic resolver

This is typically what you'll need, make one by creating a class extending ClassParameterResolver, you need to pass the output type there, then by implementing resolver(s) specific to your command type(s).

Creating resolvers for parametrized types

You can also extend TypedParameterResolver for use with parametrized type, Kotlin users can pass a KType using typeOf, but Java users can use a KotlinTypeToken instead.

The class must then be discoverable, which is why you also need to annotate it with @Resolver, or the function returning the built resolver.

Info

@Resolver is one of the annotations that are considered as a service annotation. This means that it behaves exactly the same as if you had used @BService, except here the annotation is more meaningful.

Resolver factories

These are more advanced, they let you match the being-resolved parameters individually, you can inspect them in depth and decide if they are compatible or not.

If you still want to make the parameters by type only, use TypedParameterResolverFactory, if you need more control, use ParameterResolverFactory.

If one is compatible, you can then return a customized resolver.

Priorities

When a resolver is requested by a command, at most one resolver factory must support the parameter.

Note

Resolvers are requested per interaction type (e.g. text command and slash command resolvers are treated separately), for example, this enables making a TimeUnit resolver for slash commands, and a separate one for text commands, with the default priority, without any issue.

If there is more than one, then a higher priority can be used to override the other.

They are set on the @Resolver annotation, or on the ParameterResolverFactory.priority property.

Built-in resolver generators

The framework also provides functions in Resolvers to do most of the work for some types, all you need to do is declare a service factory with @Resolver and use the provided methods.

Note

Currently there is only a factory for enum resolvers, but others might be added in the future.

How to easily make a resolver for an enum type

object TimeUnitResolverSimplified {
    // The displayed name should be lowercase with the first letter uppercase, see Resolvers#toHumanName
    @Resolver
    fun getTimeUnitResolverSimplified() = enumResolver<TimeUnit>(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS) {
        // Optional configuration
    }
}
As this functions as a service factory, the method needs to be in an object or have a no-arg constructor.

public class TimeUnitResolverSimplified {
    @Resolver
    public static ParameterResolver<?, TimeUnit> getTimeUnitResolverSimplified() {
        // The displayed name should be lowercase with the first letter uppercase, see Resolvers#toHumanName
        return Resolvers.enumResolver(
                        TimeUnit.class,
                        EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS))
                // Optional configuration
                .build();
    }
}
As this functions as a service factory, the method needs to be static.

Text commands support

To support text commands, you need to configure it further with withTextSupport.

Implementation

Slash commands

The interface to be implemented is SlashParameterResolver.

A TimeUnit resolver

@Resolver
class SlashTimeUnitResolver :
        ClassParameterResolver<SlashTimeUnitResolver, TimeUnit>(TimeUnit::class),
        SlashParameterResolver<SlashTimeUnitResolver, TimeUnit> {

    override val optionType: OptionType = OptionType.STRING

    // This is all you need to implement to support predefined choices
    override fun getPredefinedChoices(guild: Guild?): Collection<Choice> {
        return listOf(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS)
            // The Resolvers class helps us by providing resolvers for any enum type.
            // We're just using the helper method to change an enum value to a more natural name.
            .map { Choice(it.toHumanName(), it.name) }
    }

    override suspend fun resolveSuspend(
        option: SlashCommandOption,
        event: CommandInteractionPayload,
        optionMapping: OptionMapping
    ): TimeUnit = enumValueOf<TimeUnit>(optionMapping.asString)
}
@Resolver
@NullMarked // Everything is non-null unless @Nullable
public class SlashTimeUnitResolver
        extends ClassParameterResolver<SlashTimeUnitResolver, TimeUnit>
        implements SlashParameterResolver<SlashTimeUnitResolver, TimeUnit> {

    public SlashTimeUnitResolver() {
        super(TimeUnit.class);
    }

    @Override
    public OptionType getOptionType() {
        return OptionType.STRING;
    }

    @Override
    public Collection<Command.Choice> getPredefinedChoices(@Nullable Guild guild) {
        return Stream.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS)
                // The Resolvers class helps us by providing resolvers for any enum type.
                // We're just using the helper method to change an enum value to a more natural name.
                .map(u -> new Command.Choice(Resolvers.toHumanName(u), u.name()))
                .toList();
    }

    @Nullable
    @Override
    public TimeUnit resolve(SlashCommandOption option, CommandInteractionPayload event, OptionMapping optionMapping) {
        return TimeUnit.valueOf(optionMapping.getAsString());
    }
}

As you can see, this defines the slash command's option to be a string, and provides predefined choices, letting you easily use them in your commands.

Note: Using the choices generated by these resolvers requires you to enable SlashOption.usePredefinedChoices.

Text commands

Typically, the interface to be implemented is TextParameterResolver, but sometimes you'll want to implement QuotableTextParameterResolver when parsing gets too complicated.

A TimeUnit resolver

@Resolver
class TextTimeUnitResolver : ClassParameterResolver<TextTimeUnitResolver, TimeUnit>(TimeUnit::class),
                             TextParameterResolver<TextTimeUnitResolver, TimeUnit> {

    // "(?i)" and "(?-i)" delimits a section of the pattern which is case-insensitive
    override val pattern: Pattern = Pattern.compile("(?i)(seconds|minutes|hours|days)(?-i)")
    // A string combined with other options to check that a variation's final pattern is parse-able
    override val testExample: String = "hOurS"

    override fun getHelpExample(option: TextCommandOption, event: BaseCommandEvent): String {
        return "hours"
    }

    override suspend fun resolveSuspend(
        option: TextCommandOption,
        event: MessageReceivedEvent,
        args: Array<String?>,
    ): TimeUnit {
        // This code runs only if the pattern matched,
        // in this case, it must correspond to a valid 'TimeUnit'
        val unitStr = args[0]!!.uppercase()
        return TimeUnit.valueOf(unitStr)
    }
}
@Resolver
@NullMarked // Everything is non-null unless @Nullable
public class TextTimeUnitResolver
        extends ClassParameterResolver<TextTimeUnitResolver, TimeUnit>
        implements TextParameterResolver<TextTimeUnitResolver, TimeUnit> {

    public TextTimeUnitResolver() {
        super(TimeUnit.class);
    }

    @Override
    public Pattern getPattern() {
        // "(?i)" and "(?-i)" delimits a section of the pattern which is case-insensitive
        return Pattern.compile("(?i)(seconds|minutes|hours|days)(?-i)");
    }

    @Override
    public String getTestExample() {
        // A string combined with other options to check that a variation's final pattern is parse-able
        return "hOurS";
    }

    @Override
    public String getHelpExample(TextCommandOption textCommandOption, BaseCommandEvent baseCommandEvent) {
        return "hours";
    }

    @Nullable
    @Override
    public TimeUnit resolve(TextCommandOption option, MessageReceivedEvent event, String[] args) {
        // This code runs only if the pattern matched,
        // in this case, it must correspond to a valid 'TimeUnit'
        var unitStr = args[0].toUpperCase();
        return TimeUnit.valueOf(unitStr);
    }
}