Skip to content

Commit

Permalink
Support choice converters in chat commands.
Browse files Browse the repository at this point in the history
Fixes #137
  • Loading branch information
gdude2002 committed Jun 3, 2024
1 parent 88378b2 commit ef6ac2b
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 28 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
org.gradle.parallel=true
kotlin.incremental=true
ksp.incremental=false
projectVersion=1.8.0-SNAPSHOT
projectVersion=1.8.1-SNAPSHOT
#dokka will run out of memory with the default meta space
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1024m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl

import com.kotlindiscord.kord.extensions.DiscordRelayedException
import com.kotlindiscord.kord.extensions.commands.Argument
import com.kotlindiscord.kord.extensions.commands.CommandContext
import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceConverter
Expand All @@ -14,10 +15,12 @@ import com.kotlindiscord.kord.extensions.commands.converters.Validator
import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter
import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType
import com.kotlindiscord.kord.extensions.parser.StringParser
import com.kotlindiscord.kord.extensions.utils.getIgnoringCase
import dev.kord.core.entity.interaction.OptionValue
import dev.kord.core.entity.interaction.StringOptionValue
import dev.kord.rest.builder.interaction.OptionsBuilder
import dev.kord.rest.builder.interaction.StringChoiceBuilder
import mu.KotlinLogging

/**
* Choice converter for enum arguments. Supports mapping up to 25 choices to an enum type.
Expand Down Expand Up @@ -67,13 +70,46 @@ public class EnumChoiceConverter<E>(
) : ChoiceConverter<E>(choices) where E : Enum<E>, E : ChoiceEnum {
override val signatureTypeString: String = typeName

private val logger = KotlinLogging.logger { }

override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean {
val arg: String = named ?: parser?.parseNext()?.data ?: return false
val choiceValue = choices.getIgnoringCase(arg, context.getLocale())

if (null != choiceValue) {
// The conditional looks weird, but it won't compile otherwise
this.parsed = choiceValue

return true
}

try {
parsed = getter.invoke(arg) ?: return false
val result = getter.invoke(arg)
?: throw DiscordRelayedException(
context.translate(
"converters.choice.invalidChoice",

replacements = arrayOf(
arg,
choices.entries.joinToString { "**${it.key}** -> `${it.value}`" }
)
)
)

this.parsed = result
} catch (e: IllegalArgumentException) {
return false
logger.warn(e) { "Failed to get enum value for argument: $arg" }

throw DiscordRelayedException(
context.translate(
"converters.choice.invalidChoice",

replacements = arrayOf(
arg,
choices.entries.joinToString { "**${it.key}** -> `${it.value}`" }
)
)
)
}

return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.kotlindiscord.kord.extensions.commands.converters.Validator
import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter
import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType
import com.kotlindiscord.kord.extensions.parser.StringParser
import com.kotlindiscord.kord.extensions.utils.getIgnoringCase
import dev.kord.core.entity.interaction.IntegerOptionValue
import dev.kord.core.entity.interaction.OptionValue
import dev.kord.rest.builder.interaction.IntegerOptionBuilder
Expand Down Expand Up @@ -41,9 +42,31 @@ public class NumberChoiceConverter(

override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean {
val arg: String = named ?: parser?.parseNext()?.data ?: return false
val choiceValue = choices.getIgnoringCase(arg, context.getLocale())

if (choiceValue != null) {
this.parsed = choiceValue

return true
}

try {
this.parsed = arg.toLong(radix)
val result = arg.toLong(radix)

if (result !in choices.values) {
throw DiscordRelayedException(
context.translate(
"converters.choice.invalidChoice",

replacements = arrayOf(
arg,
choices.entries.joinToString { "**${it.key}** -> `${it.value}`" }
)
)
)
}

this.parsed = result
} catch (e: NumberFormatException) {
val errorString = if (radix == DEFAULT_RADIX) {
context.translate("converters.number.error.invalid.defaultBase", replacements = arrayOf(arg))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

package com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl

import com.kotlindiscord.kord.extensions.DiscordRelayedException
import com.kotlindiscord.kord.extensions.commands.Argument
import com.kotlindiscord.kord.extensions.commands.CommandContext
import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceConverter
import com.kotlindiscord.kord.extensions.commands.converters.Validator
import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter
import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType
import com.kotlindiscord.kord.extensions.parser.StringParser
import com.kotlindiscord.kord.extensions.utils.getIgnoringCase
import dev.kord.core.entity.interaction.OptionValue
import dev.kord.core.entity.interaction.StringOptionValue
import dev.kord.rest.builder.interaction.OptionsBuilder
Expand All @@ -34,6 +36,26 @@ public class StringChoiceConverter(

override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean {
val arg: String = named ?: parser?.parseNext()?.data ?: return false
val choiceValue = choices.getIgnoringCase(arg, context.getLocale())

if (choiceValue != null) {
this.parsed = choiceValue

return true
}

if (arg.lowercase(context.getLocale()) !in choices.values.map { it.lowercase(context.getLocale()) }) {
throw DiscordRelayedException(
context.translate(
"converters.choice.invalidChoice",

replacements = arrayOf(
arg,
choices.entries.joinToString { "**${it.key}** -> `${it.value}`" }
)
)
)
}

this.parsed = arg

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import com.kotlindiscord.kord.extensions.ExtensibleBot
import com.kotlindiscord.kord.extensions.commands.Argument
import com.kotlindiscord.kord.extensions.commands.Arguments
import com.kotlindiscord.kord.extensions.commands.CommandContext
import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceConverter
import com.kotlindiscord.kord.extensions.commands.converters.*
import com.kotlindiscord.kord.extensions.commands.getDefaultTranslatedDisplayName
import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider
Expand Down Expand Up @@ -123,8 +122,6 @@ public open class ChatCommandParser : KordExKoinComponent {
}

when (val converter = currentArg.converter) {
is ChoiceConverter<*> -> error("Choice converters may only be used with slash commands")

is SingleConverter<*> -> try {
val parsed = if (hasKwargs) {
if (kwValue!!.size != 1) {
Expand Down Expand Up @@ -771,32 +768,32 @@ public open class ChatCommandParser : KordExKoinComponent {
}
}

else -> throw ArgumentParsingException(
context.translate(
"argumentParser.error.errorInArgument",
else -> throw ArgumentParsingException(
context.translate(
"argumentParser.error.errorInArgument",

replacements = arrayOf(
context.translate(
currentArg.displayName,
bundleName = context.command.resolvedBundle ?: converter.bundle
),
replacements = arrayOf(
context.translate(
currentArg.displayName,
bundleName = context.command.resolvedBundle ?: converter.bundle
),

context.translate(
"argumentParser.error.unknownConverterType",
replacements = arrayOf(currentArg.converter)
)
)
),
context.translate(
"argumentParser.error.unknownConverterType",
replacements = arrayOf(currentArg.converter)
)
)
),

"argumentParser.error.errorInArgument",
"argumentParser.error.errorInArgument",

context.getLocale(),
context.command.resolvedBundle ?: converter.bundle,
context.getLocale(),
context.command.resolvedBundle ?: converter.bundle,

currentArg,
argumentsObj,
parser
)
currentArg,
argumentsObj,
parser
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package com.kotlindiscord.kord.extensions.utils

import dev.kord.common.annotation.KordPreview
import dev.kord.core.event.Event
import java.util.Locale

/** Type alias representing a string keyed map. **/
public typealias StringKeyedMap<T> = Map<String, T & Any>
Expand Down Expand Up @@ -83,3 +84,14 @@ public inline fun <reified V : Any, reified T : V> MutableStringKeyedMap<V>.getO

return value
}

/** For string-keyed maps, attempt to retrieve a value using a case-insensitive key. **/
public fun <V : Any> Map<String, V>.getIgnoringCase(key: String, locale: Locale? = null): V? {
val lowerCase = entries.associate { it.key.lowercase() to it.value }

return if (locale != null) {
lowerCase[key.lowercase(locale)]
} else {
lowerCase[key.lowercase()]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ converters.channel.signatureType=channel
converters.channel.error.missing=Unable to find channel\: {0}
converters.channel.error.invalid=Value `{0}` is not a valid channel ID.
converters.channel.error.wrongType=Supplied channel is the wrong type (**{0}**) - must be one of: {1}
converters.choice.invalidChoice=Invalid choice `{0}`, valid choices are: {1}
converters.color.error.unknown=Unknown color\: `{0}`
converters.color.error.unknownOrFailed=Failed to parse color\: `{0}`
converters.color.signatureType=color
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ utils.string.true=1, y, yes, t, true
converters.timestamp.signatureType=timestamp
converters.timestamp.error.invalid=Value `{0}` is not a valid timestamp.
converters.channel.error.wrongType=Supplied channel is the wrong type (**{0}**) - must be one of: {1}
converters.choice.invalidChoice=Invalid choice `{0}`, valid choices are: {1}
converters.string.error.invalid.tooShort=Value `{0}` must not be shorter than `{1}` characters.
converters.string.error.invalid.tooLong=Value `{0}` must not be longer than `{1}` characters.
permission.sendVoiceMessages=Send Voice Messages
Expand Down

0 comments on commit ef6ac2b

Please sign in to comment.