Compare commits
10 commits
135cbd31a5
...
26e5ba9e85
Author | SHA1 | Date | |
---|---|---|---|
26e5ba9e85 | |||
b22cf439ad | |||
513959d4a7 | |||
a62d86563a | |||
04472f8bfa | |||
fce8afa2b2 | |||
411e183e04 | |||
690812b628 | |||
c901eb2f66 | |||
1c20e05066 |
30 changed files with 744 additions and 243 deletions
|
@ -2,24 +2,19 @@ package io.github.null2264.tsukumogami.bot
|
||||||
|
|
||||||
import co.touchlab.kermit.Logger
|
import co.touchlab.kermit.Logger
|
||||||
import io.github.null2264.tsukumogami.bot.core.di.appModule
|
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.module.GeneralModule
|
import io.github.null2264.tsukumogami.core.bot
|
||||||
import io.github.null2264.tsukumogami.bot.core.Bot
|
|
||||||
import org.koin.core.context.GlobalContext.startKoin
|
import org.koin.core.context.GlobalContext.startKoin
|
||||||
|
|
||||||
suspend fun main() {
|
suspend fun main() {
|
||||||
|
|
||||||
startKoin {
|
bot {
|
||||||
modules(appModule)
|
|
||||||
}
|
|
||||||
|
|
||||||
Bot {
|
|
||||||
Logger.setTag("Tsukumogami")
|
Logger.setTag("Tsukumogami")
|
||||||
|
|
||||||
token = System.getenv("DISCORD_TOKEN")
|
token = System.getenv("DISCORD_TOKEN")
|
||||||
|
|
||||||
prefixes("src!", "mm!") // mm! for backwards compatibility
|
prefixes("src!", "mm!") // mm! for backwards compatibility
|
||||||
|
|
||||||
extensions(::DeveloperModule, ::GeneralModule)
|
modules(generalModule)
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
|
@ -1,17 +1,13 @@
|
||||||
package io.github.null2264.tsukumogami.bot.core.module
|
package io.github.null2264.tsukumogami.bot.core.module
|
||||||
|
|
||||||
import io.github.null2264.tsukumogami.core.module.annotation.Command
|
//class DeveloperModule : BotModule("Developer", "Only for developers") {
|
||||||
import io.github.null2264.tsukumogami.core.Context
|
//
|
||||||
import io.github.null2264.tsukumogami.core.module.BotModule
|
// @Command(
|
||||||
|
// name="poweroff",
|
||||||
class DeveloperModule : BotModule("Developer", "Only for developers") {
|
// description="Turn the bot off",
|
||||||
|
// )
|
||||||
@Command(
|
// private suspend fun shutdown(ctx: Context) {
|
||||||
name="poweroff",
|
// ctx.reply("Shutting Down...", mentionsAuthor = true)
|
||||||
description="Turn the bot off",
|
// bot?.stop()
|
||||||
)
|
// }
|
||||||
private suspend fun shutdown(ctx: Context) {
|
//}
|
||||||
ctx.reply("Shutting Down...", mentionsAuthor = true)
|
|
||||||
bot?.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
package io.github.null2264.tsukumogami.bot.core.module
|
package io.github.null2264.tsukumogami.bot.core.module
|
||||||
|
|
||||||
import dev.kord.core.entity.effectiveName
|
import io.github.null2264.tsukumogami.bot.core.module.arguments.Test2Arguments
|
||||||
import io.github.null2264.tsukumogami.core.module.annotation.Command
|
import io.github.null2264.tsukumogami.bot.core.module.arguments.TestArguments
|
||||||
import io.github.null2264.tsukumogami.core.Context
|
import io.github.null2264.tsukumogami.core.module.api.botModules
|
||||||
import io.github.null2264.tsukumogami.core.module.BotModule
|
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
|
||||||
class GeneralModule : BotModule("General", "idk") {
|
val generalModule = botModules("General") {
|
||||||
|
commands("ping", alias = setOf("p"), description = "Ping the bot!") { ctx ->
|
||||||
@Command(description = "Ping the bot!")
|
|
||||||
private suspend fun ping(ctx: Context) {
|
|
||||||
val startTime = Clock.System.now()
|
val startTime = Clock.System.now()
|
||||||
ctx.typing()
|
ctx.typing()
|
||||||
val endTime = Clock.System.now()
|
val endTime = Clock.System.now()
|
||||||
ctx.send("Pong! ${endTime.toEpochMilliseconds() - startTime.toEpochMilliseconds()}ms")
|
ctx.send("Pong! ${endTime.toEpochMilliseconds() - startTime.toEpochMilliseconds()}ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command("test")
|
groups("group") {
|
||||||
private suspend fun differentName(ctx: Context) {
|
commands("test", arguments = ::Test2Arguments) { ctx, args -> ctx.reply("Hello world ${args.user}") }
|
||||||
ctx.send("Hello World! ${ctx.author?.effectiveName}")
|
groups("group") {
|
||||||
|
commands("test", alias = setOf("t")) { ctx ->
|
||||||
|
ctx.reply("Hello world ${ctx.command?.module?.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commands("test", arguments = ::TestArguments) { ctx, args -> ctx.send("Hello world ${args.test}") }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
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") {
|
||||||
|
default("Lmao")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Test2Arguments : Arguments() {
|
||||||
|
val user by user("User")
|
||||||
|
}
|
|
@ -1,108 +0,0 @@
|
||||||
package io.github.null2264.tsukumogami.core
|
|
||||||
|
|
||||||
import co.touchlab.kermit.Logger
|
|
||||||
import dev.kord.core.Kord
|
|
||||||
import dev.kord.core.entity.Message
|
|
||||||
import dev.kord.core.event.gateway.ReadyEvent
|
|
||||||
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.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 kotlin.reflect.full.callSuspend
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
abstract class AbstractBot(configurator: BotConfigurator.() -> Unit) {
|
|
||||||
|
|
||||||
private val commands: Map<String, Command>
|
|
||||||
private val extensions: Map<String, BotModule>
|
|
||||||
private val prefixes: List<String>
|
|
||||||
private val client: Kord
|
|
||||||
|
|
||||||
init {
|
|
||||||
val currentConfig = BotConfigurator()
|
|
||||||
currentConfig.apply(configurator)
|
|
||||||
|
|
||||||
extensions = mutableMapOf()
|
|
||||||
currentConfig.extensions.forEach {
|
|
||||||
val module = it.call()
|
|
||||||
module.setup()
|
|
||||||
module.install(this, currentConfig)
|
|
||||||
extensions[module.name] = module
|
|
||||||
}
|
|
||||||
commands = currentConfig.commands
|
|
||||||
prefixes = currentConfig.prefixes
|
|
||||||
|
|
||||||
client = runBlocking {
|
|
||||||
Kord(currentConfig.token).apply {
|
|
||||||
on<ReadyEvent> { onReady() }
|
|
||||||
|
|
||||||
on<MessageCreateEvent> { onMessage(this.message, this) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun start() {
|
|
||||||
client.login {
|
|
||||||
@OptIn(PrivilegedIntent::class)
|
|
||||||
intents += Intent.MessageContent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun stop() {
|
|
||||||
client.shutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCommand(name: String) = commands[name]
|
|
||||||
|
|
||||||
private fun getContext(message: Message): Context {
|
|
||||||
val candidate = message.content.hasPrefix()
|
|
||||||
return Context(this, message, candidate?.first, candidate?.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun onCommandError(context: Context, error: CommandException) {
|
|
||||||
context.send(error.message!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun processCommand(message: Message) {
|
|
||||||
val ctx = getContext(message)
|
|
||||||
|
|
||||||
try {
|
|
||||||
ctx.command?.let {
|
|
||||||
it.callback.callSuspend(extensions[it.extension], ctx)
|
|
||||||
} ?: throw CommandNotFound()
|
|
||||||
} catch (e: CommandException) {
|
|
||||||
onCommandError(ctx, e)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Logger.e(e) { "Something went wrong while trying to process command '${ctx.command?.name}'" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun onMessage(message: Message, event: MessageCreateEvent) {
|
|
||||||
if (message.author?.isBot != false) return
|
|
||||||
|
|
||||||
processCommand(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun onReady() {
|
|
||||||
Logger.i { "Online! ${client.getSelf().username}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.hasPrefix(): Pair<String, String>? {
|
|
||||||
if (this.isBlank()) return null
|
|
||||||
|
|
||||||
var ret: Pair<String, String>? = null
|
|
||||||
|
|
||||||
prefixes.forEach {
|
|
||||||
if (this.substring(0, it.length) == it) {
|
|
||||||
ret = Pair(this.substring(0, it.length), this.substring(it.length).split(" ").first())
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
127
core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt
Normal file
127
core/src/main/kotlin/io/github/null2264/tsukumogami/core/Bot.kt
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package io.github.null2264.tsukumogami.core
|
||||||
|
|
||||||
|
import co.touchlab.kermit.Logger
|
||||||
|
import dev.kord.core.Kord
|
||||||
|
import dev.kord.core.entity.Message
|
||||||
|
import dev.kord.core.event.gateway.ReadyEvent
|
||||||
|
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.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, TsukumogamiKoinComponent {
|
||||||
|
|
||||||
|
val client: Kord by inject()
|
||||||
|
private val modules = mutableMapOf<String, BotModule>()
|
||||||
|
private val _prefixes = mutableListOf<String>()
|
||||||
|
val prefixes: List<String> get() = _prefixes.toList()
|
||||||
|
internal lateinit var token: String
|
||||||
|
override val allCommands: MutableMap<String, Command> = mutableMapOf()
|
||||||
|
|
||||||
|
fun addModule(module: BotModule) {
|
||||||
|
modules[module.name] = module.install(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addPrefix(prefix: String) {
|
||||||
|
_prefixes.add(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addCommand(name: String, command: Command) {
|
||||||
|
if (allCommands.containsKey(name)) {
|
||||||
|
throw IllegalStateException("Duplicate command: '${command.name}'")
|
||||||
|
}
|
||||||
|
allCommands[command.name] = command
|
||||||
|
}
|
||||||
|
|
||||||
|
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(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeCommand(command: Command) {
|
||||||
|
removeCommand(command.name)
|
||||||
|
command.alias.forEach { removeCommand(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun start() {
|
||||||
|
client.login {
|
||||||
|
@OptIn(PrivilegedIntent::class)
|
||||||
|
intents += Intent.MessageContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun stop() {
|
||||||
|
client.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCommand(name: String?) = allCommands[name]
|
||||||
|
|
||||||
|
private fun getContext(message: Message): Context {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun onCommandError(context: Context, error: CommandException) {
|
||||||
|
context.send(error.message!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun processCommand(message: Message) {
|
||||||
|
val ctx = getContext(message)
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.command?.invoke(ctx) ?: throw CommandNotFound()
|
||||||
|
} catch (e: CommandException) {
|
||||||
|
onCommandError(ctx, e)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.e(e) { "Something went wrong while trying to process command '${ctx.command?.name}'" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun onMessage(message: Message, event: MessageCreateEvent) {
|
||||||
|
if (message.author?.isBot != false) return
|
||||||
|
|
||||||
|
processCommand(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun onReady() {
|
||||||
|
Logger.i { "Online! ${client.getSelf().username}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.parsePrefixAndCommand(): Pair<String, String>? {
|
||||||
|
if (this.isBlank()) return null
|
||||||
|
|
||||||
|
var ret: Pair<String, String>? = null
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
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 = ""
|
||||||
|
|
||||||
|
var kordBuilder: suspend (String) -> Kord = { token ->
|
||||||
|
Kord(token).apply {
|
||||||
|
on<ReadyEvent> { bot.onReady() }
|
||||||
|
on<MessageCreateEvent> { bot.onMessage(this.message, this) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun modules(vararg modules: BotModule) {
|
||||||
|
modules.forEach(bot::addModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun prefixes(vararg prefixes: String) {
|
||||||
|
prefixes.forEach(bot::addPrefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bot(clazz: KFunction<Bot> = ::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
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
package io.github.null2264.tsukumogami.core
|
|
||||||
|
|
||||||
import io.github.null2264.tsukumogami.core.module.BotModule
|
|
||||||
import io.github.null2264.tsukumogami.core.module.Command
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KFunction
|
|
||||||
import kotlin.reflect.full.isSubclassOf
|
|
||||||
|
|
||||||
class BotConfigurator internal constructor() {
|
|
||||||
|
|
||||||
internal val commands = mutableMapOf<String, Command>()
|
|
||||||
internal val extensions = mutableListOf<KFunction<BotModule>>()
|
|
||||||
internal val prefixes = mutableListOf<String>()
|
|
||||||
var token: String = ""
|
|
||||||
|
|
||||||
internal fun isExists(name: String?) = this.commands.containsKey(name)
|
|
||||||
|
|
||||||
fun commands(command: Command, name: String? = null) {
|
|
||||||
this.commands[if (name.isNullOrEmpty()) command.name else name] = command
|
|
||||||
}
|
|
||||||
|
|
||||||
fun extensions(vararg extensions: KFunction<BotModule>) {
|
|
||||||
extensions.forEach {
|
|
||||||
val kClass = it.returnType.classifier as KClass<*>
|
|
||||||
if (!kClass.isSubclassOf(BotModule::class))
|
|
||||||
return
|
|
||||||
|
|
||||||
this.extensions.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun prefixes(vararg prefixes: String) {
|
|
||||||
prefixes(prefixes.toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun prefixes(prefixes: List<String>) {
|
|
||||||
this.prefixes.addAll(prefixes)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,11 +4,33 @@ import dev.kord.core.behavior.channel.createMessage
|
||||||
import dev.kord.core.entity.Message
|
import dev.kord.core.entity.Message
|
||||||
import dev.kord.rest.builder.message.AllowedMentionsBuilder
|
import dev.kord.rest.builder.message.AllowedMentionsBuilder
|
||||||
import dev.kord.rest.builder.message.allowedMentions
|
import dev.kord.rest.builder.message.allowedMentions
|
||||||
|
import io.github.null2264.tsukumogami.core.commands.Command
|
||||||
|
|
||||||
class Context(private val bot: AbstractBot, private val message: Message, val prefix: String?, private val commandName: String?) {
|
class Context(
|
||||||
|
val bot: Bot,
|
||||||
|
val message: Message,
|
||||||
|
/**
|
||||||
|
* The prefix that used to invoke the command
|
||||||
|
*/
|
||||||
|
val prefix: String?,
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user that invoked the command
|
||||||
|
*
|
||||||
|
* Alias to [Message.author]
|
||||||
|
*/
|
||||||
val author get() = message.author
|
val author get() = message.author
|
||||||
val command get() = commandName?.let { bot.getCommand(it) }
|
|
||||||
|
/**
|
||||||
|
* The text that invoked the command
|
||||||
|
*/
|
||||||
|
var invokedWith: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command that currently being invoked
|
||||||
|
*/
|
||||||
|
var command: Command? = null
|
||||||
|
|
||||||
suspend fun send(content: String) = message.channel.createMessage(content)
|
suspend fun send(content: String) = message.channel.createMessage(content)
|
||||||
|
|
||||||
|
@ -22,4 +44,15 @@ class Context(private val bot: AbstractBot, private val message: Message, val pr
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun typing() = message.channel.type()
|
suspend fun typing() = message.channel.type()
|
||||||
|
|
||||||
|
fun parseArguments(): MutableList<String>? =
|
||||||
|
if (prefix != null && invokedWith != null) {
|
||||||
|
message.content
|
||||||
|
.substringAfter("$prefix$invokedWith")
|
||||||
|
.trim()
|
||||||
|
.split(' ')
|
||||||
|
.toMutableList()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,12 @@
|
||||||
|
package io.github.null2264.tsukumogami.core.commands
|
||||||
|
|
||||||
|
import io.github.null2264.tsukumogami.core.commands.converters.Converter
|
||||||
|
|
||||||
|
data class Argument<T : Any?>(
|
||||||
|
val name: String,
|
||||||
|
val converter: Converter<T>,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
converter.argumentObj = this
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
val args = mutableListOf<Argument<*>>()
|
||||||
|
|
||||||
|
fun <R : Any> args(
|
||||||
|
name: String,
|
||||||
|
converter: Converter<R>
|
||||||
|
): Converter<R> {
|
||||||
|
args.add(Argument(name, converter))
|
||||||
|
|
||||||
|
return converter
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun parse(context: Context) {
|
||||||
|
val currentValues = context.parseArguments()
|
||||||
|
|
||||||
|
run {
|
||||||
|
args.forEach { arg ->
|
||||||
|
val value = currentValues?.removeFirstOrNull() ?: return@run
|
||||||
|
arg.converter.parse(context, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
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(
|
||||||
|
val name: String,
|
||||||
|
val alias: Set<String>,
|
||||||
|
val description: String,
|
||||||
|
private val arguments: KFunction<Arguments>,
|
||||||
|
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)
|
||||||
|
handler.invoke(context, parsedArguments)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package io.github.null2264.tsukumogami.core.commands
|
||||||
|
|
||||||
|
class EmptyArguments : Arguments()
|
|
@ -0,0 +1,38 @@
|
||||||
|
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<String>,
|
||||||
|
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<String, Command> = 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}")
|
||||||
|
.trim()
|
||||||
|
.substringBefore(' ')
|
||||||
|
val command = allCommands[subcommandName] ?: return
|
||||||
|
context.invokedWith += " $subcommandName"
|
||||||
|
context.command = command
|
||||||
|
command.invoke(context)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
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<String, Command>
|
||||||
|
|
||||||
|
@TsukumogamiInternalApi
|
||||||
|
fun _addCommand(command: Command) {
|
||||||
|
allCommands[command.name] = command
|
||||||
|
command.alias.forEach { allCommands[it] = command }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commands(
|
||||||
|
name: String,
|
||||||
|
alias: Set<String> = setOf(),
|
||||||
|
description: String = "",
|
||||||
|
handler: suspend (Context) -> Unit,
|
||||||
|
) {
|
||||||
|
val command = Command(name, alias, description, ::EmptyArguments) { ctx, _ -> handler(ctx) }
|
||||||
|
_addCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Args : Arguments> commands(
|
||||||
|
name: String,
|
||||||
|
alias: Set<String> = setOf(),
|
||||||
|
description: String = "",
|
||||||
|
arguments: KFunction<Args>,
|
||||||
|
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<String> = setOf(),
|
||||||
|
description: String = "",
|
||||||
|
declaration: IGroup.() -> Unit,
|
||||||
|
) {
|
||||||
|
val group = Group(name, alias, description)
|
||||||
|
declaration(group)
|
||||||
|
_addCommand(group)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
* Annotation to tag a function as command
|
|
@ -0,0 +1,24 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
abstract class Converter<OutputType: Any?> {
|
||||||
|
|
||||||
|
lateinit var argumentObj: Argument<OutputType>
|
||||||
|
|
||||||
|
abstract var parsed: OutputType
|
||||||
|
|
||||||
|
abstract suspend fun parse(context: Context, input: String): OutputType
|
||||||
|
|
||||||
|
operator fun getValue(thisRef: Arguments, property: KProperty<*>): OutputType {
|
||||||
|
return this.parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
fun default(defaultValue: OutputType): OutputType {
|
||||||
|
this.parsed = defaultValue
|
||||||
|
return this.parsed
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package io.github.null2264.tsukumogami.core.commands.converters.impl
|
||||||
|
|
||||||
|
import io.github.null2264.tsukumogami.core.Context
|
||||||
|
import io.github.null2264.tsukumogami.core.commands.converters.Converter
|
||||||
|
|
||||||
|
class StringConverter : Converter<String>() {
|
||||||
|
|
||||||
|
override var parsed: String = ""
|
||||||
|
|
||||||
|
override suspend fun parse(context: Context, input: String): String {
|
||||||
|
this.parsed = input
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package io.github.null2264.tsukumogami.core.commands.converters.impl
|
||||||
|
|
||||||
|
import dev.kord.common.entity.Snowflake
|
||||||
|
import dev.kord.core.entity.User
|
||||||
|
import io.github.null2264.tsukumogami.core.Context
|
||||||
|
import io.github.null2264.tsukumogami.core.annotation.TsukumogamiInternalApi
|
||||||
|
import io.github.null2264.tsukumogami.core.commands.converters.Converter
|
||||||
|
import io.github.null2264.tsukumogami.core.exceptions.CommandException
|
||||||
|
import io.github.null2264.tsukumogami.core.ext.users
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
|
||||||
|
class UserConverter : Converter<User>() {
|
||||||
|
|
||||||
|
override lateinit var parsed: User
|
||||||
|
|
||||||
|
override suspend fun parse(context: Context, input: String): User {
|
||||||
|
if (input.equals("me", true)) {
|
||||||
|
val user = context.author
|
||||||
|
if (user != null) {
|
||||||
|
this.parsed = user
|
||||||
|
return this.parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (input.equals("you", true)) {
|
||||||
|
this.parsed = context.bot.client.getSelf()
|
||||||
|
return this.parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parsed = context.findUser(input) ?:
|
||||||
|
throw CommandException("User not found")
|
||||||
|
|
||||||
|
return this.parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun Context.findUser(arg: String): User? =
|
||||||
|
if (arg.startsWith("<@") && arg.endsWith(">")) {
|
||||||
|
val id: String = arg.substring(2, arg.length - 1).replace("!", "")
|
||||||
|
|
||||||
|
try {
|
||||||
|
bot.client.getUser(Snowflake(id))
|
||||||
|
} catch (_: NumberFormatException) {
|
||||||
|
throw CommandException("Invalid user ID")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
bot.client.getUser(Snowflake(arg))
|
||||||
|
} catch (_: NumberFormatException) {
|
||||||
|
if (!arg.contains("#")) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
bot.client.users.firstOrNull { user ->
|
||||||
|
user.tag.equals(arg, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +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, declaration: Converter<String>.() -> Unit = {}): Converter<String> {
|
||||||
|
val converter = StringConverter()
|
||||||
|
declaration(converter)
|
||||||
|
return args(name, converter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Arguments.user(name: String, declaration: Converter<User>.() -> Unit = {}): Converter<User> {
|
||||||
|
val converter = UserConverter()
|
||||||
|
declaration(converter)
|
||||||
|
return args(name, converter)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,56 @@
|
||||||
|
package io.github.null2264.tsukumogami.core.ext
|
||||||
|
|
||||||
|
fun String.parseCommandAndArguments(): List<String> {
|
||||||
|
if (isBlank()) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = mutableListOf<String>()
|
||||||
|
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
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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<Module>, 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<Module>) {
|
||||||
|
get().unloadModules(modules)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,47 +1,27 @@
|
||||||
package io.github.null2264.tsukumogami.core.module
|
package io.github.null2264.tsukumogami.core.module
|
||||||
|
|
||||||
import co.touchlab.kermit.Logger
|
import io.github.null2264.tsukumogami.core.Bot
|
||||||
import io.github.null2264.tsukumogami.core.AbstractBot
|
import io.github.null2264.tsukumogami.core.annotation.TsukumogamiInternalApi
|
||||||
import io.github.null2264.tsukumogami.core.module.annotation.Command as CommandAnnotation
|
import io.github.null2264.tsukumogami.core.commands.Command
|
||||||
import io.github.null2264.tsukumogami.core.BotConfigurator
|
import io.github.null2264.tsukumogami.core.commands.IGroup
|
||||||
import kotlin.reflect.jvm.isAccessible
|
|
||||||
import kotlin.reflect.jvm.kotlinFunction
|
|
||||||
|
|
||||||
abstract class BotModule(val name: String, val description: String? = null) {
|
@OptIn(TsukumogamiInternalApi::class)
|
||||||
|
open class BotModule constructor(val name: String) : IGroup {
|
||||||
|
|
||||||
var bot: AbstractBot? = null
|
override val allCommands: MutableMap<String, Command> = mutableMapOf()
|
||||||
internal set
|
|
||||||
|
|
||||||
open fun setup() {}
|
override fun _addCommand(command: Command) {
|
||||||
|
command.module = this
|
||||||
|
super._addCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun install(bot: AbstractBot, configurator: BotConfigurator) {
|
internal fun install(bot: Bot): BotModule {
|
||||||
this.bot = bot
|
allCommands.values.distinctBy { it.name }.forEach(bot::addCommand)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
val methods = this::class.java.declaredMethods
|
internal fun uninstall(bot: Bot): BotModule {
|
||||||
for (method in methods) {
|
allCommands.values.distinctBy { it.name }.forEach(bot::removeCommand)
|
||||||
for (annotation in method.annotations) {
|
return this
|
||||||
if (annotation !is CommandAnnotation)
|
|
||||||
continue
|
|
||||||
|
|
||||||
configurator.apply {
|
|
||||||
val kMethod = method.kotlinFunction
|
|
||||||
if (isExists(kMethod?.name)) {
|
|
||||||
Logger.e { "Command already exists" }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kMethod?.let {
|
|
||||||
it.isAccessible = true
|
|
||||||
commands(
|
|
||||||
Command(
|
|
||||||
annotation.name.ifEmpty { it.name },
|
|
||||||
name,
|
|
||||||
it,
|
|
||||||
annotation.description.ifEmpty { description },
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
package io.github.null2264.tsukumogami.core.module
|
|
||||||
|
|
||||||
import kotlin.reflect.KFunction
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class holding information about a command
|
|
||||||
*/
|
|
||||||
data class Command(
|
|
||||||
val name: String,
|
|
||||||
val extension: String,
|
|
||||||
val callback: KFunction<*>,
|
|
||||||
val description: String? = null,
|
|
||||||
)
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue