mirror of
https://github.com/devproje/px32-bot.git
synced 2024-11-26 10:43:05 +00:00
Merge branch 'kernel-v0.3.0' into 'master'
Kernel v0.3.0-SNAPSHOT See merge request devproje/px32-bot!2
This commit is contained in:
commit
4c525fcb4c
15 changed files with 449 additions and 246 deletions
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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<SubcommandData>()
|
||||
val subcommandGroups = mutableListOf<SubcommandGroupData>()
|
||||
val options = mutableListOf<OptionData>()
|
||||
|
||||
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<OptionData>()
|
||||
|
||||
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<SubcommandData>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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<out String>) {
|
||||
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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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<ListenerAdapter>()
|
||||
private val commandContainer = CommandHandler()
|
||||
|
||||
val memLock = Mutex()
|
||||
val plugins get() = PluginLoader.getPlugins().map { it.value }
|
||||
val commandContainer = CommandHandler()
|
||||
var plugins = mutableMapOf<PluginConfig, Plugin>()
|
||||
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<ListenerAdapter>
|
||||
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<CommandHandler>().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<CommandHandler>().forEach { h ->
|
||||
h.register(jda)
|
||||
}
|
||||
|
||||
memLock.unlock()
|
||||
}
|
||||
|
||||
object PluginLoader {
|
||||
private val plugins = mutableMapOf<PluginConfig, Plugin>()
|
||||
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<PluginConfig, Plugin> {
|
||||
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<PluginConfig>()
|
||||
|
||||
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<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) {
|
||||
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<PluginConfig>(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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 -> "**비만**"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue