feat: Improve arguments

* Add UserConverter
* Add a way to specify default value
This commit is contained in:
Ahmad Ansori Palembani 2025-05-23 06:19:02 +07:00
parent fce8afa2b2
commit 04472f8bfa
13 changed files with 119 additions and 21 deletions

View file

@ -1,5 +1,6 @@
package io.github.null2264.tsukumogami.bot.core.module
import io.github.null2264.tsukumogami.bot.core.module.arguments.Test2Arguments
import io.github.null2264.tsukumogami.bot.core.module.arguments.TestArguments
import io.github.null2264.tsukumogami.core.module.api.botModules
import kotlinx.datetime.Clock
@ -13,7 +14,7 @@ val generalModule = botModules("General") {
}
groups("group") {
commands("test", arguments = ::TestArguments) { ctx, args -> ctx.send("Hello world ${args.test}") }
commands("test", arguments = ::Test2Arguments) { ctx, args -> ctx.send("Hello world ${args.user}") }
}
commands("test", arguments = ::TestArguments) { ctx, args -> ctx.send("Hello world ${args.test}") }

View file

@ -2,7 +2,14 @@ package io.github.null2264.tsukumogami.bot.core.module.arguments
import io.github.null2264.tsukumogami.core.commands.Arguments
import io.github.null2264.tsukumogami.core.commands.ext.string
import io.github.null2264.tsukumogami.core.commands.ext.user
class TestArguments : Arguments() {
val test by string("Test")
val test by string("Test") {
default("Lmao")
}
}
class Test2Arguments : Arguments() {
val user by user("User")
}

View file

@ -69,7 +69,7 @@ open class Bot internal constructor(): IGroup {
private fun getContext(message: Message): Context {
val candidate = message.content.parsePrefixCommandAndArguments()
val context = Context(this, message, candidate?.first, candidate?.second?.toMutableList())
context.command = getCommand(context.candidate?.removeAt(0))
context.command = getCommand(context.pullCandidateOrNull())
return context
}

View file

@ -7,7 +7,7 @@ import dev.kord.rest.builder.message.allowedMentions
import io.github.null2264.tsukumogami.core.commands.Command
class Context(
private val bot: Bot,
val bot: Bot,
private val message: Message,
/**
* The prefix that used to invoke the command
@ -19,6 +19,9 @@ class Context(
val candidate: MutableList<String>?,
) {
/**
* The user that invoked the command
*/
val author get() = message.author
/**
@ -26,6 +29,13 @@ class Context(
*/
var command: Command? = null
fun getCandidateOrNull() = candidate?.getOrNull(0)
fun pullCandidateOrNull() = candidate?.removeFirstOrNull()
fun pullCandidateIf(predicate: () -> Boolean): String? {
if (predicate()) return candidate?.removeFirstOrNull()
return null
}
suspend fun send(content: String) = message.channel.createMessage(content)
suspend fun reply(content: String, mentionsAuthor: Boolean = false) = message.channel.createMessage {

View file

@ -1,5 +1,6 @@
package io.github.null2264.tsukumogami.core.commands
import io.github.null2264.tsukumogami.core.Context
import io.github.null2264.tsukumogami.core.commands.converters.Converter
abstract class Arguments {
@ -15,17 +16,13 @@ abstract class Arguments {
return converter
}
suspend fun parse(values: List<String>?) {
val currentValues = values?.toMutableList()
suspend fun parse(context: Context) {
val currentValues = context.candidate?.toMutableList()
run {
args.forEach { arg ->
val value = try {
currentValues?.removeAt(0)
} catch (e: Exception) {
null
} ?: return@run
arg.converter.parse(value)
val value = currentValues?.removeFirstOrNull() ?: return@run
arg.converter.parse(context, value)
}
}
}

View file

@ -13,7 +13,7 @@ open class Command(
open suspend fun invoke(context: Context) {
val parsedArguments = arguments.call()
parsedArguments.parse(context.candidate)
parsedArguments.parse(context)
handler.invoke(context, parsedArguments)
}
}

View file

@ -19,9 +19,9 @@ class Group(
override val allCommands: MutableMap<String, Command> = mutableMapOf()
override suspend fun invoke(context: Context) {
val command = allCommands[context.candidate?.get(0)] ?: return
context.candidate?.removeAt(0)
context.command = command
val command = allCommands[context.getCandidateOrNull()]
context.pullCandidateIf { command != null } ?: return
context.command = command!!
command.invoke(context)
}
}

View file

@ -1,5 +1,6 @@
package io.github.null2264.tsukumogami.core.commands.converters
import io.github.null2264.tsukumogami.core.Context
import io.github.null2264.tsukumogami.core.commands.Argument
import io.github.null2264.tsukumogami.core.commands.Arguments
import kotlin.reflect.KProperty
@ -10,9 +11,14 @@ abstract class Converter<OutputType: Any?> {
abstract var parsed: OutputType
abstract suspend fun parse(input: String): OutputType
abstract suspend fun parse(context: Context, input: String): OutputType
operator fun getValue(thisRef: Arguments, property: KProperty<*>): OutputType {
return parsed
return this.parsed
}
fun default(defaultValue: OutputType): OutputType {
this.parsed = defaultValue
return this.parsed
}
}

View file

@ -1,12 +1,13 @@
package io.github.null2264.tsukumogami.core.commands.converters.impl
import io.github.null2264.tsukumogami.core.Context
import io.github.null2264.tsukumogami.core.commands.converters.Converter
class StringConverter : Converter<String>() {
override var parsed: String = ""
override suspend fun parse(input: String): String {
override suspend fun parse(context: Context, input: String): String {
this.parsed = input
return input
}

View file

@ -0,0 +1,57 @@
package io.github.null2264.tsukumogami.core.commands.converters.impl
import dev.kord.common.entity.Snowflake
import dev.kord.core.entity.User
import io.github.null2264.tsukumogami.core.Context
import io.github.null2264.tsukumogami.core.annotation.TsukumogamiInternalApi
import io.github.null2264.tsukumogami.core.commands.converters.Converter
import io.github.null2264.tsukumogami.core.exceptions.CommandException
import io.github.null2264.tsukumogami.core.ext.users
import kotlinx.coroutines.flow.firstOrNull
class UserConverter : Converter<User>() {
override lateinit var parsed: User
override suspend fun parse(context: Context, input: String): User {
if (input.equals("me", true)) {
val user = context.author
if (user != null) {
this.parsed = user
return this.parsed
}
}
if (input.equals("you", true)) {
this.parsed = context.bot.client.getSelf()
return this.parsed
}
this.parsed = context.findUser(input) ?:
throw CommandException("User not found")
return this.parsed
}
private suspend fun Context.findUser(arg: String): User? =
if (arg.startsWith("<@") && arg.endsWith(">")) {
val id: String = arg.substring(2, arg.length - 1).replace("!", "")
try {
bot.client.getUser(Snowflake(id))
} catch (_: NumberFormatException) {
throw CommandException("Invalid user ID")
}
} else {
try {
bot.client.getUser(Snowflake(arg))
} catch (_: NumberFormatException) {
if (!arg.contains("#")) {
null
} else {
bot.client.users.firstOrNull { user ->
user.tag.equals(arg, true)
}
}
}
}
}

View file

@ -1,6 +1,19 @@
package io.github.null2264.tsukumogami.core.commands.ext
import dev.kord.core.entity.User
import io.github.null2264.tsukumogami.core.commands.Arguments
import io.github.null2264.tsukumogami.core.commands.converters.Converter
import io.github.null2264.tsukumogami.core.commands.converters.impl.StringConverter
import io.github.null2264.tsukumogami.core.commands.converters.impl.UserConverter
fun Arguments.string(name: String) = args(name, StringConverter())
fun Arguments.string(name: String, declaration: Converter<String>.() -> Unit = {}): Converter<String> {
val converter = StringConverter()
declaration(converter)
return args(name, converter)
}
fun Arguments.user(name: String, declaration: Converter<User>.() -> Unit = {}): Converter<User> {
val converter = UserConverter()
declaration(converter)
return args(name, converter)
}

View file

@ -0,0 +1,6 @@
package io.github.null2264.tsukumogami.core.ext
import dev.kord.core.Kord
import dev.kord.core.supplier.EntitySupplyStrategy
val Kord.users get() = with(EntitySupplyStrategy.cache).users

View file

@ -4,7 +4,7 @@ import io.github.null2264.tsukumogami.core.Bot
import io.github.null2264.tsukumogami.core.commands.Command
import io.github.null2264.tsukumogami.core.commands.IGroup
class BotModule internal constructor(val name: String) : IGroup {
open class BotModule constructor(val name: String) : IGroup {
override val allCommands: MutableMap<String, Command> = mutableMapOf()