Using autocomplete
Autocomplete lets you have options where you can give suggestions to the user while they type,
the framework allows you to return a collection of choices,
choice-compatible types such as String
, Long
and Double
, or even custom types,
all of which can be cached.
Note
Autocompleted options do not force the user to choose one of the returned choices, they can still type anything.
Creating autocomplete handlers¶
You will have to implement the AutocompleteHandlerProvider
,
enabling you to declare autocomplete handlers using the manager.
You can optionally put a name on the handler, if you plan on using autocompleteByName
,
however, that's not necessary when using autocompleteByFunction
.
@BService
class SlashWordAutocompleteDsl : AutocompleteHandlerProvider {
// https://en.wikipedia.org/wiki/Dolch_word_list#Dolch_list:_Nouns
// but 30 words
private val words = listOf(
"apple", "baby", "back", "ball", "bear", "bed", "bell", "bird", "birthday", "boat",
"box", "boy", "bread", "brother", "cake", "car", "cat", "chair", "chicken", "children",
"Christmas", "coat", "corn", "cow", "day", "dog", "doll", "door", "duck", "egg"
)
// You can also make this return a collection of Choice, see the AutocompleteManager#autocomplete docs
fun onWordAutocomplete(event: CommandAutoCompleteInteractionEvent): Collection<String> {
// Here you would typically filter the words based on what the user inputs,
// but it is already done when you return a Collection<String>
return words
}
// All autocomplete declarations run before any command is registered,
// so you can, in theory, add autocomplete handlers anywhere,
// and use them in any command.
override fun declareAutocomplete(manager: AutocompleteManager) {
manager.autocomplete(::onWordAutocomplete)
}
}
You will have to use @AutocompleteHandler
,
give it a unique name, I'd recommend using one similar to ClassName: optionName
, it will be useful to reference it in commands later on.
Info
An annotated autocomplete handler can still be referenced by name and by function in code-declared commands.
@Handler // Required by the AutocompleteHandler annotation, can be replaced with @Command
class SlashWordAutocomplete {
// https://en.wikipedia.org/wiki/Dolch_word_list#Dolch_list:_Nouns
// but 30 words
private val words = listOf(
"apple", "baby", "back", "ball", "bear", "bed", "bell", "bird", "birthday", "boat",
"box", "boy", "bread", "brother", "cake", "car", "cat", "chair", "chicken", "children",
"Christmas", "coat", "corn", "cow", "day", "dog", "doll", "door", "duck", "egg"
)
// You can also make this return a collection of Choice, see the annotation docs
@AutocompleteHandler(WORD_AUTOCOMPLETE_NAME)
fun onWordAutocomplete(event: CommandAutoCompleteInteractionEvent): Collection<String> {
// Here you would typically filter the words based on what the user inputs,
// but it is already done when you return a Collection<String>
return words
}
companion object {
const val WORD_AUTOCOMPLETE_NAME = "SlashWord: word"
}
}
@Handler // Required by the AutocompleteHandler annotation, can be replaced with @Command
public class SlashWordAutocomplete extends ApplicationCommand {
// https://en.wikipedia.org/wiki/Dolch_word_list#Dolch_list:_Nouns
// but 30 words
private static final List<String> WORDS = List.of(
"apple", "baby", "back", "ball", "bear", "bed", "bell", "bird", "birthday", "boat",
"box", "boy", "bread", "brother", "cake", "car", "cat", "chair", "chicken", "children",
"Christmas", "coat", "corn", "cow", "day", "dog", "doll", "door", "duck", "egg"
);
public static final String WORD_AUTOCOMPLETE_NAME = "SlashWord: word";
// You can also make this return a collection of Choice, see the annotation docs
@AutocompleteHandler(WORD_AUTOCOMPLETE_NAME)
public Collection<String> onWordAutocomplete(CommandAutoCompleteInteractionEvent event) {
// Here you would typically filter the words based on what the user inputs,
// but it is already done when you return a Collection<String>
return WORDS;
}
}
You may also configure other properties:
showUserInput
: Makes the first choice be the user's own inputmode
: Lets you configure out the automatic choice sorter (forString
/Long
/Double
only)
Sorting autocomplete results of Choice
and custom types
Sorting results by relevancy is a tricky task, while it can be as simple as myItemName.startsWith(input)
you can try to use AutocompleteAlgorithms
to easily sort the results.
This is what gets applied on primitive types, but the results won't always be the best.
It may sometimes makes more sense to use one "similarity" algorithm over another, depending on what user input you expect, and what the source items are, you can experiment different algos from the java-string-similarity library, already included in the framework.
You are encouraged to to try inputs against different algos, and find what works the best, which one could filter the results of the previous algo, etc.
Caching¶
When the results are stable, you can enable autocomplete caching, saving time when a user types the same query.
To enable it, configure the cache using the cache
configurer.
By default it will cache by using the user input, but you can add arguments to the cache key by:
- Adding the user ID with
userLocal
- Adding the channel ID with
channelLocal
- Adding the guild ID with
guildLocal
- Adding values of options by their names in
compositeKeys
To enable it, configure the cache using @CacheAutocomplete
.
By default it will cache by using the user input, but you can add arguments to the cache key by:
- Adding the user ID with
userLocal
- Adding the channel ID with
channelLocal
- Adding the guild ID with
guildLocal
- Adding values of options by their names in
compositeKeys
Note
If the outputs for the same input are stable but may rarely change (think, a list that updates daily), you can invalidate autocomplete caches when it eventually does.
Tip
You can also disable the autocomplete cache while developing your bot with the disableAutocompleteCache property, this should help you test your handler live, using hotswap.
Transforming elements into choices¶
If you wish to return collections of anything but the default supported types,
you will need to create a service which transforms those objects into choices,
by implementing AutocompleteTransformer
.
Example
data class FullName(val firstName: String, val secondName: String)
@BService
class FullNameTransformer : AutocompleteTransformer<FullName> {
override val elementType: Class<FullName> = FullName::class.java
override fun apply(e: FullName): Command.Choice {
return Command.Choice("${e.firstName} ${e.secondName}", "${e.firstName}|${e.secondName}")
}
}
public record FullName(String firstName, String secondName) { }
@BService
public class FullNameTransformer implements AutocompleteTransformer<FullName> {
@NotNull
@Override
public Class<FullName> getElementType() {
return FullName.class;
}
@NotNull
@Override
public Command.Choice apply(@NotNull FullName fullName) {
return new Command.Choice(
"%s %s".formatted(fullName.firstName(), fullName.secondName()),
"%s|%s".formatted(fullName, fullName.secondName())
);
}
}
Usage in commands¶
Discover how to use your autocomplete handlers on: