From 1c20e0506619b1707035fbbdfa1ce43d1491a3a4 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 20 May 2025 05:54:37 +0700 Subject: [PATCH 01/10] refactor: Turn CommandHolder into internal data class Since we want commands to be registered from modules --- .../io/github/null2264/tsukumogami/core/AbstractBot.kt | 4 ++-- .../github/null2264/tsukumogami/core/BotConfigurator.kt | 6 +++--- .../github/null2264/tsukumogami/core/module/BotModule.kt | 8 ++++---- .../core/module/{Command.kt => CommandHolder.kt} | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) rename core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/{Command.kt => CommandHolder.kt} (88%) diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt index 604b054..594bd09 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt @@ -11,13 +11,13 @@ import dev.kord.gateway.PrivilegedIntent import io.github.null2264.tsukumogami.core.exceptions.CommandException import io.github.null2264.tsukumogami.core.exceptions.CommandNotFound import io.github.null2264.tsukumogami.core.module.BotModule -import io.github.null2264.tsukumogami.core.module.Command +import io.github.null2264.tsukumogami.core.module.CommandHolder import kotlin.reflect.full.callSuspend import kotlinx.coroutines.runBlocking abstract class AbstractBot(configurator: BotConfigurator.() -> Unit) { - private val commands: Map + private val commands: Map private val extensions: Map private val prefixes: List private val client: Kord diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt index de98f03..dbdfc8a 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt @@ -1,21 +1,21 @@ package io.github.null2264.tsukumogami.core import io.github.null2264.tsukumogami.core.module.BotModule -import io.github.null2264.tsukumogami.core.module.Command +import io.github.null2264.tsukumogami.core.module.CommandHolder import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.full.isSubclassOf class BotConfigurator internal constructor() { - internal val commands = mutableMapOf() + internal val commands = mutableMapOf() internal val extensions = mutableListOf>() internal val prefixes = mutableListOf() var token: String = "" internal fun isExists(name: String?) = this.commands.containsKey(name) - fun commands(command: Command, name: String? = null) { + internal fun commands(command: CommandHolder, name: String? = null) { this.commands[if (name.isNullOrEmpty()) command.name else name] = command } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt index 26fe1d5..1cb21a6 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt @@ -2,7 +2,7 @@ package io.github.null2264.tsukumogami.core.module import co.touchlab.kermit.Logger import io.github.null2264.tsukumogami.core.AbstractBot -import io.github.null2264.tsukumogami.core.module.annotation.Command as CommandAnnotation +import io.github.null2264.tsukumogami.core.module.annotation.Command import io.github.null2264.tsukumogami.core.BotConfigurator import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.kotlinFunction @@ -20,7 +20,7 @@ abstract class BotModule(val name: String, val description: String? = null) { val methods = this::class.java.declaredMethods for (method in methods) { for (annotation in method.annotations) { - if (annotation !is CommandAnnotation) + if (annotation !is Command) continue configurator.apply { @@ -32,11 +32,11 @@ abstract class BotModule(val name: String, val description: String? = null) { kMethod?.let { it.isAccessible = true commands( - Command( + CommandHolder( annotation.name.ifEmpty { it.name }, name, - it, annotation.description.ifEmpty { description }, + it, ) ) } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/Command.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/CommandHolder.kt similarity index 88% rename from core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/Command.kt rename to core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/CommandHolder.kt index c5d64ed..f2a56dc 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/Command.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/CommandHolder.kt @@ -5,9 +5,9 @@ import kotlin.reflect.KFunction /** * Class holding information about a command */ -data class Command( +internal data class CommandHolder( val name: String, val extension: String, - val callback: KFunction<*>, val description: String? = null, + val callback: KFunction<*>, ) From c901eb2f661e9b1d131d0fc3f42be70d90a33dff Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 20 May 2025 19:47:52 +0700 Subject: [PATCH 02/10] refactor: Prepare Arguments and stuff --- .../bot/core/module/DeveloperModule.kt | 2 +- .../bot/core/module/GeneralModule.kt | 2 +- .../core/module/arguments/TestArguments.kt | 8 +++ .../null2264/tsukumogami/core/AbstractBot.kt | 17 +++--- .../tsukumogami/core/BotConfigurator.kt | 15 ++--- .../null2264/tsukumogami/core/Context.kt | 4 +- .../tsukumogami/core/commands/Argument.kt | 12 ++++ .../tsukumogami/core/commands/Arguments.kt | 16 ++++++ .../{module => commands}/CommandHolder.kt | 4 +- .../annotation/Command.kt | 3 +- .../core/commands/converters/Converter.kt | 17 ++++++ .../converters/impl/StringConverter.kt | 11 ++++ .../core/commands/ext/ArgumentsExtensions.kt | 6 ++ .../tsukumogami/core/module/BotModule.kt | 3 +- .../core/module/internal/Modules.kt | 40 +++++++++++++ .../core/utils/StringExtensions.kt | 56 +++++++++++++++++++ 16 files changed, 189 insertions(+), 27 deletions(-) create mode 100644 bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/arguments/TestArguments.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Argument.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt rename core/src/main/kotlin/io/github/null2264/tsukumogami/core/{module => commands}/CommandHolder.kt (67%) rename core/src/main/kotlin/io/github/null2264/tsukumogami/core/{module => commands}/annotation/Command.kt (85%) create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/ext/ArgumentsExtensions.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/internal/Modules.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/utils/StringExtensions.kt diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/DeveloperModule.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/DeveloperModule.kt index 95f82f9..17a07d9 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/DeveloperModule.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/DeveloperModule.kt @@ -1,6 +1,6 @@ package io.github.null2264.tsukumogami.bot.core.module -import io.github.null2264.tsukumogami.core.module.annotation.Command +import io.github.null2264.tsukumogami.core.commands.annotation.Command import io.github.null2264.tsukumogami.core.Context import io.github.null2264.tsukumogami.core.module.BotModule diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt index ce39762..b3adb11 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt @@ -1,7 +1,7 @@ package io.github.null2264.tsukumogami.bot.core.module import dev.kord.core.entity.effectiveName -import io.github.null2264.tsukumogami.core.module.annotation.Command +import io.github.null2264.tsukumogami.core.commands.annotation.Command import io.github.null2264.tsukumogami.core.Context import io.github.null2264.tsukumogami.core.module.BotModule import kotlinx.datetime.Clock diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/arguments/TestArguments.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/arguments/TestArguments.kt new file mode 100644 index 0000000..e252549 --- /dev/null +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/arguments/TestArguments.kt @@ -0,0 +1,8 @@ +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 + +class TestArguments : Arguments() { + val test by string("Test") +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt index 594bd09..e04126f 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt @@ -11,7 +11,8 @@ import dev.kord.gateway.PrivilegedIntent import io.github.null2264.tsukumogami.core.exceptions.CommandException import io.github.null2264.tsukumogami.core.exceptions.CommandNotFound import io.github.null2264.tsukumogami.core.module.BotModule -import io.github.null2264.tsukumogami.core.module.CommandHolder +import io.github.null2264.tsukumogami.core.commands.CommandHolder +import io.github.null2264.tsukumogami.core.utils.parseCommandAndArguments import kotlin.reflect.full.callSuspend import kotlinx.coroutines.runBlocking @@ -22,13 +23,13 @@ abstract class AbstractBot(configurator: BotConfigurator.() -> Unit) { private val prefixes: List private val client: Kord + // TODO: Bind Bot and Kord to Koin init { val currentConfig = BotConfigurator() currentConfig.apply(configurator) extensions = mutableMapOf() - currentConfig.extensions.forEach { - val module = it.call() + currentConfig.extensions.forEach { module -> module.setup() module.install(this, currentConfig) extensions[module.name] = module @@ -59,7 +60,7 @@ abstract class AbstractBot(configurator: BotConfigurator.() -> Unit) { fun getCommand(name: String) = commands[name] private fun getContext(message: Message): Context { - val candidate = message.content.hasPrefix() + val candidate = message.content.parsePrefixCommandAndArguments() return Context(this, message, candidate?.first, candidate?.second) } @@ -91,14 +92,16 @@ abstract class AbstractBot(configurator: BotConfigurator.() -> Unit) { Logger.i { "Online! ${client.getSelf().username}" } } - fun String.hasPrefix(): Pair? { + private fun String.parsePrefixCommandAndArguments(): Pair>? { if (this.isBlank()) return null - var ret: Pair? = null + var ret: Pair>? = null prefixes.forEach { if (this.substring(0, it.length) == it) { - ret = Pair(this.substring(0, it.length), this.substring(it.length).split(" ").first()) + val prefix = this.substring(0, it.length) + val cleanPrompt = this.substring(it.length) + ret = Pair(prefix, cleanPrompt.parseCommandAndArguments()) return@forEach } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt index dbdfc8a..ce46057 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt @@ -1,15 +1,14 @@ package io.github.null2264.tsukumogami.core +import io.github.null2264.tsukumogami.core.module.internal.Modules import io.github.null2264.tsukumogami.core.module.BotModule -import io.github.null2264.tsukumogami.core.module.CommandHolder -import kotlin.reflect.KClass +import io.github.null2264.tsukumogami.core.commands.CommandHolder import kotlin.reflect.KFunction -import kotlin.reflect.full.isSubclassOf class BotConfigurator internal constructor() { internal val commands = mutableMapOf() - internal val extensions = mutableListOf>() + internal val extensions = Modules() internal val prefixes = mutableListOf() var token: String = "" @@ -20,13 +19,7 @@ class BotConfigurator internal constructor() { } fun extensions(vararg extensions: KFunction) { - extensions.forEach { - val kClass = it.returnType.classifier as KClass<*> - if (!kClass.isSubclassOf(BotModule::class)) - return - - this.extensions.add(it) - } + this.extensions.initializeAndAddAll(extensions.toList()) } fun prefixes(vararg prefixes: String) { diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt index baed03d..6f2ad19 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt @@ -5,10 +5,10 @@ import dev.kord.core.entity.Message import dev.kord.rest.builder.message.AllowedMentionsBuilder import dev.kord.rest.builder.message.allowedMentions -class Context(private val bot: AbstractBot, private val message: Message, val prefix: String?, private val commandName: String?) { +class Context(private val bot: AbstractBot, private val message: Message, val prefix: String?, private val commandAndArguments: List?) { val author get() = message.author - val command get() = commandName?.let { bot.getCommand(it) } + val command get() = commandAndArguments?.get(0)?.let { bot.getCommand(it) } suspend fun send(content: String) = message.channel.createMessage(content) diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Argument.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Argument.kt new file mode 100644 index 0000000..bc85b9b --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Argument.kt @@ -0,0 +1,12 @@ +package io.github.null2264.tsukumogami.core.commands + +import io.github.null2264.tsukumogami.core.commands.converters.Converter + +data class Argument( + val name: String, + val converter: Converter, +) { + init { + converter.argumentObj = this + } +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt new file mode 100644 index 0000000..686be91 --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt @@ -0,0 +1,16 @@ +package io.github.null2264.tsukumogami.core.commands + +import io.github.null2264.tsukumogami.core.commands.converters.Converter + +open class Arguments { + val args = mutableListOf>() + + fun args( + name: String, + converter: Converter + ): Converter { + args.add(Argument(name, converter)) + + return converter + } +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/CommandHolder.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/CommandHolder.kt similarity index 67% rename from core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/CommandHolder.kt rename to core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/CommandHolder.kt index f2a56dc..25c9a41 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/CommandHolder.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/CommandHolder.kt @@ -1,11 +1,11 @@ -package io.github.null2264.tsukumogami.core.module +package io.github.null2264.tsukumogami.core.commands import kotlin.reflect.KFunction /** * Class holding information about a command */ -internal data class CommandHolder( +data class CommandHolder internal constructor( val name: String, val extension: String, val description: String? = null, diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/annotation/Command.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/annotation/Command.kt similarity index 85% rename from core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/annotation/Command.kt rename to core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/annotation/Command.kt index 9d6bf50..9756178 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/annotation/Command.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/annotation/Command.kt @@ -1,5 +1,4 @@ -package io.github.null2264.tsukumogami.core.module.annotation - +package io.github.null2264.tsukumogami.core.commands.annotation /** * Annotation to tag a function as command diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt new file mode 100644 index 0000000..cc9ed82 --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt @@ -0,0 +1,17 @@ +package io.github.null2264.tsukumogami.core.commands.converters + +import io.github.null2264.tsukumogami.core.commands.Argument +import io.github.null2264.tsukumogami.core.commands.Arguments +import kotlin.reflect.KProperty + +abstract class Converter { + lateinit var argumentObj: Argument + + abstract var parsed: OutputType + + abstract suspend fun parse(input: String): OutputType + + operator fun getValue(thisRef: Arguments, property: KProperty<*>): OutputType { + return parsed + } +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt new file mode 100644 index 0000000..723c52a --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt @@ -0,0 +1,11 @@ +package io.github.null2264.tsukumogami.core.commands.converters.impl + +import io.github.null2264.tsukumogami.core.commands.converters.Converter + +class StringConverter : Converter() { + override var parsed: String = "" + override suspend fun parse(input: String): String { + this.parsed = input + return input + } +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/ext/ArgumentsExtensions.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/ext/ArgumentsExtensions.kt new file mode 100644 index 0000000..c48824b --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/ext/ArgumentsExtensions.kt @@ -0,0 +1,6 @@ +package io.github.null2264.tsukumogami.core.commands.ext + +import io.github.null2264.tsukumogami.core.commands.Arguments +import io.github.null2264.tsukumogami.core.commands.converters.impl.StringConverter + +fun Arguments.string(name: String) = args(name, StringConverter()) diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt index 1cb21a6..ef96d0e 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt @@ -2,8 +2,9 @@ package io.github.null2264.tsukumogami.core.module import co.touchlab.kermit.Logger import io.github.null2264.tsukumogami.core.AbstractBot -import io.github.null2264.tsukumogami.core.module.annotation.Command +import io.github.null2264.tsukumogami.core.commands.annotation.Command import io.github.null2264.tsukumogami.core.BotConfigurator +import io.github.null2264.tsukumogami.core.commands.CommandHolder import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.kotlinFunction diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/internal/Modules.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/internal/Modules.kt new file mode 100644 index 0000000..01ae3e2 --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/internal/Modules.kt @@ -0,0 +1,40 @@ +package io.github.null2264.tsukumogami.core.module.internal + +import io.github.null2264.tsukumogami.core.module.BotModule +import java.util.function.Consumer +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.full.isSubclassOf + +class Modules { + + private val list = mutableListOf() + + fun get(index: Int) = list[index] + + private fun KFunction.tryInitialize(): BotModule? { + val kClass = this.returnType.classifier as KClass<*> + if (!kClass.isSubclassOf(BotModule::class)) + return null + + return this.call() + } + + fun initializeAndAddAll(modules: List>) { + addAll(modules.mapNotNull { it.tryInitialize() }) + } + + fun addAll(modules: List) { + list.addAll(modules) + } + + fun add(module: KFunction) { + module.tryInitialize()?.let { add(it) } + } + + fun add(module: BotModule) { + list.add(module) + } + + fun forEach(consumer: Consumer) = list.forEach(consumer) +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/utils/StringExtensions.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/utils/StringExtensions.kt new file mode 100644 index 0000000..0ed11fe --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/utils/StringExtensions.kt @@ -0,0 +1,56 @@ +package io.github.null2264.tsukumogami.core.utils + +fun String.parseCommandAndArguments(): List { + if (isBlank()) { + return emptyList() + } + + val result = mutableListOf() + val currentToken = StringBuilder() + var inQuotes = false + + for (char in this) { + when (char) { + '"' -> { + if (inQuotes) { + // Closing quote: add the accumulated token + result.add(currentToken.toString()) + currentToken.clear() + inQuotes = false + } else { + // Opening quote: + // If there's an existing token (e.g., word"another"), add it first + if (currentToken.isNotEmpty()) { + result.add(currentToken.toString()) + currentToken.clear() + } + inQuotes = true + } + } + ' ' -> { + if (inQuotes) { + // Space inside quotes: append it + currentToken.append(char) + } else { + // Space outside quotes: separator + if (currentToken.isNotEmpty()) { + result.add(currentToken.toString()) + currentToken.clear() + } + // Ignore multiple spaces between tokens + } + } + else -> { + // Any other character: append it + currentToken.append(char) + } + } + } + + // Add any remaining token after the loop (e.g., if the string doesn't end with a quote or space) + if (currentToken.isNotEmpty()) { + result.add(currentToken.toString()) + } + + return result +} From 690812b628e497d439fd7e7e8d1d4dd1245847df Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 21 May 2025 18:42:09 +0700 Subject: [PATCH 03/10] refactor: Move stuff around --- .../io/github/null2264/tsukumogami/core/Context.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt index 6f2ad19..2c878e6 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt @@ -4,11 +4,18 @@ import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.Message import dev.kord.rest.builder.message.AllowedMentionsBuilder import dev.kord.rest.builder.message.allowedMentions +import io.github.null2264.tsukumogami.core.commands.CommandHolder -class Context(private val bot: AbstractBot, private val message: Message, val prefix: String?, private val commandAndArguments: List?) { +class Context( + private val bot: AbstractBot, + private val message: Message, + val prefix: String?, + commandAndArguments: List?, +) { val author get() = message.author - val command get() = commandAndArguments?.get(0)?.let { bot.getCommand(it) } + val commandAndArguments: MutableList? = commandAndArguments?.toMutableList() + val command: CommandHolder? = this.commandAndArguments?.removeAt(0)?.let { bot.getCommand(it) } suspend fun send(content: String) = message.channel.createMessage(content) From 411e183e04c6b7a5436ad3067a77367301c20e07 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 22 May 2025 16:13:06 +0700 Subject: [PATCH 04/10] refactor: Completely changed the API and a working argument --- .../github/null2264/tsukumogami/bot/Main.kt | 9 ++- .../null2264/tsukumogami/bot/core/Bot.kt | 6 -- .../bot/core/module/DeveloperModule.kt | 26 ++++---- .../bot/core/module/GeneralModule.kt | 19 +++--- .../core/{AbstractBot.kt => Bot.kt} | 65 ++++++++++--------- .../tsukumogami/core/BotConfigurator.kt | 32 --------- .../null2264/tsukumogami/core/BotHolder.kt | 32 +++++++++ .../null2264/tsukumogami/core/Context.kt | 20 ++++-- .../core/annotation/TsukumogamiAnnotations.kt | 13 ++++ .../tsukumogami/core/commands/Arguments.kt | 8 ++- .../tsukumogami/core/commands/Command.kt | 20 ++++++ .../core/commands/CommandHolder.kt | 13 ---- .../core/commands/EmptyArguments.kt | 4 ++ .../tsukumogami/core/commands/Group.kt | 26 ++++++++ .../tsukumogami/core/commands/IGroup.kt | 45 +++++++++++++ .../core/{utils => ext}/StringExtensions.kt | 2 +- .../tsukumogami/core/module/BotModule.kt | 52 ++++----------- .../core/module/api/BotModuleApi.kt | 11 ++++ .../core/module/internal/Modules.kt | 40 ------------ 19 files changed, 244 insertions(+), 199 deletions(-) delete mode 100644 bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/Bot.kt rename core/src/main/kotlin/io/github/null2264/tsukumogami/core/{AbstractBot.kt => Bot.kt} (58%) delete mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotHolder.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/annotation/TsukumogamiAnnotations.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt delete mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/CommandHolder.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/EmptyArguments.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt rename core/src/main/kotlin/io/github/null2264/tsukumogami/core/{utils => ext}/StringExtensions.kt (97%) create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/api/BotModuleApi.kt delete mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/internal/Modules.kt diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt index d0518a5..1b52e0b 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt @@ -2,9 +2,8 @@ package io.github.null2264.tsukumogami.bot import co.touchlab.kermit.Logger import io.github.null2264.tsukumogami.bot.core.di.appModule -import io.github.null2264.tsukumogami.bot.core.module.DeveloperModule -import io.github.null2264.tsukumogami.bot.core.module.GeneralModule -import io.github.null2264.tsukumogami.bot.core.Bot +import io.github.null2264.tsukumogami.bot.core.module.generalModule +import io.github.null2264.tsukumogami.core.bot import org.koin.core.context.GlobalContext.startKoin suspend fun main() { @@ -13,13 +12,13 @@ suspend fun main() { modules(appModule) } - Bot { + bot { Logger.setTag("Tsukumogami") token = System.getenv("DISCORD_TOKEN") prefixes("src!", "mm!") // mm! for backwards compatibility - extensions(::DeveloperModule, ::GeneralModule) + modules(generalModule) }.start() } diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/Bot.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/Bot.kt deleted file mode 100644 index 0645b7a..0000000 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/Bot.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.null2264.tsukumogami.bot.core - -import io.github.null2264.tsukumogami.core.AbstractBot -import io.github.null2264.tsukumogami.core.BotConfigurator - -class Bot(block: BotConfigurator.() -> Unit = {}) : AbstractBot(block) diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/DeveloperModule.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/DeveloperModule.kt index 17a07d9..b8952fd 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/DeveloperModule.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/DeveloperModule.kt @@ -1,17 +1,13 @@ package io.github.null2264.tsukumogami.bot.core.module -import io.github.null2264.tsukumogami.core.commands.annotation.Command -import io.github.null2264.tsukumogami.core.Context -import io.github.null2264.tsukumogami.core.module.BotModule - -class DeveloperModule : BotModule("Developer", "Only for developers") { - - @Command( - name="poweroff", - description="Turn the bot off", - ) - private suspend fun shutdown(ctx: Context) { - ctx.reply("Shutting Down...", mentionsAuthor = true) - bot?.stop() - } -} +//class DeveloperModule : BotModule("Developer", "Only for developers") { +// +// @Command( +// name="poweroff", +// description="Turn the bot off", +// ) +// private suspend fun shutdown(ctx: Context) { +// ctx.reply("Shutting Down...", mentionsAuthor = true) +// bot?.stop() +// } +//} diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt index b3adb11..f96494d 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt @@ -1,23 +1,20 @@ package io.github.null2264.tsukumogami.bot.core.module -import dev.kord.core.entity.effectiveName -import io.github.null2264.tsukumogami.core.commands.annotation.Command -import io.github.null2264.tsukumogami.core.Context -import io.github.null2264.tsukumogami.core.module.BotModule +import io.github.null2264.tsukumogami.bot.core.module.arguments.TestArguments +import io.github.null2264.tsukumogami.core.module.api.botModules import kotlinx.datetime.Clock -class GeneralModule : BotModule("General", "idk") { - - @Command(description = "Ping the bot!") - private suspend fun ping(ctx: Context) { +val generalModule = botModules("General") { + commands("ping", description = "Ping the bot!") { ctx -> val startTime = Clock.System.now() ctx.typing() val endTime = Clock.System.now() ctx.send("Pong! ${endTime.toEpochMilliseconds() - startTime.toEpochMilliseconds()}ms") } - @Command("test") - private suspend fun differentName(ctx: Context) { - ctx.send("Hello World! ${ctx.author?.effectiveName}") + groups("group") { + commands("test", arguments = ::TestArguments) { ctx, args -> ctx.send("Hello world ${args.test}") } } + + commands("test", arguments = ::TestArguments) { ctx, args -> ctx.send("Hello world ${args.test}") } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt similarity index 58% rename from core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt rename to core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt index e04126f..86c7832 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/AbstractBot.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt @@ -8,45 +8,52 @@ import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.on import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent +import io.github.null2264.tsukumogami.core.commands.Command import io.github.null2264.tsukumogami.core.exceptions.CommandException import io.github.null2264.tsukumogami.core.exceptions.CommandNotFound import io.github.null2264.tsukumogami.core.module.BotModule -import io.github.null2264.tsukumogami.core.commands.CommandHolder -import io.github.null2264.tsukumogami.core.utils.parseCommandAndArguments +import io.github.null2264.tsukumogami.core.commands.IGroup +import io.github.null2264.tsukumogami.core.ext.parseCommandAndArguments import kotlin.reflect.full.callSuspend import kotlinx.coroutines.runBlocking -abstract class AbstractBot(configurator: BotConfigurator.() -> Unit) { +open class Bot internal constructor(): IGroup { - private val commands: Map - private val extensions: Map - private val prefixes: List - private val client: Kord + lateinit var client: Kord + private val modules = mutableMapOf() + private val _prefixes = mutableListOf() + val prefixes: List get() = _prefixes.toList() + internal lateinit var token: String + override val allCommands: MutableMap = mutableMapOf() - // TODO: Bind Bot and Kord to Koin - init { - val currentConfig = BotConfigurator() - currentConfig.apply(configurator) + fun addModule(module: BotModule) { + modules[module.name] = module.install(this) + } - extensions = mutableMapOf() - currentConfig.extensions.forEach { module -> - module.setup() - module.install(this, currentConfig) - extensions[module.name] = module + fun addPrefix(prefix: String) { + _prefixes.add(prefix) + } + + fun addCommand(command: Command) { + if (allCommands.containsKey(command.name)) { + throw IllegalStateException("Duplicate command: '${command.name}'") } - commands = currentConfig.commands - prefixes = currentConfig.prefixes + allCommands[command.name] = command + } - client = runBlocking { - Kord(currentConfig.token).apply { - on { onReady() } - - on { onMessage(this.message, this) } - } + fun removeCommand(command: Command) { + if (!allCommands.containsKey(command.name)) { + throw IllegalStateException("Command not found: '${command.name}'") } + allCommands.remove(command.name) } open suspend fun start() { + client = Kord(token).apply { + on { onReady() } + on { onMessage(this.message, this) } + } + client.login { @OptIn(PrivilegedIntent::class) intents += Intent.MessageContent @@ -57,11 +64,13 @@ abstract class AbstractBot(configurator: BotConfigurator.() -> Unit) { client.shutdown() } - fun getCommand(name: String) = commands[name] + fun getCommand(name: String?) = allCommands[name] private fun getContext(message: Message): Context { val candidate = message.content.parsePrefixCommandAndArguments() - return Context(this, message, candidate?.first, candidate?.second) + val context = Context(this, message, candidate?.first, candidate?.second) + context.command = getCommand(candidate?.second?.get(0)) + return context } open suspend fun onCommandError(context: Context, error: CommandException) { @@ -72,9 +81,7 @@ abstract class AbstractBot(configurator: BotConfigurator.() -> Unit) { val ctx = getContext(message) try { - ctx.command?.let { - it.callback.callSuspend(extensions[it.extension], ctx) - } ?: throw CommandNotFound() + ctx.command?.invoke(ctx) ?: throw CommandNotFound() } catch (e: CommandException) { onCommandError(ctx, e) } catch (e: Exception) { diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt deleted file mode 100644 index ce46057..0000000 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotConfigurator.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.null2264.tsukumogami.core - -import io.github.null2264.tsukumogami.core.module.internal.Modules -import io.github.null2264.tsukumogami.core.module.BotModule -import io.github.null2264.tsukumogami.core.commands.CommandHolder -import kotlin.reflect.KFunction - -class BotConfigurator internal constructor() { - - internal val commands = mutableMapOf() - internal val extensions = Modules() - internal val prefixes = mutableListOf() - var token: String = "" - - internal fun isExists(name: String?) = this.commands.containsKey(name) - - internal fun commands(command: CommandHolder, name: String? = null) { - this.commands[if (name.isNullOrEmpty()) command.name else name] = command - } - - fun extensions(vararg extensions: KFunction) { - this.extensions.initializeAndAddAll(extensions.toList()) - } - - fun prefixes(vararg prefixes: String) { - prefixes(prefixes.toList()) - } - - fun prefixes(prefixes: List) { - this.prefixes.addAll(prefixes) - } -} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotHolder.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotHolder.kt new file mode 100644 index 0000000..33c755c --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotHolder.kt @@ -0,0 +1,32 @@ +package io.github.null2264.tsukumogami.core + +import dev.kord.core.Kord +import io.github.null2264.tsukumogami.core.module.BotModule +import kotlin.reflect.KFunction + +class BotHolder internal constructor( + val bot: Bot +) { + + internal val prefixes = mutableListOf() + var token: String + get() = bot.token + set(value) { + bot.token = value + } + + fun modules(vararg modules: BotModule) { + modules.forEach(bot::addModule) + } + + fun prefixes(vararg prefixes: String) { + prefixes.forEach(bot::addPrefix) + } +} + +fun bot(clazz: KFunction = ::Bot, declaration: BotHolder.() -> Unit): Bot { + val bot = clazz.call() + val holder = BotHolder(bot) + declaration(holder) + return holder.bot +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt index 2c878e6..9dfe7ed 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt @@ -4,18 +4,26 @@ import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.Message import dev.kord.rest.builder.message.AllowedMentionsBuilder import dev.kord.rest.builder.message.allowedMentions -import io.github.null2264.tsukumogami.core.commands.CommandHolder +import io.github.null2264.tsukumogami.core.commands.Command class Context( - private val bot: AbstractBot, + private val bot: Bot, private val message: Message, + /** + * The prefix that used to invoke the command + */ val prefix: String?, - commandAndArguments: List?, + /** + * Potential command name and/or arguments + */ + val candidate: List?, ) { - val author get() = message.author - val commandAndArguments: MutableList? = commandAndArguments?.toMutableList() - val command: CommandHolder? = this.commandAndArguments?.removeAt(0)?.let { bot.getCommand(it) } + + /** + * The command that currently being invoked + */ + var command: Command? = null suspend fun send(content: String) = message.channel.createMessage(content) diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/annotation/TsukumogamiAnnotations.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/annotation/TsukumogamiAnnotations.kt new file mode 100644 index 0000000..c0b0b38 --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/annotation/TsukumogamiAnnotations.kt @@ -0,0 +1,13 @@ +package io.github.null2264.tsukumogami.core.annotation + +@RequiresOptIn(message = "Intended for internal usage. External usage is strongly discouraged.", level = RequiresOptIn.Level.ERROR) +@Retention(AnnotationRetention.BINARY) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.TYPEALIAS, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY, + AnnotationTarget.FIELD, + AnnotationTarget.CONSTRUCTOR, +) +annotation class TsukumogamiInternalApi diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt index 686be91..40984d3 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt @@ -2,7 +2,7 @@ package io.github.null2264.tsukumogami.core.commands import io.github.null2264.tsukumogami.core.commands.converters.Converter -open class Arguments { +abstract class Arguments { val args = mutableListOf>() fun args( @@ -13,4 +13,10 @@ open class Arguments { return converter } + + suspend fun parse(value: String) { + args.forEach { arg -> + arg.converter.parse(value) + } + } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt new file mode 100644 index 0000000..c770818 --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt @@ -0,0 +1,20 @@ +package io.github.null2264.tsukumogami.core.commands + +import io.github.null2264.tsukumogami.core.Context +import kotlin.reflect.KFunction + +open class Command( + val name: String, + val alias: Set, + val description: String, + private val arguments: KFunction, + private val handler: suspend (Context, Arguments) -> Unit, +) { + + open suspend fun invoke(context: Context) { + val parsedArguments = arguments.call() + // TODO: Don't hardcode this + parsedArguments.parse(context.candidate?.get(1) ?: "test") + handler.invoke(context, parsedArguments) + } +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/CommandHolder.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/CommandHolder.kt deleted file mode 100644 index 25c9a41..0000000 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/CommandHolder.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.null2264.tsukumogami.core.commands - -import kotlin.reflect.KFunction - -/** - * Class holding information about a command - */ -data class CommandHolder internal constructor( - val name: String, - val extension: String, - val description: String? = null, - val callback: KFunction<*>, -) diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/EmptyArguments.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/EmptyArguments.kt new file mode 100644 index 0000000..7c0d70c --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/EmptyArguments.kt @@ -0,0 +1,4 @@ +package io.github.null2264.tsukumogami.core.commands + +class EmptyArguments : Arguments() { +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt new file mode 100644 index 0000000..59bdc28 --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt @@ -0,0 +1,26 @@ +package io.github.null2264.tsukumogami.core.commands + +import io.github.null2264.tsukumogami.core.Context + +class Group( + name: String, + alias: Set, + description: String, +) : + IGroup, + Command( + name, + alias, + description, + ::EmptyArguments, + { _, _ -> /* No handler for group to match the behaviour of Discord's slash command */ }, + ) { + + override val allCommands: MutableMap = mutableMapOf() + + override suspend fun invoke(context: Context) { + val command = allCommands["TODO"] ?: return + context.command = command + command.invoke(context) + } +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt new file mode 100644 index 0000000..42d8f5a --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt @@ -0,0 +1,45 @@ +package io.github.null2264.tsukumogami.core.commands + +import io.github.null2264.tsukumogami.core.Context +import kotlin.reflect.KFunction + +interface IGroup { + + val allCommands: MutableMap + + private fun addCommand(command: Command) { + allCommands[command.name] = command + command.alias.forEach { allCommands[it] = command } + } + + fun commands( + name: String, + alias: Set = setOf(), + description: String = "", + handler: suspend (Context) -> Unit, + ) { + val command = Command(name, alias, description, ::EmptyArguments) { ctx, _ -> handler(ctx) } + addCommand(command) + } + + fun commands( + name: String, + alias: Set = setOf(), + description: String = "", + arguments: KFunction, + handler: suspend (Context, Args) -> Unit, + ) { + val command = Command(name, alias, description, arguments) { ctx, args -> handler(ctx, args as Args) } + addCommand(command) + } + + fun groups( + name: String, + alias: Set = setOf(), + description: String = "", + declaration: IGroup.() -> Unit, + ) { + val group = Group(name, alias, description) + addCommand(group) + } +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/utils/StringExtensions.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/StringExtensions.kt similarity index 97% rename from core/src/main/kotlin/io/github/null2264/tsukumogami/core/utils/StringExtensions.kt rename to core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/StringExtensions.kt index 0ed11fe..2bc9b79 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/utils/StringExtensions.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/StringExtensions.kt @@ -1,4 +1,4 @@ -package io.github.null2264.tsukumogami.core.utils +package io.github.null2264.tsukumogami.core.ext fun String.parseCommandAndArguments(): List { if (isBlank()) { diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt index ef96d0e..2605e3a 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt @@ -1,48 +1,20 @@ package io.github.null2264.tsukumogami.core.module -import co.touchlab.kermit.Logger -import io.github.null2264.tsukumogami.core.AbstractBot -import io.github.null2264.tsukumogami.core.commands.annotation.Command -import io.github.null2264.tsukumogami.core.BotConfigurator -import io.github.null2264.tsukumogami.core.commands.CommandHolder -import kotlin.reflect.jvm.isAccessible -import kotlin.reflect.jvm.kotlinFunction +import io.github.null2264.tsukumogami.core.Bot +import io.github.null2264.tsukumogami.core.commands.Command +import io.github.null2264.tsukumogami.core.commands.IGroup -abstract class BotModule(val name: String, val description: String? = null) { +class BotModule internal constructor(val name: String) : IGroup { - var bot: AbstractBot? = null - internal set + override val allCommands: MutableMap = mutableMapOf() - open fun setup() {} + internal fun install(bot: Bot): BotModule { + allCommands.values.forEach(bot::addCommand) + return this + } - internal fun install(bot: AbstractBot, configurator: BotConfigurator) { - this.bot = bot - - val methods = this::class.java.declaredMethods - for (method in methods) { - for (annotation in method.annotations) { - if (annotation !is Command) - continue - - configurator.apply { - val kMethod = method.kotlinFunction - if (isExists(kMethod?.name)) { - Logger.e { "Command already exists" } - return - } - kMethod?.let { - it.isAccessible = true - commands( - CommandHolder( - annotation.name.ifEmpty { it.name }, - name, - annotation.description.ifEmpty { description }, - it, - ) - ) - } - } - } - } + internal fun uninstall(bot: Bot): BotModule { + allCommands.values.forEach(bot::removeCommand) + return this } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/api/BotModuleApi.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/api/BotModuleApi.kt new file mode 100644 index 0000000..20f5f4e --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/api/BotModuleApi.kt @@ -0,0 +1,11 @@ +package io.github.null2264.tsukumogami.core.module.api + +import io.github.null2264.tsukumogami.core.module.BotModule + +typealias BotModuleDeclaration = BotModule.() -> Unit + +fun botModules(name: String, declaration: BotModuleDeclaration): BotModule { + val module = BotModule(name) + declaration(module) + return module +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/internal/Modules.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/internal/Modules.kt deleted file mode 100644 index 01ae3e2..0000000 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/internal/Modules.kt +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.null2264.tsukumogami.core.module.internal - -import io.github.null2264.tsukumogami.core.module.BotModule -import java.util.function.Consumer -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.full.isSubclassOf - -class Modules { - - private val list = mutableListOf() - - fun get(index: Int) = list[index] - - private fun KFunction.tryInitialize(): BotModule? { - val kClass = this.returnType.classifier as KClass<*> - if (!kClass.isSubclassOf(BotModule::class)) - return null - - return this.call() - } - - fun initializeAndAddAll(modules: List>) { - addAll(modules.mapNotNull { it.tryInitialize() }) - } - - fun addAll(modules: List) { - list.addAll(modules) - } - - fun add(module: KFunction) { - module.tryInitialize()?.let { add(it) } - } - - fun add(module: BotModule) { - list.add(module) - } - - fun forEach(consumer: Consumer) = list.forEach(consumer) -} From fce8afa2b2117395e57163add82b6263945df187 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 22 May 2025 17:36:18 +0700 Subject: [PATCH 05/10] fix: Dynamically parse arguments --- .../io/github/null2264/tsukumogami/core/Bot.kt | 4 ++-- .../core/{BotHolder.kt => BotBuilder.kt} | 8 +++----- .../github/null2264/tsukumogami/core/Context.kt | 3 ++- .../tsukumogami/core/commands/Arguments.kt | 16 +++++++++++++--- .../tsukumogami/core/commands/Command.kt | 3 +-- .../null2264/tsukumogami/core/commands/Group.kt | 3 ++- .../null2264/tsukumogami/core/commands/IGroup.kt | 1 + .../core/commands/converters/Converter.kt | 1 + .../commands/converters/impl/StringConverter.kt | 2 ++ 9 files changed, 27 insertions(+), 14 deletions(-) rename core/src/main/kotlin/io/github/null2264/tsukumogami/core/{BotHolder.kt => BotBuilder.kt} (69%) diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt index 86c7832..b0283cc 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt @@ -68,8 +68,8 @@ 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) - context.command = getCommand(candidate?.second?.get(0)) + val context = Context(this, message, candidate?.first, candidate?.second?.toMutableList()) + context.command = getCommand(context.candidate?.removeAt(0)) return context } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotHolder.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt similarity index 69% rename from core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotHolder.kt rename to core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt index 33c755c..be9cbb6 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotHolder.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt @@ -1,14 +1,12 @@ package io.github.null2264.tsukumogami.core -import dev.kord.core.Kord import io.github.null2264.tsukumogami.core.module.BotModule import kotlin.reflect.KFunction -class BotHolder internal constructor( +class BotBuilder internal constructor( val bot: Bot ) { - internal val prefixes = mutableListOf() var token: String get() = bot.token set(value) { @@ -24,9 +22,9 @@ class BotHolder internal constructor( } } -fun bot(clazz: KFunction = ::Bot, declaration: BotHolder.() -> Unit): Bot { +fun bot(clazz: KFunction = ::Bot, declaration: BotBuilder.() -> Unit): Bot { val bot = clazz.call() - val holder = BotHolder(bot) + val holder = BotBuilder(bot) declaration(holder) return holder.bot } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt index 9dfe7ed..dc849eb 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt @@ -16,8 +16,9 @@ class Context( /** * Potential command name and/or arguments */ - val candidate: List?, + val candidate: MutableList?, ) { + val author get() = message.author /** diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt index 40984d3..e7adb40 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt @@ -3,6 +3,7 @@ package io.github.null2264.tsukumogami.core.commands import io.github.null2264.tsukumogami.core.commands.converters.Converter abstract class Arguments { + val args = mutableListOf>() fun args( @@ -14,9 +15,18 @@ abstract class Arguments { return converter } - suspend fun parse(value: String) { - args.forEach { arg -> - arg.converter.parse(value) + suspend fun parse(values: List?) { + val currentValues = values?.toMutableList() + + run { + args.forEach { arg -> + val value = try { + currentValues?.removeAt(0) + } catch (e: Exception) { + null + } ?: return@run + arg.converter.parse(value) + } } } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt index c770818..8dda97c 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt @@ -13,8 +13,7 @@ open class Command( open suspend fun invoke(context: Context) { val parsedArguments = arguments.call() - // TODO: Don't hardcode this - parsedArguments.parse(context.candidate?.get(1) ?: "test") + parsedArguments.parse(context.candidate) handler.invoke(context, parsedArguments) } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt index 59bdc28..8ce1afe 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt @@ -19,7 +19,8 @@ class Group( override val allCommands: MutableMap = mutableMapOf() override suspend fun invoke(context: Context) { - val command = allCommands["TODO"] ?: return + val command = allCommands[context.candidate?.get(0)] ?: return + context.candidate?.removeAt(0) context.command = command command.invoke(context) } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt index 42d8f5a..9f87233 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt @@ -40,6 +40,7 @@ interface IGroup { declaration: IGroup.() -> Unit, ) { val group = Group(name, alias, description) + declaration(group) addCommand(group) } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt index cc9ed82..aa0fe6a 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt @@ -5,6 +5,7 @@ import io.github.null2264.tsukumogami.core.commands.Arguments import kotlin.reflect.KProperty abstract class Converter { + lateinit var argumentObj: Argument abstract var parsed: OutputType diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt index 723c52a..354817c 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt @@ -3,7 +3,9 @@ package io.github.null2264.tsukumogami.core.commands.converters.impl import io.github.null2264.tsukumogami.core.commands.converters.Converter class StringConverter : Converter() { + override var parsed: String = "" + override suspend fun parse(input: String): String { this.parsed = input return input From 04472f8bfa9637d5867fee8aec312074053b5e38 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 23 May 2025 06:19:02 +0700 Subject: [PATCH 06/10] feat: Improve arguments * Add UserConverter * Add a way to specify default value --- .../bot/core/module/GeneralModule.kt | 3 +- .../core/module/arguments/TestArguments.kt | 9 ++- .../github/null2264/tsukumogami/core/Bot.kt | 2 +- .../null2264/tsukumogami/core/Context.kt | 12 +++- .../tsukumogami/core/commands/Arguments.kt | 13 ++--- .../tsukumogami/core/commands/Command.kt | 2 +- .../tsukumogami/core/commands/Group.kt | 6 +- .../core/commands/converters/Converter.kt | 10 +++- .../converters/impl/StringConverter.kt | 3 +- .../commands/converters/impl/UserConverter.kt | 57 +++++++++++++++++++ .../core/commands/ext/ArgumentsExtensions.kt | 15 ++++- .../tsukumogami/core/ext/KordExtensions.kt | 6 ++ .../tsukumogami/core/module/BotModule.kt | 2 +- 13 files changed, 119 insertions(+), 21 deletions(-) create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/UserConverter.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/KordExtensions.kt diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt index f96494d..0c3bd10 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt @@ -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}") } diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/arguments/TestArguments.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/arguments/TestArguments.kt index e252549..abc529f 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/arguments/TestArguments.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/arguments/TestArguments.kt @@ -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") } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt index b0283cc..418f46e 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt @@ -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 } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt index dc849eb..7c8cac4 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt @@ -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?, ) { + /** + * 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 { diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt index e7adb40..d593c69 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt @@ -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?) { - 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) } } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt index 8dda97c..382e66a 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt @@ -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) } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt index 8ce1afe..af7897d 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt @@ -19,9 +19,9 @@ class Group( override val allCommands: MutableMap = 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) } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt index aa0fe6a..2824df3 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt @@ -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 { 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 } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt index 354817c..2a8a40d 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt @@ -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() { 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 } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/UserConverter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/UserConverter.kt new file mode 100644 index 0000000..b88d27e --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/UserConverter.kt @@ -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() { + + 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) + } + } + } + } +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/ext/ArgumentsExtensions.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/ext/ArgumentsExtensions.kt index c48824b..c603b45 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/ext/ArgumentsExtensions.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/ext/ArgumentsExtensions.kt @@ -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.() -> Unit = {}): Converter { + val converter = StringConverter() + declaration(converter) + return args(name, converter) +} + +fun Arguments.user(name: String, declaration: Converter.() -> Unit = {}): Converter { + val converter = UserConverter() + declaration(converter) + return args(name, converter) +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/KordExtensions.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/KordExtensions.kt new file mode 100644 index 0000000..728b106 --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/KordExtensions.kt @@ -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 diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt index 2605e3a..de5571c 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt @@ -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 = mutableMapOf() From a62d86563ab687782867e5a4e1dc53545f704f4a Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 23 May 2025 15:07:09 +0700 Subject: [PATCH 07/10] refactor: Adjust how command is invoked --- .../bot/core/module/GeneralModule.kt | 2 +- .../github/null2264/tsukumogami/core/Bot.kt | 46 ++++++++++++------- .../null2264/tsukumogami/core/Context.kt | 31 ++++++++----- .../tsukumogami/core/commands/Arguments.kt | 2 +- .../tsukumogami/core/commands/Group.kt | 10 ++-- .../tsukumogami/core/commands/IGroup.kt | 1 - 6 files changed, 57 insertions(+), 35 deletions(-) diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt index 0c3bd10..0591074 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt @@ -6,7 +6,7 @@ import io.github.null2264.tsukumogami.core.module.api.botModules import kotlinx.datetime.Clock val generalModule = botModules("General") { - commands("ping", description = "Ping the bot!") { ctx -> + commands("ping", alias = setOf("p"), description = "Ping the bot!") { ctx -> val startTime = Clock.System.now() ctx.typing() val endTime = Clock.System.now() diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt index 418f46e..a68e8f0 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt @@ -34,18 +34,28 @@ open class Bot internal constructor(): IGroup { _prefixes.add(prefix) } - fun addCommand(command: Command) { - if (allCommands.containsKey(command.name)) { + private fun addCommand(name: String, command: Command) { + if (allCommands.containsKey(name)) { throw IllegalStateException("Duplicate command: '${command.name}'") } allCommands[command.name] = command } - fun removeCommand(command: Command) { - if (!allCommands.containsKey(command.name)) { - throw IllegalStateException("Command not found: '${command.name}'") + fun addCommand(command: Command) { + addCommand(command.name, command) + command.alias.forEach { addCommand(it, command) } + } + + private fun removeCommand(name: String) { + if (!allCommands.containsKey(name)) { + throw IllegalStateException("Command not found: '${name}'") } - allCommands.remove(command.name) + allCommands.remove(name) + } + + fun removeCommand(command: Command) { + removeCommand(command.name) + command.alias.forEach { removeCommand(it) } } open suspend fun start() { @@ -67,9 +77,10 @@ open class Bot internal constructor(): IGroup { fun getCommand(name: String?) = allCommands[name] 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.pullCandidateOrNull()) + val parsed = message.content.parsePrefixAndCommand() + val context = Context(this, message, parsed?.first) + context.command = getCommand(parsed?.second) + if (context.command != null) context.invokedWith = parsed?.second return context } @@ -99,17 +110,18 @@ open class Bot internal constructor(): IGroup { Logger.i { "Online! ${client.getSelf().username}" } } - private fun String.parsePrefixCommandAndArguments(): Pair>? { + private fun String.parsePrefixAndCommand(): Pair? { if (this.isBlank()) return null - var ret: Pair>? = null + var ret: Pair? = null - prefixes.forEach { - if (this.substring(0, it.length) == it) { - val prefix = this.substring(0, it.length) - val cleanPrompt = this.substring(it.length) - ret = Pair(prefix, cleanPrompt.parseCommandAndArguments()) - return@forEach + run { + prefixes.forEach { candidate -> + val prefix = this.substring(0, candidate.length) + if (prefix != candidate) { return@forEach } + val command = this.drop(candidate.length).substringBefore(' ') + ret = prefix to command + return@run } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt index 7c8cac4..bb71633 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt @@ -8,34 +8,30 @@ import io.github.null2264.tsukumogami.core.commands.Command class Context( val bot: Bot, - private val message: Message, + val message: Message, /** * The prefix that used to invoke the command */ val prefix: String?, - /** - * Potential command name and/or arguments - */ - val candidate: MutableList?, ) { /** * The user that invoked the command + * + * Alias to [Message.author] */ val author get() = message.author + /** + * The text that invoked the command + */ + var invokedWith: String? = null + /** * The command that currently being invoked */ 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 { @@ -48,4 +44,15 @@ class Context( } suspend fun typing() = message.channel.type() + + fun parseArguments(): MutableList? = + if (prefix != null && invokedWith != null) { + message.content + .substringAfter("$prefix$invokedWith") + .trim() + .split(' ') + .toMutableList() + } else { + null + } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt index d593c69..860e23e 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt @@ -17,7 +17,7 @@ abstract class Arguments { } suspend fun parse(context: Context) { - val currentValues = context.candidate?.toMutableList() + val currentValues = context.parseArguments() run { args.forEach { arg -> diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt index af7897d..0c41d48 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt @@ -19,9 +19,13 @@ class Group( override val allCommands: MutableMap = mutableMapOf() override suspend fun invoke(context: Context) { - val command = allCommands[context.getCandidateOrNull()] - context.pullCandidateIf { command != null } ?: return - context.command = command!! + val subcommandName = context.message.content + .substringAfter("${context.prefix}${context.invokedWith}") + .trim() + .substringBefore(' ') + val command = allCommands[subcommandName] ?: return + context.invokedWith += " $subcommandName" + context.command = command command.invoke(context) } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt index 9f87233..dcc1640 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt @@ -9,7 +9,6 @@ interface IGroup { private fun addCommand(command: Command) { allCommands[command.name] = command - command.alias.forEach { allCommands[it] = command } } fun commands( From 513959d4a7fcf0d1d5de4aae22901689d078f5c7 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 23 May 2025 15:12:34 +0700 Subject: [PATCH 08/10] chore: Testing group inside group --- .../null2264/tsukumogami/bot/core/module/GeneralModule.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt index 0591074..60b6ff4 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt @@ -14,7 +14,10 @@ val generalModule = botModules("General") { } groups("group") { - commands("test", arguments = ::Test2Arguments) { ctx, args -> ctx.send("Hello world ${args.user}") } + commands("test", arguments = ::Test2Arguments) { ctx, args -> ctx.reply("Hello world ${args.user}") } + groups("group") { + commands("test", arguments = ::TestArguments) { ctx, args -> ctx.reply("Hello world ${args.test}") } + } } commands("test", arguments = ::TestArguments) { ctx, args -> ctx.send("Hello world ${args.test}") } From b22cf439ad74694a1b6f7b8de0c16ca29ba5ec50 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 23 May 2025 15:29:16 +0700 Subject: [PATCH 09/10] fix: Fix alias not being registered for group subcommands --- .../bot/core/module/GeneralModule.kt | 4 +++- .../tsukumogami/core/commands/Command.kt | 17 +++++++++++++++++ .../null2264/tsukumogami/core/commands/Group.kt | 7 +++++++ .../tsukumogami/core/commands/IGroup.kt | 12 ++++++++---- .../tsukumogami/core/module/BotModule.kt | 11 +++++++++-- 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt index 60b6ff4..3b0176e 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/core/module/GeneralModule.kt @@ -16,7 +16,9 @@ val generalModule = botModules("General") { groups("group") { commands("test", arguments = ::Test2Arguments) { ctx, args -> ctx.reply("Hello world ${args.user}") } groups("group") { - commands("test", arguments = ::TestArguments) { ctx, args -> ctx.reply("Hello world ${args.test}") } + commands("test", alias = setOf("t")) { ctx -> + ctx.reply("Hello world ${ctx.command?.module?.name}") + } } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt index 382e66a..d0e66dd 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Command.kt @@ -1,6 +1,7 @@ package io.github.null2264.tsukumogami.core.commands import io.github.null2264.tsukumogami.core.Context +import io.github.null2264.tsukumogami.core.module.BotModule import kotlin.reflect.KFunction open class Command( @@ -11,6 +12,22 @@ open class Command( private val handler: suspend (Context, Arguments) -> Unit, ) { + /** + * Return parent command if this command is a subcommand otherwise it returns null + */ + var parent: Command? = null + + /** + * The module this command belong to + */ + var module: BotModule? = null + get() { + if (parent != null) { + field = parent?.module + } + return field + } + open suspend fun invoke(context: Context) { val parsedArguments = arguments.call() parsedArguments.parse(context) diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt index 0c41d48..30cde51 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Group.kt @@ -1,7 +1,9 @@ package io.github.null2264.tsukumogami.core.commands import io.github.null2264.tsukumogami.core.Context +import io.github.null2264.tsukumogami.core.annotation.TsukumogamiInternalApi +@OptIn(TsukumogamiInternalApi::class) class Group( name: String, alias: Set, @@ -18,6 +20,11 @@ class Group( override val allCommands: MutableMap = mutableMapOf() + override fun _addCommand(command: Command) { + command.parent = this + super._addCommand(command) + } + override suspend fun invoke(context: Context) { val subcommandName = context.message.content .substringAfter("${context.prefix}${context.invokedWith}") diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt index dcc1640..78d06c7 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/IGroup.kt @@ -1,14 +1,18 @@ package io.github.null2264.tsukumogami.core.commands import io.github.null2264.tsukumogami.core.Context +import io.github.null2264.tsukumogami.core.annotation.TsukumogamiInternalApi import kotlin.reflect.KFunction +@OptIn(TsukumogamiInternalApi::class) interface IGroup { val allCommands: MutableMap - private fun addCommand(command: Command) { + @TsukumogamiInternalApi + fun _addCommand(command: Command) { allCommands[command.name] = command + command.alias.forEach { allCommands[it] = command } } fun commands( @@ -18,7 +22,7 @@ interface IGroup { handler: suspend (Context) -> Unit, ) { val command = Command(name, alias, description, ::EmptyArguments) { ctx, _ -> handler(ctx) } - addCommand(command) + _addCommand(command) } fun commands( @@ -29,7 +33,7 @@ interface IGroup { handler: suspend (Context, Args) -> Unit, ) { val command = Command(name, alias, description, arguments) { ctx, args -> handler(ctx, args as Args) } - addCommand(command) + _addCommand(command) } fun groups( @@ -40,6 +44,6 @@ interface IGroup { ) { val group = Group(name, alias, description) declaration(group) - addCommand(group) + _addCommand(group) } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt index de5571c..bdf2b85 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/module/BotModule.kt @@ -1,20 +1,27 @@ package io.github.null2264.tsukumogami.core.module import io.github.null2264.tsukumogami.core.Bot +import io.github.null2264.tsukumogami.core.annotation.TsukumogamiInternalApi import io.github.null2264.tsukumogami.core.commands.Command import io.github.null2264.tsukumogami.core.commands.IGroup +@OptIn(TsukumogamiInternalApi::class) open class BotModule constructor(val name: String) : IGroup { override val allCommands: MutableMap = mutableMapOf() + override fun _addCommand(command: Command) { + command.module = this + super._addCommand(command) + } + internal fun install(bot: Bot): BotModule { - allCommands.values.forEach(bot::addCommand) + allCommands.values.distinctBy { it.name }.forEach(bot::addCommand) return this } internal fun uninstall(bot: Bot): BotModule { - allCommands.values.forEach(bot::removeCommand) + allCommands.values.distinctBy { it.name }.forEach(bot::removeCommand) return this } } From 26e5ba9e85b840da02c68227ed6656d73fb7fb8f Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 24 May 2025 08:38:26 +0700 Subject: [PATCH 10/10] feat: Koin integration --- .../github/null2264/tsukumogami/bot/Main.kt | 4 - .../github/null2264/tsukumogami/core/Bot.kt | 11 +-- .../null2264/tsukumogami/core/BotBuilder.kt | 30 +++++++- .../core/commands/EmptyArguments.kt | 3 +- .../tsukumogami/core/ext/KoinExtensions.kt | 17 +++++ .../core/koin/TsukumogamiKoinComponent.kt | 8 ++ .../core/koin/TsukumogamiKoinContext.kt | 75 +++++++++++++++++++ 7 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/KoinExtensions.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/koin/TsukumogamiKoinComponent.kt create mode 100644 core/src/main/kotlin/io/github/null2264/tsukumogami/core/koin/TsukumogamiKoinContext.kt diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt index 1b52e0b..0b1b878 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt @@ -8,10 +8,6 @@ import org.koin.core.context.GlobalContext.startKoin suspend fun main() { - startKoin { - modules(appModule) - } - bot { Logger.setTag("Tsukumogami") diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt index a68e8f0..82c03b8 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt @@ -14,12 +14,14 @@ import io.github.null2264.tsukumogami.core.exceptions.CommandNotFound import io.github.null2264.tsukumogami.core.module.BotModule import io.github.null2264.tsukumogami.core.commands.IGroup import io.github.null2264.tsukumogami.core.ext.parseCommandAndArguments +import io.github.null2264.tsukumogami.core.koin.TsukumogamiKoinComponent import kotlin.reflect.full.callSuspend import kotlinx.coroutines.runBlocking +import org.koin.core.component.inject -open class Bot internal constructor(): IGroup { +open class Bot internal constructor(): IGroup, TsukumogamiKoinComponent { - lateinit var client: Kord + val client: Kord by inject() private val modules = mutableMapOf() private val _prefixes = mutableListOf() val prefixes: List get() = _prefixes.toList() @@ -59,11 +61,6 @@ open class Bot internal constructor(): IGroup { } open suspend fun start() { - client = Kord(token).apply { - on { onReady() } - on { onMessage(this.message, this) } - } - client.login { @OptIn(PrivilegedIntent::class) intents += Intent.MessageContent diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt index be9cbb6..51fc4f7 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt @@ -1,17 +1,28 @@ package io.github.null2264.tsukumogami.core +import dev.kord.core.Kord +import dev.kord.core.event.gateway.ReadyEvent +import dev.kord.core.event.message.MessageCreateEvent +import dev.kord.core.on +import io.github.null2264.tsukumogami.core.ext.loadModule +import io.github.null2264.tsukumogami.core.koin.TsukumogamiKoinContext import io.github.null2264.tsukumogami.core.module.BotModule import kotlin.reflect.KFunction +import kotlinx.coroutines.runBlocking +import org.koin.dsl.bind class BotBuilder internal constructor( val bot: Bot ) { - var token: String - get() = bot.token - set(value) { - bot.token = value + var token: String = "" + + var kordBuilder: suspend (String) -> Kord = { token -> + Kord(token).apply { + on { bot.onReady() } + on { bot.onMessage(this.message, this) } } + } fun modules(vararg modules: BotModule) { modules.forEach(bot::addModule) @@ -23,8 +34,19 @@ class BotBuilder internal constructor( } fun bot(clazz: KFunction = ::Bot, declaration: BotBuilder.() -> Unit): Bot { + if (TsukumogamiKoinContext.getOrNull() == null) + TsukumogamiKoinContext.startKoin { + } + val bot = clazz.call() val holder = BotBuilder(bot) declaration(holder) + + val kord = runBlocking { + holder.kordBuilder(holder.token) + } + loadModule { single { kord } bind Kord::class } + loadModule { single { bot } bind Bot::class } + return holder.bot } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/EmptyArguments.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/EmptyArguments.kt index 7c0d70c..92140a3 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/EmptyArguments.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/EmptyArguments.kt @@ -1,4 +1,3 @@ package io.github.null2264.tsukumogami.core.commands -class EmptyArguments : Arguments() { -} +class EmptyArguments : Arguments() diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/KoinExtensions.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/KoinExtensions.kt new file mode 100644 index 0000000..b509a2b --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/ext/KoinExtensions.kt @@ -0,0 +1,17 @@ +package io.github.null2264.tsukumogami.core.ext + +import io.github.null2264.tsukumogami.core.koin.TsukumogamiKoinContext +import org.koin.core.module.Module +import org.koin.dsl.ModuleDeclaration +import org.koin.dsl.module + +fun loadModule( + createdAtStart: Boolean = false, + moduleDeclaration: ModuleDeclaration, +): Module { + val moduleObj = module(createdAtStart, moduleDeclaration) + + TsukumogamiKoinContext.loadKoinModules(moduleObj) + + return moduleObj +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/koin/TsukumogamiKoinComponent.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/koin/TsukumogamiKoinComponent.kt new file mode 100644 index 0000000..525d75c --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/koin/TsukumogamiKoinComponent.kt @@ -0,0 +1,8 @@ +package io.github.null2264.tsukumogami.core.koin + +import org.koin.core.Koin +import org.koin.core.component.KoinComponent + +interface TsukumogamiKoinComponent : KoinComponent { + override fun getKoin(): Koin = TsukumogamiKoinContext.get() +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/koin/TsukumogamiKoinContext.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/koin/TsukumogamiKoinContext.kt new file mode 100644 index 0000000..4ff3ad1 --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/koin/TsukumogamiKoinContext.kt @@ -0,0 +1,75 @@ +package io.github.null2264.tsukumogami.core.koin + +import org.koin.core.Koin +import org.koin.core.KoinApplication +import org.koin.core.context.KoinContext +import org.koin.core.error.KoinApplicationAlreadyStartedException +import org.koin.core.module.Module +import org.koin.dsl.KoinAppDeclaration + +/** + * The [KoinContext] for Tsukumogami. + * + * To use this context, implement [TsukumogamiKoinComponent]. + * + * @see org.koin.core.context.GlobalContext + */ +object TsukumogamiKoinContext : KoinContext { + /** The current [Koin] instance. */ + private var koin: Koin? = null + + /** The current [KoinApplication]. */ + private var koinApp: KoinApplication? = null + + override fun get(): Koin = koin ?: error("KoinApplication has not been started") + override fun getOrNull(): Koin? = koin + public fun getKoinApplicationOrNull(): KoinApplication? = koinApp + + private fun register(koinApplication: KoinApplication) { + if (koin != null) { + throw KoinApplicationAlreadyStartedException("KoinApplication has already been started") + } + + koinApp = koinApplication + koin = koinApplication.koin + } + + override fun loadKoinModules(module: Module, createEagerInstances: Boolean) { + get().loadModules(listOf(module), createEagerInstances = createEagerInstances) + } + + override fun loadKoinModules(modules: List, createEagerInstances: Boolean) { + get().loadModules(modules, createEagerInstances = createEagerInstances) + } + + override fun startKoin(koinApplication: KoinApplication): KoinApplication = synchronized(this) { + register(koinApplication) + koinApplication.createEagerInstances() + koinApplication.allowOverride(true) + + return koinApplication + } + + override fun startKoin(appDeclaration: KoinAppDeclaration): KoinApplication = synchronized(this) { + val koinApplication = KoinApplication.init() + + register(koinApplication) + appDeclaration(koinApplication) + koinApplication.createEagerInstances() + + return koinApplication + } + + override fun stopKoin() { + koin?.close() + koin = null + } + + override fun unloadKoinModules(module: Module) { + get().unloadModules(listOf(module)) + } + + override fun unloadKoinModules(modules: List) { + get().unloadModules(modules) + } +}