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