mirror of
https://github.com/devproje/px32-bot.git
synced 2024-11-26 10:43:05 +00:00
feat: add async task container
This commit is contained in:
parent
867a7c4ff7
commit
74fb7c7e04
14 changed files with 206 additions and 131 deletions
|
@ -3,11 +3,13 @@ package net.projecttl.p.x32.api
|
|||
import kotlinx.serialization.json.Json
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
import net.projecttl.p.x32.api.model.PluginConfig
|
||||
import net.projecttl.p.x32.api.util.AsyncTaskContainer
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
abstract class Plugin {
|
||||
private val handlerContainer = mutableListOf<ListenerAdapter>()
|
||||
val taskContainer = AsyncTaskContainer()
|
||||
|
||||
val config = this.javaClass.getResourceAsStream("/plugin.json")!!.let {
|
||||
val raw = it.bufferedReader().readText()
|
||||
|
|
|
@ -3,7 +3,6 @@ package net.projecttl.p.x32.api.command
|
|||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.dv8tion.jda.api.JDA
|
||||
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
|
||||
|
@ -11,8 +10,8 @@ import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEven
|
|||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands
|
||||
|
||||
fun commandHandler(block: (CommandHandler) -> Unit): CommandHandler {
|
||||
val handler = CommandHandler()
|
||||
fun commandHandler(guildId: Long = 0L, block: (CommandHandler) -> Unit): CommandHandler {
|
||||
val handler = CommandHandler(guildId)
|
||||
block.invoke(handler)
|
||||
|
||||
return handler
|
||||
|
@ -25,15 +24,14 @@ class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() {
|
|||
override fun onSlashCommandInteraction(ev: SlashCommandInteractionEvent) {
|
||||
val name = ev.interaction.name
|
||||
|
||||
commands.forEach { command ->
|
||||
if (command.data.name == name) {
|
||||
commands.singleOrNull { it.data.name == name }?.also { command ->
|
||||
if (command is GlobalCommand) {
|
||||
GlobalScope.launch {
|
||||
command.execute(ev)
|
||||
println("${ev.user.id} is use command for: ${ev.interaction.name}")
|
||||
}
|
||||
|
||||
return@forEach
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,15 +39,14 @@ class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() {
|
|||
override fun onUserContextInteraction(ev: UserContextInteractionEvent) {
|
||||
val name = ev.interaction.name
|
||||
|
||||
commands.forEach { command ->
|
||||
if (command.data.name == name) {
|
||||
commands.singleOrNull { it.data.name == name }?.also { command ->
|
||||
if (command is UserContext) {
|
||||
GlobalScope.launch {
|
||||
command.execute(ev)
|
||||
println("${ev.user.id} is use user context for: ${ev.interaction.name}")
|
||||
}
|
||||
|
||||
return@forEach
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,15 +54,14 @@ class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() {
|
|||
override fun onMessageContextInteraction(ev: MessageContextInteractionEvent) {
|
||||
val name = ev.interaction.name
|
||||
|
||||
commands.forEach { command ->
|
||||
if (command.data.name == name) {
|
||||
commands.singleOrNull { it.data.name == name }?.also { command ->
|
||||
if (command is MessageContext) {
|
||||
GlobalScope.launch {
|
||||
command.execute(ev)
|
||||
println("${ev.user.id} is use message context for: ${ev.interaction.name}")
|
||||
}
|
||||
|
||||
return@forEach
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,16 +38,24 @@ class AsyncTask(val loop: Boolean = true, val block: suspend () -> Unit) {
|
|||
* This feature is Experimental
|
||||
*/
|
||||
class AsyncTaskContainer {
|
||||
private val tasks = mutableMapOf<Long, AsyncTask>()
|
||||
val tasks = mutableMapOf<Long, AsyncTask>()
|
||||
private var tid = 0L
|
||||
|
||||
fun getTask(tid: Long): AsyncTask? {
|
||||
return tasks[tid]
|
||||
}
|
||||
|
||||
fun createTask(task: AsyncTask) {
|
||||
suspend fun createTask(task: AsyncTask) {
|
||||
tasks[tid] = task
|
||||
println("created task with id: $tid")
|
||||
try {
|
||||
task.run()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
tasks.remove(tid)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
tid++
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package net.projecttl.p.x32.api.util
|
|||
|
||||
import net.dv8tion.jda.api.EmbedBuilder
|
||||
import net.dv8tion.jda.api.entities.User
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.random.Random
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import net.dv8tion.jda.api.interactions.commands.build.CommandData
|
|||
import net.dv8tion.jda.internal.interactions.CommandDataImpl
|
||||
import net.projecttl.p.x32.api.command.GlobalCommand
|
||||
import net.projecttl.p.x32.config.DefaultConfig
|
||||
import net.projecttl.p.x32.kernel.PluginLoader
|
||||
import net.projecttl.p.x32.kernel.CoreKernel.PluginLoader
|
||||
import java.lang.management.ManagementFactory
|
||||
|
||||
object Info : GlobalCommand {
|
||||
|
|
|
@ -8,7 +8,7 @@ import net.dv8tion.jda.internal.interactions.CommandDataImpl
|
|||
import net.projecttl.p.x32.api.command.GlobalCommand
|
||||
import net.projecttl.p.x32.api.util.colour
|
||||
import net.projecttl.p.x32.api.util.footer
|
||||
import net.projecttl.p.x32.kernel.PluginLoader
|
||||
import net.projecttl.p.x32.kernel.CoreKernel.PluginLoader
|
||||
|
||||
object PluginCommand : GlobalCommand {
|
||||
override val data = CommandData.fromData(CommandDataImpl("plugin", "봇에 불러온 플러그인을 확인 합니다").toData())
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
package net.projecttl.p.x32.kernel
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.dv8tion.jda.api.JDA
|
||||
import net.dv8tion.jda.api.JDABuilder
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent
|
||||
import net.projecttl.p.x32.api.Plugin
|
||||
import net.projecttl.p.x32.api.command.CommandHandler
|
||||
import net.projecttl.p.x32.api.model.PluginConfig
|
||||
import net.projecttl.p.x32.api.util.AsyncTaskContainer
|
||||
import net.projecttl.p.x32.config.Config
|
||||
import net.projecttl.p.x32.func.BundleModule
|
||||
import net.projecttl.p.x32.logger
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.charset.Charset
|
||||
import java.util.jar.JarFile
|
||||
|
||||
class CoreKernel(token: String) {
|
||||
private val builder = JDABuilder.createDefault(token, listOf(
|
||||
|
@ -116,4 +124,88 @@ class CoreKernel(token: String) {
|
|||
|
||||
memLock.unlock()
|
||||
}
|
||||
|
||||
object PluginLoader {
|
||||
private val plugins = mutableMapOf<PluginConfig, Plugin>()
|
||||
private val parentDir = File("./plugins").apply {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlugins(): Map<PluginConfig, Plugin> {
|
||||
return plugins.toMap()
|
||||
}
|
||||
|
||||
fun putModule(config: PluginConfig, plugin: Plugin) {
|
||||
try {
|
||||
logger.info("Load module ${config.name} v${config.version}")
|
||||
plugin.onLoad()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
plugin.destroy()
|
||||
return
|
||||
}
|
||||
|
||||
plugins[config] = plugin
|
||||
}
|
||||
|
||||
fun load() {
|
||||
parentDir.listFiles()?.forEach { file ->
|
||||
loadPlugin(file)
|
||||
}
|
||||
|
||||
logger.info("Loaded ${plugins.size} plugins")
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
plugins.forEach { (config, plugin) ->
|
||||
logger.info("disable ${config.name} plugin...")
|
||||
|
||||
try {
|
||||
plugin.destroy()
|
||||
} catch (ex: Exception) {
|
||||
logger.error("failed to destroy ${config.name} plugin")
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPlugin(file: File) {
|
||||
if (file.name == "px32-bot-module") {
|
||||
return
|
||||
}
|
||||
|
||||
if (!file.name.endsWith(".jar")) {
|
||||
return
|
||||
}
|
||||
|
||||
val jar = JarFile(file)
|
||||
val cnf = jar.entries().toList().singleOrNull { jarEntry -> jarEntry.name == "plugin.json" }
|
||||
if (cnf == null)
|
||||
return logger.error("${file.name} is not a plugin. aborted")
|
||||
|
||||
val stream = jar.getInputStream(cnf)
|
||||
val raw = stream.use {
|
||||
return@use it.readBytes().toString(Charset.forName("UTF-8"))
|
||||
}
|
||||
|
||||
val config = Json.decodeFromString<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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
package net.projecttl.p.x32.kernel
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.projecttl.p.x32.api.Plugin
|
||||
import net.projecttl.p.x32.api.model.PluginConfig
|
||||
import net.projecttl.p.x32.logger
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.charset.Charset
|
||||
import java.util.jar.JarFile
|
||||
|
||||
object PluginLoader {
|
||||
private val plugins = mutableMapOf<PluginConfig, Plugin>()
|
||||
private val parentDir = File("./plugins").apply {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlugins(): Map<PluginConfig, Plugin> {
|
||||
return plugins.toMap()
|
||||
}
|
||||
|
||||
fun putModule(config: PluginConfig, plugin: Plugin) {
|
||||
try {
|
||||
logger.info("Load module ${config.name} v${config.version}")
|
||||
plugin.onLoad()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
plugin.destroy()
|
||||
return
|
||||
}
|
||||
|
||||
plugins[config] = plugin
|
||||
}
|
||||
|
||||
fun load() {
|
||||
parentDir.listFiles()?.forEach { file ->
|
||||
loadPlugin(file)
|
||||
}
|
||||
|
||||
logger.info("Loaded ${plugins.size} plugins")
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
plugins.forEach { (config, plugin) ->
|
||||
logger.info("disable ${config.name} plugin...")
|
||||
|
||||
try {
|
||||
plugin.destroy()
|
||||
} catch (ex: Exception) {
|
||||
logger.error("failed to destroy ${config.name} plugin")
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPlugin(file: File) {
|
||||
if (file.name == "px32-bot-module") {
|
||||
return
|
||||
}
|
||||
|
||||
if (!file.name.endsWith(".jar")) {
|
||||
return
|
||||
}
|
||||
|
||||
val jar = JarFile(file)
|
||||
val cnf = jar.entries().toList().singleOrNull { jarEntry -> jarEntry.name == "plugin.json" }
|
||||
if (cnf == null)
|
||||
return logger.error("${file.name} is not a plugin. aborted")
|
||||
|
||||
val stream = jar.getInputStream(cnf)
|
||||
val raw = stream.use {
|
||||
return@use it.readBytes().toString(Charset.forName("UTF-8"))
|
||||
}
|
||||
|
||||
val config = Json.decodeFromString<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
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package net.projecttl.p.x32.kernel
|
||||
|
||||
object TaskManager {
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
token=<discord_bot_token>
|
||||
owner=
|
||||
|
||||
# Px32's Default module activation
|
||||
# setting value is 1 or 0
|
||||
bundle_func=1
|
||||
|
||||
# Database connection settings
|
||||
db_driver=org.sqlite.JDBC
|
||||
db_url=jdbc:sqlite:data.db
|
||||
db_username=
|
||||
|
|
|
@ -17,6 +17,8 @@ class BundleModule : Plugin() {
|
|||
addHandler(Ready)
|
||||
addHandler(commandHandler { handler ->
|
||||
handler.addCommand(Avatar)
|
||||
handler.addCommand(Bmi)
|
||||
handler.addCommand(Dice)
|
||||
handler.addCommand(MsgLength)
|
||||
handler.addCommand(MsgPurge)
|
||||
handler.addCommand(Ping)
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package net.projecttl.p.x32.func.command
|
||||
|
||||
import net.dv8tion.jda.api.EmbedBuilder
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData
|
||||
import net.dv8tion.jda.internal.interactions.CommandDataImpl
|
||||
import net.projecttl.p.x32.api.command.GlobalCommand
|
||||
import net.projecttl.p.x32.api.util.colour
|
||||
import net.projecttl.p.x32.api.util.footer
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.pow
|
||||
|
||||
object Bmi : GlobalCommand {
|
||||
override val data = CommandData.fromData(CommandDataImpl("bmi", "키와 몸무게 기반으로 bmi지수를 계산할 수 있어요").apply {
|
||||
addOption(OptionType.NUMBER, "height", "신장 길이를 적어 주세요 (cm)", true)
|
||||
addOption(OptionType.NUMBER, "weight", "몸무게를 적어 주세요 (kg)", true)
|
||||
}.toData())
|
||||
|
||||
override suspend fun execute(ev: SlashCommandInteractionEvent) {
|
||||
val height = ev.getOption("height")!!.asDouble
|
||||
val weight = ev.getOption("weight")!!.asDouble
|
||||
|
||||
if (height <= 0.0 || weight <= 0.0) {
|
||||
ev.reply(":warning: 키 또는 몸무게가 0 또는 음수일 수 없어요").setEphemeral(true).queue()
|
||||
return
|
||||
}
|
||||
|
||||
val bmi = weight / (height / 100).pow(2)
|
||||
fun result(bmi: Double): String {
|
||||
return when {
|
||||
bmi < 18.5 -> "**저체중**"
|
||||
bmi in 18.5..24.9 -> "**정상 체중**"
|
||||
bmi in 25.0.. 29.9 -> "**과체중**"
|
||||
else -> "**비만**"
|
||||
}
|
||||
}
|
||||
|
||||
val df = DecimalFormat("#.##")
|
||||
df.roundingMode = RoundingMode.HALF_UP
|
||||
|
||||
ev.replyEmbeds(EmbedBuilder().apply {
|
||||
setTitle(":pencil: BMI 지수가 나왔어요")
|
||||
setDescription("${result(bmi)} 이에요")
|
||||
addField(":straight_ruler: **신장 길이**", "`${height}cm`", true)
|
||||
addField(":scales: **몸무게**", "`${weight}kg`", true)
|
||||
addField(":chart_with_downwards_trend: **BMI 지수**", "`${df.format(bmi)}` ", true)
|
||||
|
||||
colour()
|
||||
footer(ev.user)
|
||||
}.build()).queue()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package net.projecttl.p.x32.func.command
|
||||
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData
|
||||
import net.dv8tion.jda.internal.interactions.CommandDataImpl
|
||||
import net.projecttl.p.x32.api.command.GlobalCommand
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextInt
|
||||
|
||||
object Dice : GlobalCommand {
|
||||
override val data = CommandData.fromData(CommandDataImpl("dice", "랜덤으로 주사위를 굴립니다").toData())
|
||||
|
||||
override suspend fun execute(ev: SlashCommandInteractionEvent) {
|
||||
val rand = Random.nextInt(1..6)
|
||||
ev.reply(":game_die: **${rand}**").queue()
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package net.projecttl.p.x32.func.command
|
||||
|
||||
import net.projecttl.p.x32.func.Conf
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData
|
||||
import net.dv8tion.jda.internal.interactions.CommandDataImpl
|
||||
import net.projecttl.p.x32.api.command.GlobalCommand
|
||||
import net.projecttl.p.x32.func.Conf
|
||||
|
||||
object MsgPurge : GlobalCommand {
|
||||
override val data = CommandData.fromData(
|
||||
|
|
Loading…
Reference in a new issue