diff --git a/gradle.properties b/gradle.properties index 2aa3ba2..f60dbd3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ kotlin.code.style=official org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 group=net.projecttl -version=0.2.3-SNAPSHOT +version=0.3.0-SNAPSHOT ktor_version=2.3.12 log4j_version=2.23.1 diff --git a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/BotConfig.kt b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/BotConfig.kt new file mode 100644 index 0000000..dbd9bb9 --- /dev/null +++ b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/BotConfig.kt @@ -0,0 +1,70 @@ +package net.projecttl.p.x32.api + +import org.jetbrains.exposed.sql.Database +import java.io.File +import java.io.FileInputStream +import java.util.* +import kotlin.reflect.KProperty +import kotlin.system.exitProcess + +object BotConfig { + private fun useBotConfig(): BotConfigDelegate { + return BotConfigDelegate() + } + + fun useDatabase(): Database { + if (db_username.isNotBlank() && db_password.isNotBlank()) { + return Database.connect( + driver = db_driver, + url = db_url, + user = db_username, + password = db_password + ) + } + + return Database.connect( + driver = db_driver, + url = db_url + ) + } + + val token: String by useBotConfig() + val owner: String by useBotConfig() + + private val bundle_func: String by useBotConfig() + val bundle = when (bundle_func) { + "1" -> true + "0" -> false + else -> { + throw IllegalArgumentException("bundle_func option must be 0 or 1") + } + } + + private val db_driver: String by useBotConfig() + private val db_url: String by useBotConfig() + private val db_username: String by useBotConfig() + private val db_password: String by useBotConfig() +} + +private class BotConfigDelegate { + private val props = Properties() + + init { + val file = File("config.properties") + if (!file.exists()) { + val default = this.javaClass.getResourceAsStream("/config.sample.properties")!!.readBytes() + file.outputStream().use { stream -> + stream.write(default) + } + + println("config.properties is not found, create new one...") + exitProcess(1) + } + + props.load(FileInputStream(file)) + } + + operator fun getValue(thisRef: Any?, property: KProperty<*>): String { + return props.getProperty(property.name).toString() + } +} diff --git a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/command/CommandObj.kt b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/command/CommandObj.kt new file mode 100644 index 0000000..ac5068d --- /dev/null +++ b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/command/CommandObj.kt @@ -0,0 +1,128 @@ +package net.projecttl.p.x32.api.command + +import net.dv8tion.jda.api.interactions.commands.OptionType +import net.dv8tion.jda.api.interactions.commands.build.CommandData +import net.dv8tion.jda.api.interactions.commands.build.OptionData +import net.dv8tion.jda.api.interactions.commands.build.SubcommandData +import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData +import net.dv8tion.jda.internal.interactions.CommandDataImpl + +fun useCommand(init: CommandObj.() -> Unit): CommandData { + val obj = CommandObj() + init.invoke(obj) + + return obj.build() +} + +class CommandObj { + var name: String = "" + var description: String = "" + + val subcommands = mutableListOf() + val subcommandGroups = mutableListOf() + val options = mutableListOf() + + fun subcommand(sub: SubcommandObj.() -> Unit) { + val obj = SubcommandObj() + sub.invoke(obj) + + subcommands += obj.build() + } + + fun subcommandGroup(group: SubcommandGroupObj.() -> Unit) { + val obj = SubcommandGroupObj() + group.invoke(obj) + + subcommandGroups += obj.build() + } + + fun option(opt: OptionObj.() -> Unit) { + val obj = OptionObj() + opt.invoke(obj) + + options += obj.build() + } + + fun build(): CommandData { + val obj = CommandDataImpl(name, description) + if (subcommands.isNotEmpty()) { + subcommands.forEach { + obj.addSubcommands(it) + } + } + + if (subcommandGroups.isNotEmpty()) { + subcommandGroups.forEach { + obj.addSubcommandGroups(it) + } + } + + if (options.isNotEmpty()) { + options.forEach { + obj.addOptions(it) + } + } + + return CommandData.fromData(obj.toData()) + } +} + +class SubcommandObj { + var name: String = "" + var description: String = "" + val options = mutableListOf() + + fun option(opt: OptionObj.() -> Unit) { + val obj = OptionObj() + opt.invoke(obj) + + options += obj.build() + } + + fun build(): SubcommandData { + val obj = SubcommandData(name, description) + if (options.isNotEmpty()) { + options.forEach { + obj.addOptions(it) + } + } + + return obj + } +} + +class SubcommandGroupObj { + var name: String = "" + var description: String = "" + val subcommands = mutableListOf() + + fun subcommand(opt: SubcommandObj.() -> Unit) { + val obj = SubcommandObj() + opt.invoke(obj) + + subcommands += obj.build() + } + + fun build(): SubcommandGroupData { + val obj = SubcommandGroupData(name, description) + if (subcommands.isNotEmpty()) { + subcommands.forEach { + obj.addSubcommands(it) + } + } + + return obj + } +} + +class OptionObj { + lateinit var type: OptionType + var name: String = "" + var description: String = "" + var required: Boolean = false + var autoComplete: Boolean = false + + fun build(): OptionData { + return OptionData(type, name, description, required, autoComplete) + } +} diff --git a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/Px32.kt b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/Px32.kt index a747ae0..72a3e60 100644 --- a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/Px32.kt +++ b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/Px32.kt @@ -4,31 +4,52 @@ import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.dv8tion.jda.api.JDA +import net.projecttl.p.x32.api.BotConfig import net.projecttl.p.x32.command.Info import net.projecttl.p.x32.command.PluginCommand import net.projecttl.p.x32.command.Reload -import net.projecttl.p.x32.config.Config import net.projecttl.p.x32.config.DefaultConfig import net.projecttl.p.x32.kernel.CoreKernel -import org.jetbrains.exposed.sql.Database import org.slf4j.Logger import org.slf4j.LoggerFactory lateinit var jda: JDA + private set lateinit var kernel: CoreKernel -lateinit var database: Database + private set val logger: Logger = LoggerFactory.getLogger(Px32::class.java) @OptIn(DelicateCoroutinesApi::class) -fun main() { +fun main(args: Array) { println("Px32 version v${DefaultConfig.version}") - if (Config.owner.isBlank() || Config.owner.isEmpty()) { + if (BotConfig.owner.isBlank() || BotConfig.owner.isEmpty()) { logger.warn("owner option is blank or empty!") } - kernel = CoreKernel(Config.token) - val handler = kernel.getCommandContainer() + kernel = CoreKernel(BotConfig.token) + val handler = kernel.commandContainer + + if (args.contains("--remove-cmd")) { + jda = kernel.build() + try { + jda.retrieveCommands().queue { + if (it == null) { + return@queue + } + + it.forEach { command -> + logger.info("unregister command: /${command.name}") + command.jda.deleteCommandById(command.id).queue() + } + } + } catch (ex: Exception) { + ex.printStackTrace() + } + + kernel.kill() + return + } handler.addCommand(Info) handler.addCommand(Reload) diff --git a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/Info.kt b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/Info.kt index b197a42..b8598e5 100644 --- a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/Info.kt +++ b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/Info.kt @@ -3,23 +3,23 @@ package net.projecttl.p.x32.command import net.dv8tion.jda.api.JDAInfo import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.internal.interactions.CommandDataImpl import net.projecttl.p.x32.api.command.GlobalCommand +import net.projecttl.p.x32.api.command.useCommand import net.projecttl.p.x32.config.DefaultConfig -import net.projecttl.p.x32.kernel.CoreKernel.PluginLoader +import net.projecttl.p.x32.kernel import java.lang.management.ManagementFactory object Info : GlobalCommand { - override val data: CommandData = CommandData.fromData( - CommandDataImpl("info","봇의 정보를 표시 합니다").toData() - ) + override val data: CommandData = useCommand { + name = "info" + description = "봇의 정보를 표시 합니다" + } override suspend fun execute(ev: SlashCommandInteractionEvent) { val rb = ManagementFactory.getRuntimeMXBean() val r = Runtime.getRuntime() - val size = PluginLoader.getPlugins().size - + val size = kernel.plugins.size val info = """ Px32Bot v${DefaultConfig.version}, JDA `v${JDAInfo.VERSION}`, `Java ${System.getProperty("java.version")}` and `Kotlin ${KotlinVersion.CURRENT}` System on `${System.getProperty("os.name")}` @@ -30,7 +30,7 @@ object Info : GlobalCommand { Assigned `${r.maxMemory() / 1048576}MB` of Max Memories at this Bot Using `${(r.totalMemory() - r.freeMemory()) / 1048576}MB` at this Bot - Total $size plugin${if (size > 1) "s" else ""} loaded + Total $size plugin${if (size > 1) "s" else ""} loaded """.trimIndent() ev.reply(info).queue() diff --git a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/PluginCommand.kt b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/PluginCommand.kt index 370f57b..b1e02a6 100644 --- a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/PluginCommand.kt +++ b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/PluginCommand.kt @@ -3,15 +3,17 @@ package net.projecttl.p.x32.command import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.entities.MessageEmbed import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent -import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.internal.interactions.CommandDataImpl import net.projecttl.p.x32.api.command.GlobalCommand +import net.projecttl.p.x32.api.command.useCommand import net.projecttl.p.x32.api.util.colour import net.projecttl.p.x32.api.util.footer -import net.projecttl.p.x32.kernel.CoreKernel.PluginLoader +import net.projecttl.p.x32.kernel object PluginCommand : GlobalCommand { - override val data = CommandData.fromData(CommandDataImpl("plugin", "봇에 불러온 플러그인을 확인 합니다").toData()) + override val data = useCommand { + name = "plugins" + description = "봇에 불러온 플러그인을 확인 합니다" + } override suspend fun execute(ev: SlashCommandInteractionEvent) { val embed = EmbedBuilder().apply { @@ -22,7 +24,7 @@ object PluginCommand : GlobalCommand { footer(ev.user) } - val loader = PluginLoader.getPlugins() + val loader = kernel.plugins val fields = loader.map { (c, _) -> MessageEmbed.Field(":electric_plug: **${c.name}**", "`${c.version}`", true) } diff --git a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/Reload.kt b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/Reload.kt index 2d82c1f..54e056f 100644 --- a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/Reload.kt +++ b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/Reload.kt @@ -1,21 +1,23 @@ package net.projecttl.p.x32.command import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent -import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.internal.interactions.CommandDataImpl +import net.projecttl.p.x32.api.BotConfig import net.projecttl.p.x32.api.command.GlobalCommand -import net.projecttl.p.x32.config.Config +import net.projecttl.p.x32.api.command.useCommand import net.projecttl.p.x32.kernel object Reload : GlobalCommand { - override val data = CommandData.fromData(CommandDataImpl("reload", "플러그인을 다시 불러 옵니다").toData()) + override val data = useCommand { + name = "reload" + description = "플러그인을 다시 불러 옵니다" + } override suspend fun execute(ev: SlashCommandInteractionEvent) { if (kernel.memLock.isLocked) { return } - if (ev.user.id != Config.owner) { + if (ev.user.id != BotConfig.owner) { return ev.reply(":warning: 권한을 가지고 있지 않아요").queue() } diff --git a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/config/Config.kt b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/config/Config.kt deleted file mode 100644 index 5cacf94..0000000 --- a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/config/Config.kt +++ /dev/null @@ -1,48 +0,0 @@ -package net.projecttl.p.x32.config - -import net.projecttl.p.x32.logger -import java.io.File -import java.io.FileInputStream -import java.util.* -import kotlin.reflect.KProperty -import kotlin.system.exitProcess - -object Config { - private fun useConfig(): ConfigDelegate { - return ConfigDelegate() - } - - val token: String by useConfig() - val owner: String by useConfig() - - private val bundle_func: String by useConfig() - val bundle = if (bundle_func == "1") true else if (bundle_func == "0") false else throw IllegalArgumentException("bundle_func option must be 0 or 1") - - val db_driver: String by useConfig() - val db_url: String by useConfig() - val db_username: String by useConfig() - val db_password: String by useConfig() -} - -private class ConfigDelegate { - private val props = Properties() - - init { - val file = File("config.properties") - if (!file.exists()) { - val default = this.javaClass.getResourceAsStream("/config.sample.properties")!!.readBytes() - file.outputStream().use { stream -> - stream.write(default) - } - - logger.error("config.properties is not found, create new one...") - exitProcess(1) - } - - props.load(FileInputStream(file)) - } - - operator fun getValue(thisRef: Any?, property: KProperty<*>): String { - return props.getProperty(property.name).toString() - } -} \ No newline at end of file diff --git a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/CoreKernel.kt b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/CoreKernel.kt index 40ce0bc..2e60ee1 100644 --- a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/CoreKernel.kt +++ b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/CoreKernel.kt @@ -7,10 +7,11 @@ import net.dv8tion.jda.api.JDABuilder import net.dv8tion.jda.api.hooks.ListenerAdapter import net.dv8tion.jda.api.requests.GatewayIntent import net.dv8tion.jda.api.utils.MemberCachePolicy +import net.projecttl.p.x32.api.BotConfig import net.projecttl.p.x32.api.Plugin import net.projecttl.p.x32.api.command.CommandHandler import net.projecttl.p.x32.api.model.PluginConfig -import net.projecttl.p.x32.config.Config +import net.projecttl.p.x32.config.DefaultConfig import net.projecttl.p.x32.func.BundleModule import net.projecttl.p.x32.logger import java.io.File @@ -19,69 +20,67 @@ import java.nio.charset.Charset import java.util.jar.JarFile class CoreKernel(token: String) { + lateinit var jda: JDA + private set + private val builder = JDABuilder.createDefault(token, listOf( - GatewayIntent.GUILD_PRESENCES, GatewayIntent.GUILD_MEMBERS, GatewayIntent.GUILD_MESSAGES, GatewayIntent.MESSAGE_CONTENT, + GatewayIntent.GUILD_PRESENCES, + GatewayIntent.SCHEDULED_EVENTS, GatewayIntent.GUILD_VOICE_STATES, - GatewayIntent.GUILD_EMOJIS_AND_STICKERS, - GatewayIntent.SCHEDULED_EVENTS + GatewayIntent.GUILD_EMOJIS_AND_STICKERS )).setMemberCachePolicy(MemberCachePolicy.ALL) - private val handlers = mutableListOf() - private val commandContainer = CommandHandler() val memLock = Mutex() - val plugins get() = PluginLoader.getPlugins().map { it.value } + val commandContainer = CommandHandler() + var plugins = mutableMapOf() + private set + var isActive = false + private set - private fun include() { - if (Config.bundle) { - val b = BundleModule() - PluginLoader.putModule(b.config, b) + private val parentDir = File("./plugins").apply { + if (!exists()) { + mkdirs() } } - fun getCommandContainer(): CommandHandler { - return commandContainer - } + val handlers: List + get() { + if (!isActive) { + return listOf() + } + + return jda.eventManager.registeredListeners.map { it as ListenerAdapter } + } fun addHandler(handler: ListenerAdapter) { - handlers.add(handler) + if (isActive) { + jda.addEventListener(handler) + return + } + + builder.addEventListeners(handler) } fun delHandler(handler: ListenerAdapter) { - handlers.remove(handler) - } - - fun build(): JDA { - include() - - PluginLoader.load() - plugins.forEach { plugin -> - plugin.handlers.forEach { handler -> - handlers.add(handler) - } + if (isActive) { + jda.removeEventListener(handler) + return } - handlers.map { - logger.info("Load event listener: ${it::class.simpleName}") - builder.addEventListeners(it) - } - builder.addEventListeners(commandContainer) - - return builder.build() + builder.removeEventListeners(handler) } fun register(jda: JDA) { commandContainer.register(jda) - handlers.forEach { h -> - if (h is CommandHandler) { - h.register(jda) - } + jda.eventManager.registeredListeners.filterIsInstance().forEach { h -> + h.register(jda) } Runtime.getRuntime().addShutdownHook(Thread { - PluginLoader.destroy() + destroy() }) } @@ -90,127 +89,133 @@ class CoreKernel(token: String) { memLock.lock() } - PluginLoader.destroy() - plugins.forEach { plugin -> - plugin.handlers.filter { handlers.contains(it) }.map { - handlers.remove(it) - } - } - - include() - PluginLoader.load() - - plugins.forEach { plugin -> - plugin.handlers.forEach { handler -> - if (!handlers.contains(handler)) { - handlers.add(handler) - jda.addEventListener(handler) - } - } - } - - handlers.forEach { h -> - if (h is CommandHandler) { - h.register(jda) - } + destroy() + load() + handlers.filterIsInstance().forEach { h -> + h.register(jda) } memLock.unlock() } - object PluginLoader { - private val plugins = mutableMapOf() - private val parentDir = File("./plugins").apply { - if (!exists()) { - mkdirs() + fun kill() { + destroy() + jda.shutdownNow() + } + + private fun load() { + if (BotConfig.bundle) { + val b = BundleModule() + loadModule(b.config, b) + } + + parentDir.listFiles()?.forEach { file -> + try { + loadPlugin(file) + } catch (ex: Exception) { + logger.error("error occurred while to plugin loading: ${ex.message}") } } - fun getPlugins(): Map { - return plugins.toMap() + var cnt = 0 + plugins.forEach { (_, plugin) -> + plugin.handlers.forEach { + addHandler(it) + logger.info("Load event listener: ${it::class.simpleName}") + cnt++ + } } - fun putModule(config: PluginConfig, plugin: Plugin) { + logger.info("Loaded ${plugins.size} plugin${if (plugins.size > 1) "s" else ""} and $cnt handler${if (cnt > 1) "s" else ""}.") + } + + private fun destroy() { + plugins.forEach { (config, plugin) -> + logger.info("disable ${config.name} plugin...") + + plugin.handlers.forEach { + delHandler(it) + } + try { - logger.info("Load module ${config.name} v${config.version}") - plugin.onLoad() - } catch (ex: Exception) { - ex.printStackTrace() plugin.destroy() - return - } - - plugins[config] = plugin - } - - fun load() { - parentDir.listFiles()?.forEach { file -> - try { - loadPlugin(file) - } catch (ex: Exception) { - logger.error("error occurred while to plugin loading: ${ex.message}") - } - } - - logger.info("Loaded ${plugins.size} plugins") - } - - fun destroy() { - val unloaded = mutableListOf() - - plugins.forEach { (config, plugin) -> - logger.info("disable ${config.name} plugin...") - - try { - plugin.destroy() - } catch (ex: Exception) { - logger.error("failed to destroy ${config.name} plugin") - ex.printStackTrace() - } - - unloaded += config - } - - unloaded.forEach { - plugins.remove(it) - } - } - - private fun loadPlugin(file: File) { - if (file.name == "px32-bot-module") { - return - } - - if (!file.name.endsWith(".jar")) { - return - } - - val jar = JarFile(file) - val cnf = jar.entries().toList().singleOrNull { jarEntry -> jarEntry.name == "plugin.json" } - if (cnf == null) - return logger.error("${file.name} is not a plugin. aborted") - - val stream = jar.getInputStream(cnf) - val raw = stream.use { - return@use it.readBytes().toString(Charset.forName("UTF-8")) - } - - val config = Json.decodeFromString(raw) - val cl = URLClassLoader(arrayOf(file.toPath().toUri().toURL())) - val obj = cl.loadClass(config.main).getDeclaredConstructor().newInstance() - - if (obj !is Plugin) - return logger.error("${config.name} is not valid main class. aborted") - - try { - logger.info("Load plugin ${config.name} v${config.version}") - obj.onLoad() } catch (ex: Exception) { + logger.error("failed to destroy ${config.name} plugin") ex.printStackTrace() - return logger.error("Failed to load plugin ${config.name}") } + } - plugins[config] = obj + plugins = mutableMapOf() + } + + private fun loadPlugin(file: File) { + if (file.name == "px32-bot-module") { + return + } + + if (!file.name.endsWith(".jar")) { + return + } + + val jar = JarFile(file) + val cnf = jar.entries().toList().singleOrNull { jarEntry -> jarEntry.name == "plugin.json" } + if (cnf == null) + throw IllegalAccessException("${file.name} is not a plugin. aborted") + + val stream = jar.getInputStream(cnf) + val raw = stream.use { + return@use it.readBytes().toString(Charset.forName("UTF-8")) + } + + val config = Json.decodeFromString(raw) + val cl = URLClassLoader(arrayOf(file.toPath().toUri().toURL())) + val obj = cl.loadClass(config.main).getDeclaredConstructor().newInstance() + + if (obj !is Plugin) + throw IllegalAccessException("${config.name} is not valid plugin class. aborted") + + try { + loadModule(config, obj) + } catch (ex: Exception) { + throw ex } } + + private fun loadModule(config: PluginConfig, plugin: Plugin) { + try { + logger.info("Load plugin ${config.name} v${config.version}") + plugin.onLoad() + } catch (ex: Exception) { + try { + plugin.destroy() + } catch (ex: Exception) { + throw ex + } + + throw ex + } + + plugins[config] = plugin + } + + fun build(): JDA { + if (isActive) { + logger.error("core kernel is already loaded! you cannot rebuild this kernel.") + return jda + } + + load() + builder.addEventListeners(commandContainer) + jda = builder.build() + isActive = true + + Runtime.getRuntime().addShutdownHook(Thread { + isActive = false + + logger.info("shutdown now Px32 kernel v${DefaultConfig.version}") + jda.shutdownNow() + }) + + return jda + } } diff --git a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Avatar.kt b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Avatar.kt index 3da4daa..6bcad24 100644 --- a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Avatar.kt +++ b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Avatar.kt @@ -2,14 +2,16 @@ package net.projecttl.p.x32.func.command import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent -import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.internal.interactions.CommandDataImpl import net.projecttl.p.x32.api.command.UserContext +import net.projecttl.p.x32.api.command.useCommand import net.projecttl.p.x32.api.util.colour import net.projecttl.p.x32.api.util.footer object Avatar : UserContext { - override val data = CommandData.fromData(CommandDataImpl("avatar", "유저의 프로필 이미지를 가져 옵니다").toData()) + override val data = useCommand { + name = "avatar" + description = "유저의 프로필 이미지를 가져 옵니다" + } override suspend fun execute(ev: UserContextInteractionEvent) { val embed = EmbedBuilder() diff --git a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Bmi.kt b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Bmi.kt index 46ba33d..73dba7e 100644 --- a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Bmi.kt +++ b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Bmi.kt @@ -3,9 +3,8 @@ package net.projecttl.p.x32.func.command import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent import net.dv8tion.jda.api.interactions.commands.OptionType -import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.internal.interactions.CommandDataImpl import net.projecttl.p.x32.api.command.GlobalCommand +import net.projecttl.p.x32.api.command.useCommand import net.projecttl.p.x32.api.util.colour import net.projecttl.p.x32.api.util.footer import java.math.RoundingMode @@ -13,10 +12,24 @@ import java.text.DecimalFormat import kotlin.math.pow object Bmi : GlobalCommand { - override val data = CommandData.fromData(CommandDataImpl("bmi", "키와 몸무게 기반으로 bmi지수를 계산할 수 있어요").apply { - addOption(OptionType.NUMBER, "height", "신장 길이를 적어 주세요 (cm)", true) - addOption(OptionType.NUMBER, "weight", "몸무게를 적어 주세요 (kg)", true) - }.toData()) + override val data = useCommand { + name = "bmi" + description = "키와 몸무게 기반으로 bmi 지수를 계산해요" + + option { + type = OptionType.NUMBER + name = "height" + description = "신장 길이를 적어 주세요 (cm)" + required = true + } + + option { + type = OptionType.NUMBER + name = "weight" + description = "몸무게를 적어 주세요 (kg)" + required = true + } + } override suspend fun execute(ev: SlashCommandInteractionEvent) { val height = ev.getOption("height")!!.asDouble @@ -30,9 +43,9 @@ object Bmi : GlobalCommand { val bmi = weight / (height / 100).pow(2) fun result(bmi: Double): String { return when { - bmi < 18.5 -> "**저체중**" + bmi < 18.5 -> "**저체중**" bmi in 18.5..24.9 -> "**정상 체중**" - bmi in 25.0.. 29.9 -> "**과체중**" + bmi in 25.0..29.9 -> "**과체중**" else -> "**비만**" } } diff --git a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Dice.kt b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Dice.kt index 692ddcd..cfa9bd8 100644 --- a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Dice.kt +++ b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Dice.kt @@ -1,14 +1,16 @@ package net.projecttl.p.x32.func.command import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent -import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.internal.interactions.CommandDataImpl import net.projecttl.p.x32.api.command.GlobalCommand +import net.projecttl.p.x32.api.command.useCommand import kotlin.random.Random import kotlin.random.nextInt object Dice : GlobalCommand { - override val data = CommandData.fromData(CommandDataImpl("dice", "랜덤으로 주사위를 굴립니다").toData()) + override val data = useCommand { + name = "dice" + description = "랜덤으로 주사위를 굴립니다" + } override suspend fun execute(ev: SlashCommandInteractionEvent) { val rand = Random.nextInt(1..6) diff --git a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/MsgLength.kt b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/MsgLength.kt index 973cbbc..fadedbb 100644 --- a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/MsgLength.kt +++ b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/MsgLength.kt @@ -1,12 +1,14 @@ package net.projecttl.p.x32.func.command import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent -import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.internal.interactions.CommandDataImpl import net.projecttl.p.x32.api.command.MessageContext +import net.projecttl.p.x32.api.command.useCommand object MsgLength : MessageContext { - override val data = CommandData.fromData(CommandDataImpl("length", "메시지의 길이를 확인 합니다.").toData()) + override val data = useCommand { + name = "length" + description = "메시지의 길이를 확인 합니다." + } override suspend fun execute(ev: MessageContextInteractionEvent) { val target = ev.target diff --git a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/MsgPurge.kt b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/MsgPurge.kt index a5374b8..2aca0f5 100644 --- a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/MsgPurge.kt +++ b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/MsgPurge.kt @@ -2,17 +2,21 @@ package net.projecttl.p.x32.func.command import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent import net.dv8tion.jda.api.interactions.commands.OptionType -import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.internal.interactions.CommandDataImpl import net.projecttl.p.x32.api.command.GlobalCommand +import net.projecttl.p.x32.api.command.useCommand import net.projecttl.p.x32.func.Conf object MsgPurge : GlobalCommand { - override val data = CommandData.fromData( - CommandDataImpl("purge", "n개의 메시지를 채널에서 삭제해요").apply { - addOption(OptionType.INTEGER, "n", "n개만큼 메시지가 삭제 됩니다", true) - }.toData() - ) + override val data = useCommand { + name = "purge" + description = "n개의 메시지를 채널에서 삭제해요" + option { + type = OptionType.INTEGER + name = "n" + description = "n개만큼 메시지가 삭제 됩니다" + required = true + } + } override suspend fun execute(ev: SlashCommandInteractionEvent) { val n = ev.getOption("n")!!.asInt diff --git a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Ping.kt b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Ping.kt index cfe18e7..cdbdabf 100644 --- a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Ping.kt +++ b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Ping.kt @@ -6,16 +6,16 @@ import net.dv8tion.jda.api.entities.MessageEmbed import net.dv8tion.jda.api.entities.User import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.internal.interactions.CommandDataImpl import net.projecttl.p.x32.api.command.GlobalCommand +import net.projecttl.p.x32.api.command.useCommand import net.projecttl.p.x32.api.util.colour import net.projecttl.p.x32.api.util.footer object Ping : GlobalCommand { - override val data: CommandData = CommandData.fromData(CommandDataImpl( - "ping", - "Discord API 레이턴시 확인 합니다" - ).toData()) + override val data: CommandData = useCommand { + name = "ping" + description = "Discord API 레이턴시 확인 합니다" + } override suspend fun execute(ev: SlashCommandInteractionEvent) { val started = System.currentTimeMillis()