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:
Project_IO 2024-10-07 04:37:38 +00:00
commit 4c525fcb4c
15 changed files with 449 additions and 246 deletions

View file

@ -2,7 +2,7 @@ kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
group=net.projecttl group=net.projecttl
version=0.2.3-SNAPSHOT version=0.3.0-SNAPSHOT
ktor_version=2.3.12 ktor_version=2.3.12
log4j_version=2.23.1 log4j_version=2.23.1

View file

@ -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()
}
}

View file

@ -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)
}
}

View file

@ -4,31 +4,52 @@ import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.dv8tion.jda.api.JDA 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.Info
import net.projecttl.p.x32.command.PluginCommand 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.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
private set
lateinit var kernel: CoreKernel lateinit var kernel: CoreKernel
lateinit var database: Database private set
val logger: Logger = LoggerFactory.getLogger(Px32::class.java) val logger: Logger = LoggerFactory.getLogger(Px32::class.java)
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun main() { fun main(args: Array<out String>) {
println("Px32 version v${DefaultConfig.version}") 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!") logger.warn("owner option is blank or empty!")
} }
kernel = CoreKernel(Config.token) kernel = CoreKernel(BotConfig.token)
val handler = kernel.getCommandContainer() 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(Info)
handler.addCommand(Reload) handler.addCommand(Reload)

View file

@ -3,23 +3,23 @@ package net.projecttl.p.x32.command
import net.dv8tion.jda.api.JDAInfo import net.dv8tion.jda.api.JDAInfo
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
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.projecttl.p.x32.api.command.GlobalCommand 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.config.DefaultConfig
import net.projecttl.p.x32.kernel.CoreKernel.PluginLoader import net.projecttl.p.x32.kernel
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
object Info : GlobalCommand { object Info : GlobalCommand {
override val data: CommandData = CommandData.fromData( override val data: CommandData = useCommand {
CommandDataImpl("info","봇의 정보를 표시 합니다").toData() name = "info"
) description = "봇의 정보를 표시 합니다"
}
override suspend fun execute(ev: SlashCommandInteractionEvent) { override suspend fun execute(ev: SlashCommandInteractionEvent) {
val rb = ManagementFactory.getRuntimeMXBean() val rb = ManagementFactory.getRuntimeMXBean()
val r = Runtime.getRuntime() val r = Runtime.getRuntime()
val size = PluginLoader.getPlugins().size val size = kernel.plugins.size
val info = """ val info = """
Px32Bot v${DefaultConfig.version}, JDA `v${JDAInfo.VERSION}`, Px32Bot v${DefaultConfig.version}, JDA `v${JDAInfo.VERSION}`,
`Java ${System.getProperty("java.version")}` and `Kotlin ${KotlinVersion.CURRENT}` System on `${System.getProperty("os.name")}` `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 Assigned `${r.maxMemory() / 1048576}MB` of Max Memories at this Bot
Using `${(r.totalMemory() - r.freeMemory()) / 1048576}MB` 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() """.trimIndent()
ev.reply(info).queue() ev.reply(info).queue()

View file

@ -3,15 +3,17 @@ package net.projecttl.p.x32.command
import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.entities.MessageEmbed import net.dv8tion.jda.api.entities.MessageEmbed
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent 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.GlobalCommand
import net.projecttl.p.x32.api.command.useCommand
import net.projecttl.p.x32.api.util.colour import net.projecttl.p.x32.api.util.colour
import net.projecttl.p.x32.api.util.footer 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 { object PluginCommand : GlobalCommand {
override val data = CommandData.fromData(CommandDataImpl("plugin", "봇에 불러온 플러그인을 확인 합니다").toData()) override val data = useCommand {
name = "plugins"
description = "봇에 불러온 플러그인을 확인 합니다"
}
override suspend fun execute(ev: SlashCommandInteractionEvent) { override suspend fun execute(ev: SlashCommandInteractionEvent) {
val embed = EmbedBuilder().apply { val embed = EmbedBuilder().apply {
@ -22,7 +24,7 @@ object PluginCommand : GlobalCommand {
footer(ev.user) footer(ev.user)
} }
val loader = PluginLoader.getPlugins() val loader = kernel.plugins
val fields = loader.map { (c, _) -> val fields = loader.map { (c, _) ->
MessageEmbed.Field(":electric_plug: **${c.name}**", "`${c.version}`", true) MessageEmbed.Field(":electric_plug: **${c.name}**", "`${c.version}`", true)
} }

View file

@ -1,21 +1,23 @@
package net.projecttl.p.x32.command package net.projecttl.p.x32.command
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.interactions.commands.build.CommandData import net.projecttl.p.x32.api.BotConfig
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.api.command.useCommand
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 = useCommand {
name = "reload"
description = "플러그인을 다시 불러 옵니다"
}
override suspend fun execute(ev: SlashCommandInteractionEvent) { override suspend fun execute(ev: SlashCommandInteractionEvent) {
if (kernel.memLock.isLocked) { if (kernel.memLock.isLocked) {
return return
} }
if (ev.user.id != Config.owner) { if (ev.user.id != BotConfig.owner) {
return ev.reply(":warning: 권한을 가지고 있지 않아요").queue() return ev.reply(":warning: 권한을 가지고 있지 않아요").queue()
} }

View file

@ -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()
}
}

View file

@ -7,10 +7,11 @@ import net.dv8tion.jda.api.JDABuilder
import net.dv8tion.jda.api.hooks.ListenerAdapter import net.dv8tion.jda.api.hooks.ListenerAdapter
import net.dv8tion.jda.api.requests.GatewayIntent import net.dv8tion.jda.api.requests.GatewayIntent
import net.dv8tion.jda.api.utils.MemberCachePolicy 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.Plugin
import net.projecttl.p.x32.api.command.CommandHandler import net.projecttl.p.x32.api.command.CommandHandler
import net.projecttl.p.x32.api.model.PluginConfig 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.func.BundleModule
import net.projecttl.p.x32.logger import net.projecttl.p.x32.logger
import java.io.File import java.io.File
@ -19,69 +20,67 @@ import java.nio.charset.Charset
import java.util.jar.JarFile import java.util.jar.JarFile
class CoreKernel(token: String) { class CoreKernel(token: String) {
lateinit var jda: JDA
private set
private val builder = JDABuilder.createDefault(token, listOf( private val builder = JDABuilder.createDefault(token, listOf(
GatewayIntent.GUILD_PRESENCES,
GatewayIntent.GUILD_MEMBERS, GatewayIntent.GUILD_MEMBERS,
GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MESSAGES,
GatewayIntent.MESSAGE_CONTENT, GatewayIntent.MESSAGE_CONTENT,
GatewayIntent.GUILD_PRESENCES,
GatewayIntent.SCHEDULED_EVENTS,
GatewayIntent.GUILD_VOICE_STATES, GatewayIntent.GUILD_VOICE_STATES,
GatewayIntent.GUILD_EMOJIS_AND_STICKERS, GatewayIntent.GUILD_EMOJIS_AND_STICKERS
GatewayIntent.SCHEDULED_EVENTS
)).setMemberCachePolicy(MemberCachePolicy.ALL) )).setMemberCachePolicy(MemberCachePolicy.ALL)
private val handlers = mutableListOf<ListenerAdapter>()
private val commandContainer = CommandHandler()
val memLock = Mutex() 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() { private val parentDir = File("./plugins").apply {
if (Config.bundle) { if (!exists()) {
val b = BundleModule() mkdirs()
PluginLoader.putModule(b.config, b)
} }
} }
fun getCommandContainer(): CommandHandler { val handlers: List<ListenerAdapter>
return commandContainer get() {
} if (!isActive) {
return listOf()
}
return jda.eventManager.registeredListeners.map { it as ListenerAdapter }
}
fun addHandler(handler: ListenerAdapter) { fun addHandler(handler: ListenerAdapter) {
handlers.add(handler) if (isActive) {
jda.addEventListener(handler)
return
}
builder.addEventListeners(handler)
} }
fun delHandler(handler: ListenerAdapter) { fun delHandler(handler: ListenerAdapter) {
handlers.remove(handler) if (isActive) {
} jda.removeEventListener(handler)
return
fun build(): JDA {
include()
PluginLoader.load()
plugins.forEach { plugin ->
plugin.handlers.forEach { handler ->
handlers.add(handler)
}
} }
handlers.map { builder.removeEventListeners(handler)
logger.info("Load event listener: ${it::class.simpleName}")
builder.addEventListeners(it)
}
builder.addEventListeners(commandContainer)
return builder.build()
} }
fun register(jda: JDA) { fun register(jda: JDA) {
commandContainer.register(jda) commandContainer.register(jda)
handlers.forEach { h -> jda.eventManager.registeredListeners.filterIsInstance<CommandHandler>().forEach { h ->
if (h is CommandHandler) { h.register(jda)
h.register(jda)
}
} }
Runtime.getRuntime().addShutdownHook(Thread { Runtime.getRuntime().addShutdownHook(Thread {
PluginLoader.destroy() destroy()
}) })
} }
@ -90,127 +89,133 @@ class CoreKernel(token: String) {
memLock.lock() memLock.lock()
} }
PluginLoader.destroy() destroy()
plugins.forEach { plugin -> load()
plugin.handlers.filter { handlers.contains(it) }.map { handlers.filterIsInstance<CommandHandler>().forEach { h ->
handlers.remove(it) h.register(jda)
}
}
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)
}
} }
memLock.unlock() memLock.unlock()
} }
object PluginLoader { fun kill() {
private val plugins = mutableMapOf<PluginConfig, Plugin>() destroy()
private val parentDir = File("./plugins").apply { jda.shutdownNow()
if (!exists()) { }
mkdirs()
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> { var cnt = 0
return plugins.toMap() 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 { try {
logger.info("Load module ${config.name} v${config.version}")
plugin.onLoad()
} catch (ex: Exception) {
ex.printStackTrace()
plugin.destroy() 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) { } catch (ex: Exception) {
logger.error("failed to destroy ${config.name} plugin")
ex.printStackTrace() 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
}
} }

View file

@ -2,14 +2,16 @@ package net.projecttl.p.x32.func.command
import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent 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.UserContext
import net.projecttl.p.x32.api.command.useCommand
import net.projecttl.p.x32.api.util.colour import net.projecttl.p.x32.api.util.colour
import net.projecttl.p.x32.api.util.footer import net.projecttl.p.x32.api.util.footer
object Avatar : UserContext { object Avatar : UserContext {
override val data = CommandData.fromData(CommandDataImpl("avatar", "유저의 프로필 이미지를 가져 옵니다").toData()) override val data = useCommand {
name = "avatar"
description = "유저의 프로필 이미지를 가져 옵니다"
}
override suspend fun execute(ev: UserContextInteractionEvent) { override suspend fun execute(ev: UserContextInteractionEvent) {
val embed = EmbedBuilder() val embed = EmbedBuilder()

View file

@ -3,9 +3,8 @@ package net.projecttl.p.x32.func.command
import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.interactions.commands.OptionType 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.GlobalCommand
import net.projecttl.p.x32.api.command.useCommand
import net.projecttl.p.x32.api.util.colour import net.projecttl.p.x32.api.util.colour
import net.projecttl.p.x32.api.util.footer import net.projecttl.p.x32.api.util.footer
import java.math.RoundingMode import java.math.RoundingMode
@ -13,10 +12,24 @@ import java.text.DecimalFormat
import kotlin.math.pow import kotlin.math.pow
object Bmi : GlobalCommand { object Bmi : GlobalCommand {
override val data = CommandData.fromData(CommandDataImpl("bmi", "키와 몸무게 기반으로 bmi지수를 계산할 수 있어요").apply { override val data = useCommand {
addOption(OptionType.NUMBER, "height", "신장 길이를 적어 주세요 (cm)", true) name = "bmi"
addOption(OptionType.NUMBER, "weight", "몸무게를 적어 주세요 (kg)", true) description = "키와 몸무게 기반으로 bmi 지수를 계산해요"
}.toData())
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) { override suspend fun execute(ev: SlashCommandInteractionEvent) {
val height = ev.getOption("height")!!.asDouble val height = ev.getOption("height")!!.asDouble
@ -30,9 +43,9 @@ object Bmi : GlobalCommand {
val bmi = weight / (height / 100).pow(2) val bmi = weight / (height / 100).pow(2)
fun result(bmi: Double): String { fun result(bmi: Double): String {
return when { return when {
bmi < 18.5 -> "**저체중**" bmi < 18.5 -> "**저체중**"
bmi in 18.5..24.9 -> "**정상 체중**" bmi in 18.5..24.9 -> "**정상 체중**"
bmi in 25.0.. 29.9 -> "**과체중**" bmi in 25.0..29.9 -> "**과체중**"
else -> "**비만**" else -> "**비만**"
} }
} }

View file

@ -1,14 +1,16 @@
package net.projecttl.p.x32.func.command package net.projecttl.p.x32.func.command
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent 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.GlobalCommand
import net.projecttl.p.x32.api.command.useCommand
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextInt import kotlin.random.nextInt
object Dice : GlobalCommand { object Dice : GlobalCommand {
override val data = CommandData.fromData(CommandDataImpl("dice", "랜덤으로 주사위를 굴립니다").toData()) override val data = useCommand {
name = "dice"
description = "랜덤으로 주사위를 굴립니다"
}
override suspend fun execute(ev: SlashCommandInteractionEvent) { override suspend fun execute(ev: SlashCommandInteractionEvent) {
val rand = Random.nextInt(1..6) val rand = Random.nextInt(1..6)

View file

@ -1,12 +1,14 @@
package net.projecttl.p.x32.func.command package net.projecttl.p.x32.func.command
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent 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.MessageContext
import net.projecttl.p.x32.api.command.useCommand
object MsgLength : MessageContext { object MsgLength : MessageContext {
override val data = CommandData.fromData(CommandDataImpl("length", "메시지의 길이를 확인 합니다.").toData()) override val data = useCommand {
name = "length"
description = "메시지의 길이를 확인 합니다."
}
override suspend fun execute(ev: MessageContextInteractionEvent) { override suspend fun execute(ev: MessageContextInteractionEvent) {
val target = ev.target val target = ev.target

View file

@ -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.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.interactions.commands.OptionType 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.GlobalCommand
import net.projecttl.p.x32.api.command.useCommand
import net.projecttl.p.x32.func.Conf import net.projecttl.p.x32.func.Conf
object MsgPurge : GlobalCommand { object MsgPurge : GlobalCommand {
override val data = CommandData.fromData( override val data = useCommand {
CommandDataImpl("purge", "n개의 메시지를 채널에서 삭제해요").apply { name = "purge"
addOption(OptionType.INTEGER, "n", "n개만큼 메시지가 삭제 됩니다", true) description = "n개의 메시지를 채널에서 삭제해요"
}.toData() option {
) type = OptionType.INTEGER
name = "n"
description = "n개만큼 메시지가 삭제 됩니다"
required = true
}
}
override suspend fun execute(ev: SlashCommandInteractionEvent) { override suspend fun execute(ev: SlashCommandInteractionEvent) {
val n = ev.getOption("n")!!.asInt val n = ev.getOption("n")!!.asInt

View file

@ -6,16 +6,16 @@ import net.dv8tion.jda.api.entities.MessageEmbed
import net.dv8tion.jda.api.entities.User import net.dv8tion.jda.api.entities.User
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
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.projecttl.p.x32.api.command.GlobalCommand 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.colour
import net.projecttl.p.x32.api.util.footer import net.projecttl.p.x32.api.util.footer
object Ping : GlobalCommand { object Ping : GlobalCommand {
override val data: CommandData = CommandData.fromData(CommandDataImpl( override val data: CommandData = useCommand {
"ping", name = "ping"
"Discord API 레이턴시 확인 합니다" description = "Discord API 레이턴시 확인 합니다"
).toData()) }
override suspend fun execute(ev: SlashCommandInteractionEvent) { override suspend fun execute(ev: SlashCommandInteractionEvent) {
val started = System.currentTimeMillis() val started = System.currentTimeMillis()