From 1536bebf7a4d8c6755b1a1600fb560367411cc05 Mon Sep 17 00:00:00 2001 From: Project_IO Date: Sat, 21 Sep 2024 23:13:23 +0900 Subject: [PATCH] feat: plugin command --- .gitignore | 6 +- build.gradle.kts | 8 ++- gradle.properties | 4 ++ px32-bot-core/build.gradle.kts | 2 +- .../main/kotlin/net/projecttl/p/x32/Px32.kt | 14 ++++- .../projecttl/p/x32/command/PluginCommand.kt | 34 +++++++++++ .../net/projecttl/p/x32/command/Reload.kt | 5 ++ .../net/projecttl/p/x32/config/Config.kt | 42 +++++++++++++- .../projecttl/p/x32/config/DefaultConfig.kt | 2 +- .../net/projecttl/p/x32/kernel/CoreKernel.kt | 53 +++++++++++------ .../projecttl/p/x32/kernel/PluginLoader.kt | 58 +++++++++++-------- .../main/resources/config.sample.properties | 7 +++ 12 files changed, 184 insertions(+), 51 deletions(-) create mode 100644 px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/PluginCommand.kt create mode 100644 px32-bot-core/src/main/resources/config.sample.properties diff --git a/.gitignore b/.gitignore index 2a386e0..037ece4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,10 @@ build/ .fleet/ .kotlin/ -.env -!.env.example +config.properties +!config.sample.properties + +data.db deploy.sh plugins/ diff --git a/build.gradle.kts b/build.gradle.kts index 50403ff..7d7d2e0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,11 +5,14 @@ plugins { group = property("group")!! version = property("version")!! -plugins + val ktor_version: String by project val log4j_version: String by project val exposed_version: String by project +val sqlite_version: String by project +val postgres_version: String by project + allprojects { apply(plugin = "org.jetbrains.kotlin.jvm") apply(plugin = "org.jetbrains.kotlin.plugin.serialization") @@ -35,12 +38,13 @@ subprojects { implementation("net.dv8tion:JDA:5.1.0") implementation("io.ktor:ktor-client-cio:$ktor_version") implementation("io.ktor:ktor-client-core:$ktor_version") + implementation("org.xerial:sqlite-jdbc:$sqlite_version") + implementation("org.postgresql:postgresql:$postgres_version") implementation("org.apache.logging.log4j:log4j-api:$log4j_version") implementation("org.apache.logging.log4j:log4j-core:$log4j_version") implementation("org.jetbrains.exposed:exposed-core:$exposed_version") implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version") implementation("org.apache.logging.log4j:log4j-slf4j2-impl:$log4j_version") - implementation("io.ktor:ktor-client-okhttp-jvm:2.3.12") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") testImplementation(platform("org.junit:junit-bom:5.10.0")) diff --git a/gradle.properties b/gradle.properties index 787bb7d..e1b3cfe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,3 +7,7 @@ version=0.1.0-SNAPSHOT ktor_version=2.3.12 log4j_version=2.23.1 exposed_version=0.54.0 + +# Database Driver +sqlite_version=3.46.1.0 +postgres_version=42.7.4 diff --git a/px32-bot-core/build.gradle.kts b/px32-bot-core/build.gradle.kts index 10e3830..ac23c47 100644 --- a/px32-bot-core/build.gradle.kts +++ b/px32-bot-core/build.gradle.kts @@ -33,7 +33,7 @@ tasks { } shadowJar { - archiveBaseName.set(project.name) + archiveBaseName.set(rootProject.name) archiveClassifier.set("") archiveVersion.set("") 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 9a1c145..58d506b 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 @@ -1,25 +1,35 @@ package net.projecttl.p.x32 import net.dv8tion.jda.api.JDA +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.func.loadDefault import net.projecttl.p.x32.func.handler.Ready 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 lateinit var kernel: CoreKernel +lateinit var database: Database + val logger: Logger = LoggerFactory.getLogger(Px32::class.java) fun main() { println("Px32 version v${DefaultConfig.version}") - kernel = CoreKernel(System.getenv("TOKEN")) - val handler = kernel.getGlobalCommandHandler() + if (Config.owner.isBlank() || Config.owner.isEmpty()) { + logger.warn("owner option is blank or empty!") + } + + kernel = CoreKernel(Config.token) + val handler = kernel.getCommandContainer() kernel.addHandler(Ready) handler.addCommand(Reload) + handler.addCommand(PluginCommand) loadDefault(handler) jda = kernel.build() 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 new file mode 100644 index 0000000..66d3e8f --- /dev/null +++ b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/command/PluginCommand.kt @@ -0,0 +1,34 @@ +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.util.colour +import net.projecttl.p.x32.kernel.PluginLoader + +object PluginCommand : GlobalCommand { + override val data = CommandData.fromData(CommandDataImpl("plugin", "봇에 불러온 플러그인을 확인 합니다").toData()) + + override suspend fun execute(ev: SlashCommandInteractionEvent) { + val embed = EmbedBuilder().apply { + setTitle(":sparkles: **${ev.jda.selfUser.name}**봇 플러그인 리스트") + setThumbnail(ev.jda.selfUser.avatarUrl) + + colour() + } + + val loader = PluginLoader.getPlugins() + val fields = loader.map { (c, _) -> + MessageEmbed.Field(":electric_plug: **${c.name}**", "`${c.version}`", true) + } + + fields.forEach { field -> + embed.addField(field) + } + + ev.replyEmbeds(embed.build()).queue() + } +} \ No newline at end of file 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 0ce7d20..ccfcc7f 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 @@ -4,12 +4,17 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEve 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.Config import net.projecttl.p.x32.kernel object Reload : GlobalCommand { override val data = CommandData.fromData(CommandDataImpl("reload", "플러그인을 다시 불러 옵니다").toData()) override suspend fun execute(ev: SlashCommandInteractionEvent) { + if (ev.user.id != Config.owner) { + return ev.reply(":warning: 권한을 가지고 있지 않아요").queue() + } + try { kernel.reload() } catch (ex: Exception) { 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 index cba3f9a..e9a913e 100644 --- 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 @@ -1,7 +1,45 @@ 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() + + val db_driver: String by useConfig() + val db_url: String by useConfig() + val db_username: String by useConfig() + val db_password: String by useConfig() } -class ConfigDelegate { -} +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/config/DefaultConfig.kt b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/config/DefaultConfig.kt index 3b19c27..a916dec 100644 --- a/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/config/DefaultConfig.kt +++ b/px32-bot-core/src/main/kotlin/net/projecttl/p/x32/config/DefaultConfig.kt @@ -11,7 +11,7 @@ object DefaultConfig { val version: String by useConfig() } -private class DefaultConfigDelegate { +class DefaultConfigDelegate { private val props = Properties() init { 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 d97ba57..80fbbcf 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 @@ -5,15 +5,16 @@ import net.dv8tion.jda.api.JDABuilder import net.dv8tion.jda.api.hooks.ListenerAdapter import net.projecttl.p.x32.api.Plugin import net.projecttl.p.x32.api.command.CommandHandler +import net.projecttl.p.x32.jda import net.projecttl.p.x32.logger class CoreKernel(token: String) { private val builder = JDABuilder.createDefault(token) private val handlers = mutableListOf() - private val handler = CommandHandler() + private val commandContainer = CommandHandler() - fun getGlobalCommandHandler(): CommandHandler { - return handler + fun getCommandContainer(): CommandHandler { + return commandContainer } fun addHandler(handler: ListenerAdapter) { @@ -31,24 +32,19 @@ class CoreKernel(token: String) { fun build(): JDA { PluginLoader.load() - val plugins = PluginLoader.getPlugins() - plugins.forEach { (c, p) -> - logger.info("Load plugin ${c.name} v${c.version}") - p.onLoad() - - p.getHandlers().map { handler -> + plugins().forEach { plugin -> + plugin.getHandlers().forEach { handler -> handlers.add(handler) } } handlers.map { - println("test $it") builder.addEventListeners(it) } - builder.addEventListeners(handler) + builder.addEventListeners(commandContainer) val jda = builder.build() - handler.register(jda) + commandContainer.register(jda) handlers.forEach { h -> if (h is CommandHandler) { h.register(jda) @@ -63,15 +59,36 @@ class CoreKernel(token: String) { } fun reload() { - val plugins = PluginLoader.getPlugins() - plugins.forEach { (c, p) -> - logger.info("Reload plugin ${c.name} v${c.version}") - p.destroy() + val newHandlers = mutableListOf() + PluginLoader.destroy() + plugins().forEach { plugin -> + plugin.getHandlers().forEach { handler -> + if (handlers.contains(handler)) { + jda.removeEventListener(handler) + handlers.remove(handler) + } + } } PluginLoader.load() - plugins.forEach { (_, p) -> - p.onLoad() + + plugins().forEach { plugin -> + plugin.getHandlers().forEach { handler -> + if (!handlers.contains(handler)) { + handlers.add(handler) + newHandlers.add(handler) + } + } + } + + handlers.map { + builder.addEventListeners(it) + } + + newHandlers.forEach { h -> + if (h is CommandHandler) { + h.register(jda) + } } } } 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 index 78d4d93..8153f6f 100644 --- 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 @@ -1,6 +1,7 @@ package net.projecttl.p.x32.kernel import kotlinx.serialization.json.Json +import net.dv8tion.jda.api.hooks.ListenerAdapter import net.projecttl.p.x32.api.Plugin import net.projecttl.p.x32.api.model.PluginConfig import net.projecttl.p.x32.logger @@ -23,29 +24,7 @@ object PluginLoader { fun load() { parentDir.listFiles()?.forEach { file -> - if (file.name.endsWith(".jar")) { - val jar = JarFile(file) - val cnf = jar.entries().toList().singleOrNull { jarEntry -> jarEntry.name == "plugin.json" } - if (cnf != null) { - 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 clazz = cl.loadClass(config.main) - val obj = clazz.getDeclaredConstructor().newInstance() - - if (obj is Plugin) { - plugins[config] = obj - } else { - logger.error("${config.name} is not valid main class. aborted") - } - } else { - logger.error("${file.name} is not a plugin. aborted") - } - } + loadPlugin(file) } } @@ -61,4 +40,37 @@ object PluginLoader { } } } + + private fun loadPlugin(file: File) { + 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/resources/config.sample.properties b/px32-bot-core/src/main/resources/config.sample.properties new file mode 100644 index 0000000..49eb5ff --- /dev/null +++ b/px32-bot-core/src/main/resources/config.sample.properties @@ -0,0 +1,7 @@ +token= +owner= + +db_driver=org.sqlite.JDBC +db_url=jdbc:sqlite:data.db +db_username= +db_password=