diff --git a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/Plugin.kt b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/Plugin.kt index a354aa5..269b60d 100644 --- a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/Plugin.kt +++ b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/Plugin.kt @@ -3,11 +3,13 @@ package net.projecttl.p.x32.api import kotlinx.serialization.json.Json import net.dv8tion.jda.api.hooks.ListenerAdapter import net.projecttl.p.x32.api.model.PluginConfig +import net.projecttl.p.x32.api.util.AsyncTaskContainer import org.slf4j.Logger import org.slf4j.LoggerFactory abstract class Plugin { private val handlerContainer = mutableListOf() + val taskContainer = AsyncTaskContainer() val config = this.javaClass.getResourceAsStream("/plugin.json")!!.let { val raw = it.bufferedReader().readText() diff --git a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/command/CommandHandler.kt b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/command/CommandHandler.kt index 790d94c..6efd9e3 100644 --- a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/command/CommandHandler.kt +++ b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/command/CommandHandler.kt @@ -3,7 +3,6 @@ package net.projecttl.p.x32.api.command import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import net.dv8tion.jda.api.JDA import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent @@ -11,8 +10,8 @@ import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEven import net.dv8tion.jda.api.hooks.ListenerAdapter import net.dv8tion.jda.api.interactions.commands.build.Commands -fun commandHandler(block: (CommandHandler) -> Unit): CommandHandler { - val handler = CommandHandler() +fun commandHandler(guildId: Long = 0L, block: (CommandHandler) -> Unit): CommandHandler { + val handler = CommandHandler(guildId) block.invoke(handler) return handler @@ -25,15 +24,14 @@ class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() { override fun onSlashCommandInteraction(ev: SlashCommandInteractionEvent) { val name = ev.interaction.name - commands.forEach { command -> - if (command.data.name == name) { - if (command is GlobalCommand) { - GlobalScope.launch { - command.execute(ev) - } - - return@forEach + commands.singleOrNull { it.data.name == name }?.also { command -> + if (command is GlobalCommand) { + GlobalScope.launch { + command.execute(ev) + println("${ev.user.id} is use command for: ${ev.interaction.name}") } + + return } } } @@ -41,15 +39,14 @@ class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() { override fun onUserContextInteraction(ev: UserContextInteractionEvent) { val name = ev.interaction.name - commands.forEach { command -> - if (command.data.name == name) { - if (command is UserContext) { - GlobalScope.launch { - command.execute(ev) - } - - return@forEach + commands.singleOrNull { it.data.name == name }?.also { command -> + if (command is UserContext) { + GlobalScope.launch { + command.execute(ev) + println("${ev.user.id} is use user context for: ${ev.interaction.name}") } + + return } } } @@ -57,15 +54,14 @@ class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() { override fun onMessageContextInteraction(ev: MessageContextInteractionEvent) { val name = ev.interaction.name - commands.forEach { command -> - if (command.data.name == name) { - if (command is MessageContext) { - GlobalScope.launch { - command.execute(ev) - } - - return@forEach + commands.singleOrNull { it.data.name == name }?.also { command -> + if (command is MessageContext) { + GlobalScope.launch { + command.execute(ev) + println("${ev.user.id} is use message context for: ${ev.interaction.name}") } + + return } } } diff --git a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/util/AsyncTaskContainer.kt b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/util/AsyncTaskContainer.kt index 3da608e..7c482fd 100644 --- a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/util/AsyncTaskContainer.kt +++ b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/util/AsyncTaskContainer.kt @@ -38,16 +38,24 @@ class AsyncTask(val loop: Boolean = true, val block: suspend () -> Unit) { * This feature is Experimental */ class AsyncTaskContainer { - private val tasks = mutableMapOf() + val tasks = mutableMapOf() private var tid = 0L fun getTask(tid: Long): AsyncTask? { return tasks[tid] } - fun createTask(task: AsyncTask) { + suspend fun createTask(task: AsyncTask) { tasks[tid] = task println("created task with id: $tid") + try { + task.run() + } catch (ex: Exception) { + ex.printStackTrace() + tasks.remove(tid) + + return + } tid++ } diff --git a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/util/Embed.kt b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/util/Embed.kt index 58fabad..d2a415e 100644 --- a/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/util/Embed.kt +++ b/px32-bot-api/src/main/kotlin/net/projecttl/p/x32/api/util/Embed.kt @@ -2,7 +2,6 @@ package net.projecttl.p.x32.api.util import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.entities.User -import java.time.LocalDate import java.time.LocalDateTime import kotlin.random.Random 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 4f38c87..b197a42 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 @@ -6,7 +6,7 @@ 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.config.DefaultConfig -import net.projecttl.p.x32.kernel.PluginLoader +import net.projecttl.p.x32.kernel.CoreKernel.PluginLoader import java.lang.management.ManagementFactory object Info : GlobalCommand { 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 abafc93..370f57b 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 @@ -8,7 +8,7 @@ import net.dv8tion.jda.internal.interactions.CommandDataImpl import net.projecttl.p.x32.api.command.GlobalCommand import net.projecttl.p.x32.api.util.colour import net.projecttl.p.x32.api.util.footer -import net.projecttl.p.x32.kernel.PluginLoader +import net.projecttl.p.x32.kernel.CoreKernel.PluginLoader object PluginCommand : GlobalCommand { override val data = CommandData.fromData(CommandDataImpl("plugin", "봇에 불러온 플러그인을 확인 합니다").toData()) 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 9fe6932..215d0db 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 @@ -1,14 +1,22 @@ package net.projecttl.p.x32.kernel import kotlinx.coroutines.sync.Mutex +import kotlinx.serialization.json.Json import net.dv8tion.jda.api.JDA import net.dv8tion.jda.api.JDABuilder import net.dv8tion.jda.api.hooks.ListenerAdapter import net.dv8tion.jda.api.requests.GatewayIntent +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.api.util.AsyncTaskContainer import net.projecttl.p.x32.config.Config import net.projecttl.p.x32.func.BundleModule import net.projecttl.p.x32.logger +import java.io.File +import java.net.URLClassLoader +import java.nio.charset.Charset +import java.util.jar.JarFile class CoreKernel(token: String) { private val builder = JDABuilder.createDefault(token, listOf( @@ -116,4 +124,88 @@ class CoreKernel(token: String) { memLock.unlock() } + + object PluginLoader { + private val plugins = mutableMapOf() + private val parentDir = File("./plugins").apply { + if (!exists()) { + mkdirs() + } + } + + fun getPlugins(): Map { + return plugins.toMap() + } + + fun putModule(config: PluginConfig, plugin: Plugin) { + 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 -> + loadPlugin(file) + } + + logger.info("Loaded ${plugins.size} plugins") + } + + fun destroy() { + 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() + } + } + } + + 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) { + ex.printStackTrace() + return logger.error("Failed to load plugin ${config.name}") + } + + plugins[config] = obj + } + } } diff --git a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/PluginLoader.kt b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/PluginLoader.kt deleted file mode 100644 index 5ee1015..0000000 --- a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/PluginLoader.kt +++ /dev/null @@ -1,94 +0,0 @@ -package net.projecttl.p.x32.kernel - -import kotlinx.serialization.json.Json -import net.projecttl.p.x32.api.Plugin -import net.projecttl.p.x32.api.model.PluginConfig -import net.projecttl.p.x32.logger -import java.io.File -import java.net.URLClassLoader -import java.nio.charset.Charset -import java.util.jar.JarFile - -object PluginLoader { - private val plugins = mutableMapOf() - private val parentDir = File("./plugins").apply { - if (!exists()) { - mkdirs() - } - } - - fun getPlugins(): Map { - return plugins.toMap() - } - - fun putModule(config: PluginConfig, plugin: Plugin) { - 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 -> - loadPlugin(file) - } - - logger.info("Loaded ${plugins.size} plugins") - } - - fun destroy() { - 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() - } - } - } - - 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) { - ex.printStackTrace() - return logger.error("Failed to load plugin ${config.name}") - } - - plugins[config] = obj - } -} diff --git a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/TaskManager.kt b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/TaskManager.kt deleted file mode 100644 index 9460b78..0000000 --- a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/kernel/TaskManager.kt +++ /dev/null @@ -1,4 +0,0 @@ -package net.projecttl.p.x32.kernel - -object TaskManager { -} \ No newline at end of file diff --git a/px32-bot-core/src/main/resources/config.sample.properties b/px32-bot-core/src/main/resources/config.sample.properties index a0b5be4..bd5033e 100644 --- a/px32-bot-core/src/main/resources/config.sample.properties +++ b/px32-bot-core/src/main/resources/config.sample.properties @@ -1,8 +1,11 @@ token= owner= +# Px32's Default module activation +# setting value is 1 or 0 bundle_func=1 +# Database connection settings db_driver=org.sqlite.JDBC db_url=jdbc:sqlite:data.db db_username= diff --git a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/BundleModule.kt b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/BundleModule.kt index e8c6226..be68169 100644 --- a/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/BundleModule.kt +++ b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/BundleModule.kt @@ -17,6 +17,8 @@ class BundleModule : Plugin() { addHandler(Ready) addHandler(commandHandler { handler -> handler.addCommand(Avatar) + handler.addCommand(Bmi) + handler.addCommand(Dice) handler.addCommand(MsgLength) handler.addCommand(MsgPurge) handler.addCommand(Ping) 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 new file mode 100644 index 0000000..46ba33d --- /dev/null +++ b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Bmi.kt @@ -0,0 +1,54 @@ +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.util.colour +import net.projecttl.p.x32.api.util.footer +import java.math.RoundingMode +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 suspend fun execute(ev: SlashCommandInteractionEvent) { + val height = ev.getOption("height")!!.asDouble + val weight = ev.getOption("weight")!!.asDouble + + if (height <= 0.0 || weight <= 0.0) { + ev.reply(":warning: 키 또는 몸무게가 0 또는 음수일 수 없어요").setEphemeral(true).queue() + return + } + + val bmi = weight / (height / 100).pow(2) + fun result(bmi: Double): String { + return when { + bmi < 18.5 -> "**저체중**" + bmi in 18.5..24.9 -> "**정상 체중**" + bmi in 25.0.. 29.9 -> "**과체중**" + else -> "**비만**" + } + } + + val df = DecimalFormat("#.##") + df.roundingMode = RoundingMode.HALF_UP + + ev.replyEmbeds(EmbedBuilder().apply { + setTitle(":pencil: BMI 지수가 나왔어요") + setDescription("${result(bmi)} 이에요") + addField(":straight_ruler: **신장 길이**", "`${height}cm`", true) + addField(":scales: **몸무게**", "`${weight}kg`", true) + addField(":chart_with_downwards_trend: **BMI 지수**", "`${df.format(bmi)}` ", true) + + colour() + footer(ev.user) + }.build()).queue() + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..692ddcd --- /dev/null +++ b/px32-bot-module/src/main/kotlin/net/projecttl/p/x32/func/command/Dice.kt @@ -0,0 +1,17 @@ +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 kotlin.random.Random +import kotlin.random.nextInt + +object Dice : GlobalCommand { + override val data = CommandData.fromData(CommandDataImpl("dice", "랜덤으로 주사위를 굴립니다").toData()) + + override suspend fun execute(ev: SlashCommandInteractionEvent) { + val rand = Random.nextInt(1..6) + ev.reply(":game_die: **${rand}**").queue() + } +} 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 b09a18a..a5374b8 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 @@ -1,11 +1,11 @@ package net.projecttl.p.x32.func.command -import net.projecttl.p.x32.func.Conf 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.func.Conf object MsgPurge : GlobalCommand { override val data = CommandData.fromData(