mirror of
https://github.com/devproje/px32-bot.git
synced 2024-11-29 20:13:06 +00:00
feat: plugin command
This commit is contained in:
parent
01a003a001
commit
1536bebf7a
12 changed files with 184 additions and 51 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -7,8 +7,10 @@ build/
|
||||||
.fleet/
|
.fleet/
|
||||||
.kotlin/
|
.kotlin/
|
||||||
|
|
||||||
.env
|
config.properties
|
||||||
!.env.example
|
!config.sample.properties
|
||||||
|
|
||||||
|
data.db
|
||||||
|
|
||||||
deploy.sh
|
deploy.sh
|
||||||
plugins/
|
plugins/
|
||||||
|
|
|
@ -5,11 +5,14 @@ plugins {
|
||||||
|
|
||||||
group = property("group")!!
|
group = property("group")!!
|
||||||
version = property("version")!!
|
version = property("version")!!
|
||||||
plugins
|
|
||||||
val ktor_version: String by project
|
val ktor_version: String by project
|
||||||
val log4j_version: String by project
|
val log4j_version: String by project
|
||||||
val exposed_version: String by project
|
val exposed_version: String by project
|
||||||
|
|
||||||
|
val sqlite_version: String by project
|
||||||
|
val postgres_version: String by project
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
apply(plugin = "org.jetbrains.kotlin.jvm")
|
apply(plugin = "org.jetbrains.kotlin.jvm")
|
||||||
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
|
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
|
||||||
|
@ -35,12 +38,13 @@ subprojects {
|
||||||
implementation("net.dv8tion:JDA:5.1.0")
|
implementation("net.dv8tion:JDA:5.1.0")
|
||||||
implementation("io.ktor:ktor-client-cio:$ktor_version")
|
implementation("io.ktor:ktor-client-cio:$ktor_version")
|
||||||
implementation("io.ktor:ktor-client-core:$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-api:$log4j_version")
|
||||||
implementation("org.apache.logging.log4j:log4j-core:$log4j_version")
|
implementation("org.apache.logging.log4j:log4j-core:$log4j_version")
|
||||||
implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
|
implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
|
||||||
implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
||||||
implementation("org.apache.logging.log4j:log4j-slf4j2-impl:$log4j_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-serialization-json:1.7.2")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
|
||||||
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||||
|
|
|
@ -7,3 +7,7 @@ version=0.1.0-SNAPSHOT
|
||||||
ktor_version=2.3.12
|
ktor_version=2.3.12
|
||||||
log4j_version=2.23.1
|
log4j_version=2.23.1
|
||||||
exposed_version=0.54.0
|
exposed_version=0.54.0
|
||||||
|
|
||||||
|
# Database Driver
|
||||||
|
sqlite_version=3.46.1.0
|
||||||
|
postgres_version=42.7.4
|
||||||
|
|
|
@ -33,7 +33,7 @@ tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
archiveBaseName.set(project.name)
|
archiveBaseName.set(rootProject.name)
|
||||||
archiveClassifier.set("")
|
archiveClassifier.set("")
|
||||||
archiveVersion.set("")
|
archiveVersion.set("")
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,35 @@
|
||||||
package net.projecttl.p.x32
|
package net.projecttl.p.x32
|
||||||
|
|
||||||
import net.dv8tion.jda.api.JDA
|
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.command.Reload
|
||||||
|
import net.projecttl.p.x32.config.Config
|
||||||
import net.projecttl.p.x32.config.DefaultConfig
|
import net.projecttl.p.x32.config.DefaultConfig
|
||||||
import net.projecttl.p.x32.func.loadDefault
|
import net.projecttl.p.x32.func.loadDefault
|
||||||
import net.projecttl.p.x32.func.handler.Ready
|
import net.projecttl.p.x32.func.handler.Ready
|
||||||
import net.projecttl.p.x32.kernel.CoreKernel
|
import net.projecttl.p.x32.kernel.CoreKernel
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
lateinit var jda: JDA
|
lateinit var jda: JDA
|
||||||
lateinit var kernel: CoreKernel
|
lateinit var kernel: CoreKernel
|
||||||
|
lateinit var database: Database
|
||||||
|
|
||||||
val logger: Logger = LoggerFactory.getLogger(Px32::class.java)
|
val logger: Logger = LoggerFactory.getLogger(Px32::class.java)
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
println("Px32 version v${DefaultConfig.version}")
|
println("Px32 version v${DefaultConfig.version}")
|
||||||
kernel = CoreKernel(System.getenv("TOKEN"))
|
if (Config.owner.isBlank() || Config.owner.isEmpty()) {
|
||||||
val handler = kernel.getGlobalCommandHandler()
|
logger.warn("owner option is blank or empty!")
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel = CoreKernel(Config.token)
|
||||||
|
val handler = kernel.getCommandContainer()
|
||||||
kernel.addHandler(Ready)
|
kernel.addHandler(Ready)
|
||||||
|
|
||||||
handler.addCommand(Reload)
|
handler.addCommand(Reload)
|
||||||
|
handler.addCommand(PluginCommand)
|
||||||
loadDefault(handler)
|
loadDefault(handler)
|
||||||
|
|
||||||
jda = kernel.build()
|
jda = kernel.build()
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.api.interactions.commands.build.CommandData
|
||||||
import net.dv8tion.jda.internal.interactions.CommandDataImpl
|
import net.dv8tion.jda.internal.interactions.CommandDataImpl
|
||||||
import net.projecttl.p.x32.api.command.GlobalCommand
|
import net.projecttl.p.x32.api.command.GlobalCommand
|
||||||
|
import net.projecttl.p.x32.config.Config
|
||||||
import net.projecttl.p.x32.kernel
|
import net.projecttl.p.x32.kernel
|
||||||
|
|
||||||
object Reload : GlobalCommand {
|
object Reload : GlobalCommand {
|
||||||
override val data = CommandData.fromData(CommandDataImpl("reload", "플러그인을 다시 불러 옵니다").toData())
|
override val data = CommandData.fromData(CommandDataImpl("reload", "플러그인을 다시 불러 옵니다").toData())
|
||||||
|
|
||||||
override suspend fun execute(ev: SlashCommandInteractionEvent) {
|
override suspend fun execute(ev: SlashCommandInteractionEvent) {
|
||||||
|
if (ev.user.id != Config.owner) {
|
||||||
|
return ev.reply(":warning: 권한을 가지고 있지 않아요").queue()
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
kernel.reload()
|
kernel.reload()
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
|
|
|
@ -1,7 +1,45 @@
|
||||||
package net.projecttl.p.x32.config
|
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 {
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ object DefaultConfig {
|
||||||
val version: String by useConfig()
|
val version: String by useConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DefaultConfigDelegate {
|
class DefaultConfigDelegate {
|
||||||
private val props = Properties()
|
private val props = Properties()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
|
@ -5,15 +5,16 @@ import net.dv8tion.jda.api.JDABuilder
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||||
import net.projecttl.p.x32.api.Plugin
|
import net.projecttl.p.x32.api.Plugin
|
||||||
import net.projecttl.p.x32.api.command.CommandHandler
|
import net.projecttl.p.x32.api.command.CommandHandler
|
||||||
|
import net.projecttl.p.x32.jda
|
||||||
import net.projecttl.p.x32.logger
|
import net.projecttl.p.x32.logger
|
||||||
|
|
||||||
class CoreKernel(token: String) {
|
class CoreKernel(token: String) {
|
||||||
private val builder = JDABuilder.createDefault(token)
|
private val builder = JDABuilder.createDefault(token)
|
||||||
private val handlers = mutableListOf<ListenerAdapter>()
|
private val handlers = mutableListOf<ListenerAdapter>()
|
||||||
private val handler = CommandHandler()
|
private val commandContainer = CommandHandler()
|
||||||
|
|
||||||
fun getGlobalCommandHandler(): CommandHandler {
|
fun getCommandContainer(): CommandHandler {
|
||||||
return handler
|
return commandContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addHandler(handler: ListenerAdapter) {
|
fun addHandler(handler: ListenerAdapter) {
|
||||||
|
@ -31,24 +32,19 @@ class CoreKernel(token: String) {
|
||||||
fun build(): JDA {
|
fun build(): JDA {
|
||||||
PluginLoader.load()
|
PluginLoader.load()
|
||||||
|
|
||||||
val plugins = PluginLoader.getPlugins()
|
plugins().forEach { plugin ->
|
||||||
plugins.forEach { (c, p) ->
|
plugin.getHandlers().forEach { handler ->
|
||||||
logger.info("Load plugin ${c.name} v${c.version}")
|
|
||||||
p.onLoad()
|
|
||||||
|
|
||||||
p.getHandlers().map { handler ->
|
|
||||||
handlers.add(handler)
|
handlers.add(handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers.map {
|
handlers.map {
|
||||||
println("test $it")
|
|
||||||
builder.addEventListeners(it)
|
builder.addEventListeners(it)
|
||||||
}
|
}
|
||||||
builder.addEventListeners(handler)
|
builder.addEventListeners(commandContainer)
|
||||||
|
|
||||||
val jda = builder.build()
|
val jda = builder.build()
|
||||||
handler.register(jda)
|
commandContainer.register(jda)
|
||||||
handlers.forEach { h ->
|
handlers.forEach { h ->
|
||||||
if (h is CommandHandler) {
|
if (h is CommandHandler) {
|
||||||
h.register(jda)
|
h.register(jda)
|
||||||
|
@ -63,15 +59,36 @@ class CoreKernel(token: String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reload() {
|
fun reload() {
|
||||||
val plugins = PluginLoader.getPlugins()
|
val newHandlers = mutableListOf<ListenerAdapter>()
|
||||||
plugins.forEach { (c, p) ->
|
PluginLoader.destroy()
|
||||||
logger.info("Reload plugin ${c.name} v${c.version}")
|
plugins().forEach { plugin ->
|
||||||
p.destroy()
|
plugin.getHandlers().forEach { handler ->
|
||||||
|
if (handlers.contains(handler)) {
|
||||||
|
jda.removeEventListener(handler)
|
||||||
|
handlers.remove(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginLoader.load()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package net.projecttl.p.x32.kernel
|
package net.projecttl.p.x32.kernel
|
||||||
|
|
||||||
import kotlinx.serialization.json.Json
|
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.Plugin
|
||||||
import net.projecttl.p.x32.api.model.PluginConfig
|
import net.projecttl.p.x32.api.model.PluginConfig
|
||||||
import net.projecttl.p.x32.logger
|
import net.projecttl.p.x32.logger
|
||||||
|
@ -23,29 +24,7 @@ object PluginLoader {
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
parentDir.listFiles()?.forEach { file ->
|
parentDir.listFiles()?.forEach { file ->
|
||||||
if (file.name.endsWith(".jar")) {
|
loadPlugin(file)
|
||||||
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<PluginConfig>(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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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<PluginConfig>(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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
token=<discord_bot_token>
|
||||||
|
owner=
|
||||||
|
|
||||||
|
db_driver=org.sqlite.JDBC
|
||||||
|
db_url=jdbc:sqlite:data.db
|
||||||
|
db_username=
|
||||||
|
db_password=
|
Loading…
Reference in a new issue