Kord Extensions Help

Custom Converters

When the bundled converters don't meet your needs, you can create your own. Kord Extensions provides utilities that make creating your own converters easier, generating converter builder functions automatically.

Build Configuration

Before getting started, you'll need to set up KSP and the converter annotation processor.

First, add the dependencies to your gradle/libs.versions.toml. Check GitHub for the latest version of KSP.


[versions] kord-extensions = "2.2.1-SNAPSHOT" ksp = "KSP VERSION HERE" [libraries] ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } kord-extensions-processor = { module = "dev.kordex:annotation-processor", version.ref = "kord-extensions" }

Then, update your build.gradle.kts.


plugins { // ... idea id("com.google.devtools.ksp") version "KSP VERSION HERE" } dependencies { // ... ksp(libs.kord.extensions.processor) } idea { // Fixes IntelliJ indexing and build optimisation module { // Not using += due to https://github.com/gradle/gradle/issues/8749 // (Gradle closed this as fixed, but they broke it again) sourceDirs = sourceDirs + file("${layout.buildDirectory}/generated/ksp/main/kotlin") testSources.setFrom( testSources.from + file("${layout.buildDirectory}/generated/ksp/test/kotlin") ) generatedSourceDirs = generatedSourceDirs + file("${layout.buildDirectory}/generated/ksp/main/kotlin") + file("${layout.buildDirectory}/generated/ksp/test/kotlin") } }

First, add the dependencies to your gradle/libs.versions.toml. Check GitHub for the latest version of KSP.


[versions] kord-extensions = "2.2.1-SNAPSHOT" ksp = "KSP VERSION HERE" [libraries] ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } kord-extensions-processor = { module = "dev.kordex:annotation-processor", version.ref = "kord-extensions" }

Then, update your build.gradle.


plugins { // ... id "idea" id "com.google.devtools.ksp" version "KSP VERSION HERE" } dependencies { // ... ksp libs.kord.extensions.processor } idea { // Fixes IntelliJ indexing and build optimisation module { // Not using += due to https://github.com/gradle/gradle/issues/8749 // (Gradle closed this as fixed, but they broke it again) sourceDirs = sourceDirs + file("${layout.buildDirectory}/generated/ksp/main/kotlin") testSources.setFrom( testSources.from + file("${layout.buildDirectory}/generated/ksp/test/kotlin") ) generatedSourceDirs = generatedSourceDirs + file("${layout.buildDirectory}/generated/ksp/main/kotlin") + file("${layout.buildDirectory}/generated/ksp/test/kotlin") } }

Converter Anatomy

All converters extend one of the converter base types. Most of your converters will inherit the SingleConverter type, and follow a strict structure. Once you've written your converter, you'll add the @Converter annotation, which will configure the annotation processor to generate your converter's builders and DSL functions.

All converter types take a generic type parameter, referred to as T. This type represents the resulting rich type your converter provides to users when they finish parsing arguments. The only restriction for this type is that it can’t be nullable.

Constructor

Your converter's constructor must take a single parameter, the validator provided by the user. This parameter should be nullable, default to null, and the validator's generic type parameter must match the one provided to your converter.

The converter base types define this constructor parameter, so you'll need to override it.

public class SnowflakeConverter( override var validator: Validator<Snowflake> = null ) : SingleConverter<Snowflake>() { // ... }

Your converter's constructor may take additional parameters, as defined in the builderConstructorArguments property in the @Converter annotation, and explained below.

Properties

Your converter must override some properties defined by the base converter types:

Name

Type

Description

🌐

signatureTypeString

String

A translation key referring to a short description explaining the type of data this converter handles. Shown in help commands for Chat Commands and errors for all command types.

Additionally, you may override the following properties as required:

Name

Type

Description

🏷️

bundle

String?

A translation bundle, used to resolve translations for this converter. Defaults to null, which will result in the default Kord Extensions bundle being used.

🌐

errorTypeString

String?

A translation key representing a longer description for this converter's handled type than signatureTypeString. Used in "invalid value" errors instead of signatureTypeString if provided. Defaults to null.

showTypeInSignature

Boolean

Whether the signatureTypeString should be shown in help commands for Chat Commands. Defaults to true

override val signatureTypeString: String = "converters.snowflake.signatureType" override val bundle: String = DEFAULT_KORDEX_BUNDLE

Functions

Your converter must override some functions defined by the base converter types.

String Parsing

All converter types must implement the parse function, responsible for parsing arguments from a stream of string-based tokens. Chat Commands use this function to parse arguments from Discord messages into rich types.

This function takes the following parameters:

  • parser: StringParser? - Instance of Kord Extensions' String parser. Call parser.parseNext() to attempt to retrieve the next argument string token provided by the user.

    May be null when named is provided. Wrapping converters (e.g. SingleToOptional) used internally may also call parse with a parser value of null.

  • context: CommandContext - Context Objects representing the command being executed.

    Note: It is important your converter can parse arguments using only the API provided by the base CommandContext type. The converter may check the type and respond accordingly, but this parameter can be of an unusual context type that may exist for any number of purposes, and your converter can't break in these scenarios.

  • named: String? - Set when users provide values using keyword arguments. Note: It's important your converter prioritises this parameter when provided, instead of trying to parse values from the parser.

This function must return a Boolean representing whether your converter managed to successfully parse a value - true if it did, and false otherwise. If your converter managed to parse a value, it must store that value in the parsed class property.

To provide specific errors to users, your converter may throw a DiscordRelayedException.

override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { // Use `named`, try the parser, then give up if neither are present. val arg: String = named ?: parser?.parseNext()?.data ?: return false try { // Try to parse the value and store it. this.parsed = Snowflake(arg) } catch (e: NumberFormatException) { // Invalid Snowflake, supply a relevant error to the user. throw DiscordRelayedException( context.translate("converters.snowflake.error.invalid", replacements = arrayOf(arg)) ) } // Parsing was successful, report that by returning `true`. return true }

Slash Commands

All converter types that extend SlashCommandConverter (which includes all converters that extend SingleConverter) must implement two additional functions.

override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { // Get the correct option value type, and give up if an unexpected type is provided. val optionValue = (option as? StringOptionValue)?.value ?: return false try { // Try to parse the value and store it. this.parsed = Snowflake(optionValue) } catch (e: NumberFormatException) { // Invalid Snowflake, supply a relevant error to the user. throw DiscordRelayedException( context.translate("converters.snowflake.error.invalid", replacements = arrayOf(optionValue)) ) } // Parsing was successful, report that by returning `true`. return true }

@Converter Annotation

Once you've written your converter, it should something like the example below.

public class SnowflakeConverter( override var validator: Validator<Snowflake> = null ) : SingleConverter<Snowflake>() { override val signatureTypeString: String = "converters.snowflake.signatureType" override val bundle: String = DEFAULT_KORDEX_BUNDLE override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { val arg: String = named ?: parser?.parseNext()?.data ?: return false try { this.parsed = Snowflake(arg) } catch (e: NumberFormatException) { throw DiscordRelayedException( context.translate("converters.snowflake.error.invalid", replacements = arrayOf(arg)) ) } return true } override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { val optionValue = (option as? StringOptionValue)?.value ?: return false try { this.parsed = Snowflake(optionValue) } catch (e: NumberFormatException) { throw DiscordRelayedException( context.translate("converters.snowflake.error.invalid", replacements = arrayOf(optionValue)) ) } return true } }

This converter is functional, but it still needs converter functions before users can define arguments with it. To make this easier, Kord Extensions provides a special @Converter annotation that will generate everything you need.

To use it, annotate your converter class with @Converter and provide the relevant parameters.

@Converter( "snowflake", types = [ConverterType.DEFAULTING, ConverterType.LIST, ConverterType.OPTIONAL, ConverterType.SINGLE] )

Name

Type

Description

Required Parameters

names

vararg String

Converter name, used to generate the names of the converter DSL functions. Ideally a single, lower-case word.

When specified, multiple names will result in a set of DSL functions for each provided name. This may be useful for converters using words that differ in different English dialects, such as "color" and "colour."

types

Array<ConverterType>

Array of ConverterType enum entries.

  • First, provide SINGLE or COALESCING based on your converter's base type.

    • SingleConverter - provide SINGLE.

    • CoalescingConverter - provide COALESCING.

    These options are mutually exclusive and cannot be provided together.

  • Converters extending ChoiceConverter must additionally provide CHOICE.

  • Any combination of DEFAULTING, LIST and OPTIONAL may be additionally provided as desired.

Optional Parameters

imports

Array<String>

Extra imports required by your converter. These will be provided in all generated files.

builderConstructorArguments

Array<String>

Arguments to add to the generated builders' constructors. This must be a full definition (including visibility modifiers and val/var).

Prefix an argument with !! to prevent it from being passed into the converter's constructor.

builderGeneric

String

Generic type parameter that the generated builders should take. This must be a full definition (including name and type bound), and multiple may be provided.

builderFields

Array<String>

Extra fields that will be defined within the generated builders. This must be a full definition (including visibility modifiers and val/var).

For required values that users must provide, use lateinit var.

builderSuffixedWhere

String

Extra bounds for the generated builders' generic type parameters, to be provided after where in their signatures.

builderBuildFunctionPreStatements

Array<String>

Extra lines of code to add to the generated builders' build functions before the converter object is constructed.

builderBuildFunctionStatements

Array<String>

Extra lines of code to add to the generated builders' build functions after the converter object is constructed.

builderInitStatements

Array<String>

Extra lines of code to add to the generated builders' init blocks.

builderExtraStatements

Array<String>

Extra lines of code to add to the generated builders' class bodies, after their init blocks and fields.

functionBuilderArguments

Array<String>

Arguments to add to the generated builder functions, which will be passed into the builders' constructors. This must be a full definition (including name and type).

functionGeneric

String

Generic type parameter that the generated builder functions should take. This must be a full definition (including name and type bound), and it will be reified automatically.

Only one type parameter is currently supported.

functionSuffixedWhere

String

Extra bounds for the generated builder functions' type parameters, to be provided after where in their signatures.

Code will be generated when the build project is compiled, and you can find it in your project's build/ folder, under generated/main/kotlin/. Kord Extensions aims to generate well-formatted code, and it includes comments to illustrate precisely where code will be injected.

As the bundled converters make use of the annotation processor, you can look at them for more examples. They can be found on GitHub.

Using Custom Converters

Your custom converters may be used in the same manner described on the converters page. Create an Arguments class, define arguments using your converter's builder functions, and use it in your command definitions.

Last modified: 03 September 2024