mirror of
https://github.com/devproje/px32-bot.git
synced 2025-04-19 22:59:53 +09:00
Compare commits
20 commits
Author | SHA1 | Date | |
---|---|---|---|
5a13eb5376 | |||
a48e25b33e | |||
6cad37f299 | |||
cf3089a097 | |||
34854efe26 | |||
67696cd3a2 | |||
dbeb750733 | |||
4c525fcb4c | |||
83319facb2 | |||
b5aaae092b | |||
208084b617 | |||
b526a5ef57 | |||
e9f17fcd78 | |||
a360e53d0f | |||
60965ee92e | |||
75a3b73993 | |||
74fb7c7e04 | |||
867a7c4ff7 | |||
8c9b4a1112 | |||
bc4c985735 |
34 changed files with 939 additions and 345 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Project_IO
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
71
README.md
Normal file
71
README.md
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# Px32 Bot
|
||||||
|
- [JDA](https://github.com/discord-jda/JDA)로 만든 Kotlin 디스코드 봇 구동기 입니다.
|
||||||
|
|
||||||
|
## Project Repo
|
||||||
|
- [Gitlab](https://gitlab.wh64.net/devproje/px32-bot.git)
|
||||||
|
- [Github](https://github.com/devproje/px32-bot.git)
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
봇 구동기는 [이곳](https://github.com/devproje/px32-bot/releases)에서 다운로드 받으실 수 있습니다.
|
||||||
|
|
||||||
|
## Developments
|
||||||
|
API를 사용하여 플러그인 개발을 원하신다면 아래의 코드로 라이브러리를 추가 해 주세요.
|
||||||
|
JDA의 버전은 [이곳](https://github.com/discord-jda/JDA/releases)에서 확인 하실 수 있습니다.
|
||||||
|
|
||||||
|
- Maven
|
||||||
|
```xml
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>ProjectCentral</id>
|
||||||
|
<url>https://repo.wh64.net/repository/maven-releases/</url>
|
||||||
|
</repository>
|
||||||
|
|
||||||
|
// If you want to snapshot version, please write this code.
|
||||||
|
<repository>
|
||||||
|
<id>ProjectCentral-SNAPSHOT</id>
|
||||||
|
<url>https://repo.wh64.net/repository/maven-snapshots/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependencies>
|
||||||
|
...
|
||||||
|
<!-- JDA is required -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.dv8tion</groupId>
|
||||||
|
<artifactId>JDA</artifactId>
|
||||||
|
<version>VERSION</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.projecttl</groupId>
|
||||||
|
<artifactId>px32-bot-api</artifactId>
|
||||||
|
<version>VERSION</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Gradle
|
||||||
|
```kts
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://repo.wh64.net/repository/maven-releases/")
|
||||||
|
|
||||||
|
// If you want to snapshot version, please write this code.
|
||||||
|
maven("https://repo.wh64.net/repository/maven-snapshots/")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```kts
|
||||||
|
dependencies {
|
||||||
|
...
|
||||||
|
// JDA is required
|
||||||
|
compileOnly("net.dv8tion:JDA:VERSION")
|
||||||
|
compileOnly("net.projecttl:px32-bot-api:VERSION")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
- 해당 프로젝트는 [MIT License](https://gitlab.wh64.net/devproje/px32-bot/-/blob/master/LICENSE)를 따릅니다.
|
|
@ -1,5 +1,6 @@
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "2.0.20"
|
kotlin("jvm") version "2.0.20"
|
||||||
|
id("org.jetbrains.dokka") version "1.9.20"
|
||||||
kotlin("plugin.serialization") version "2.0.20"
|
kotlin("plugin.serialization") version "2.0.20"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ val sqlite_version: String by project
|
||||||
val postgres_version: String by project
|
val postgres_version: String by project
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
|
apply(plugin = "org.jetbrains.dokka")
|
||||||
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")
|
||||||
|
|
||||||
|
|
|
@ -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.1.0-SNAPSHOT
|
version=1.0.0-beta.3
|
||||||
|
|
||||||
ktor_version=2.3.12
|
ktor_version=2.3.12
|
||||||
log4j_version=2.23.1
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,24 @@ package net.projecttl.p.x32.api
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||||
import net.projecttl.p.x32.api.model.PluginConfig
|
import net.projecttl.p.x32.api.model.PluginConfig
|
||||||
|
import net.projecttl.p.x32.api.util.AsyncTaskContainer
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
abstract class Plugin {
|
abstract class Plugin {
|
||||||
private val handlerContainer = mutableListOf<ListenerAdapter>()
|
private val handlerContainer = mutableListOf<ListenerAdapter>()
|
||||||
|
val taskContainer = AsyncTaskContainer()
|
||||||
|
val pluginDataDir: File
|
||||||
|
get() {
|
||||||
|
val f = File("plugins/${config.name}")
|
||||||
|
if (!f.exists()) {
|
||||||
|
f.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
val config = this.javaClass.getResourceAsStream("/plugin.json")!!.let {
|
val config = this.javaClass.getResourceAsStream("/plugin.json")!!.let {
|
||||||
val raw = it.bufferedReader().readText()
|
val raw = it.bufferedReader().readText()
|
||||||
val obj = Json.decodeFromString<PluginConfig>(raw)
|
val obj = Json.decodeFromString<PluginConfig>(raw)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package net.projecttl.p.x32.api.command
|
package net.projecttl.p.x32.api.command
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import net.dv8tion.jda.api.JDA
|
import net.dv8tion.jda.api.JDA
|
||||||
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent
|
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent
|
||||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
|
||||||
|
@ -8,21 +10,28 @@ import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEven
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||||
import net.dv8tion.jda.api.interactions.commands.build.Commands
|
import net.dv8tion.jda.api.interactions.commands.build.Commands
|
||||||
|
|
||||||
|
fun commandHandler(guildId: Long = 0L, block: (CommandHandler) -> Unit): CommandHandler {
|
||||||
|
val handler = CommandHandler(guildId)
|
||||||
|
block.invoke(handler)
|
||||||
|
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() {
|
class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() {
|
||||||
private val commands = mutableListOf<CommandExecutor>()
|
private val commands = mutableListOf<CommandExecutor>()
|
||||||
|
|
||||||
override fun onSlashCommandInteraction(ev: SlashCommandInteractionEvent) {
|
override fun onSlashCommandInteraction(ev: SlashCommandInteractionEvent) {
|
||||||
val name = ev.interaction.name
|
val name = ev.interaction.name
|
||||||
|
|
||||||
commands.forEach { command ->
|
commands.singleOrNull { it.data.name == name }?.also { command ->
|
||||||
if (command.data.name == name) {
|
if (command is GlobalCommand) {
|
||||||
if (command is GlobalCommand) {
|
GlobalScope.launch {
|
||||||
runBlocking {
|
command.execute(ev)
|
||||||
command.execute(ev)
|
println("${ev.user.id} is use command for: ${ev.interaction.name}")
|
||||||
}
|
|
||||||
|
|
||||||
return@forEach
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,15 +39,14 @@ class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() {
|
||||||
override fun onUserContextInteraction(ev: UserContextInteractionEvent) {
|
override fun onUserContextInteraction(ev: UserContextInteractionEvent) {
|
||||||
val name = ev.interaction.name
|
val name = ev.interaction.name
|
||||||
|
|
||||||
commands.forEach { command ->
|
commands.singleOrNull { it.data.name == name }?.also { command ->
|
||||||
if (command.data.name == name) {
|
if (command is UserContext) {
|
||||||
if (command is UserContext) {
|
GlobalScope.launch {
|
||||||
runBlocking {
|
command.execute(ev)
|
||||||
command.execute(ev)
|
println("${ev.user.id} is use user context for: ${ev.interaction.name}")
|
||||||
}
|
|
||||||
|
|
||||||
return@forEach
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,15 +54,14 @@ class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() {
|
||||||
override fun onMessageContextInteraction(ev: MessageContextInteractionEvent) {
|
override fun onMessageContextInteraction(ev: MessageContextInteractionEvent) {
|
||||||
val name = ev.interaction.name
|
val name = ev.interaction.name
|
||||||
|
|
||||||
commands.forEach { command ->
|
commands.singleOrNull { it.data.name == name }?.also { command ->
|
||||||
if (command.data.name == name) {
|
if (command is MessageContext) {
|
||||||
if (command is MessageContext) {
|
GlobalScope.launch {
|
||||||
runBlocking {
|
command.execute(ev)
|
||||||
command.execute(ev)
|
println("${ev.user.id} is use message context for: ${ev.interaction.name}")
|
||||||
}
|
|
||||||
|
|
||||||
return@forEach
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,10 +113,10 @@ class CommandHandler(val guildId: Long = 0L) : ListenerAdapter() {
|
||||||
|
|
||||||
if (command is MessageContext) {
|
if (command is MessageContext) {
|
||||||
if (guild == null) {
|
if (guild == null) {
|
||||||
jda.upsertCommand(Commands.message(data.name))
|
jda.upsertCommand(Commands.message(data.name)).queue()
|
||||||
println("Register Message Context Command: /${data.name}")
|
println("Register Message Context Command: /${data.name}")
|
||||||
} else {
|
} else {
|
||||||
guild.upsertCommand(Commands.message(data.name))
|
guild.upsertCommand(Commands.message(data.name)).queue()
|
||||||
println("Register '${guild.id}' Guild's Message Context Command: /${data.name}")
|
println("Register '${guild.id}' Guild's Message Context Command: /${data.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
package net.projecttl.p.x32.api.command
|
||||||
|
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.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
|
||||||
|
|
||||||
|
var choices = mutableListOf<Command.Choice>()
|
||||||
|
|
||||||
|
fun build(): OptionData {
|
||||||
|
return OptionData(type, name, description, required, autoComplete).apply {
|
||||||
|
this@OptionObj.choices.forEach {
|
||||||
|
addChoices(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package net.projecttl.p.x32.api.util
|
||||||
|
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class only support by kotlin
|
||||||
|
* This feature is Experimental
|
||||||
|
*/
|
||||||
|
class AsyncTask(val loop: Boolean = true, val block: suspend () -> Unit) {
|
||||||
|
private val memLock = Mutex()
|
||||||
|
var isActive = true
|
||||||
|
private set
|
||||||
|
|
||||||
|
suspend fun run() {
|
||||||
|
do {
|
||||||
|
if (memLock.isLocked) {
|
||||||
|
if (!isActive) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
block.invoke()
|
||||||
|
} while (loop)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun kill() {
|
||||||
|
memLock.lock()
|
||||||
|
isActive = false
|
||||||
|
|
||||||
|
memLock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class only support by kotlin
|
||||||
|
* This feature is Experimental
|
||||||
|
*/
|
||||||
|
class AsyncTaskContainer {
|
||||||
|
val tasks = mutableMapOf<Long, AsyncTask>()
|
||||||
|
private var tid = 0L
|
||||||
|
|
||||||
|
fun getTask(tid: Long): AsyncTask? {
|
||||||
|
return tasks[tid]
|
||||||
|
}
|
||||||
|
|
||||||
|
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++
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removeTask(tid: Long) {
|
||||||
|
val task = getTask(tid) ?: throw NullPointerException("task $tid is missing or not defined")
|
||||||
|
task.kill()
|
||||||
|
|
||||||
|
tasks.remove(tid)
|
||||||
|
println("removed task with id: $tid")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun killAll() {
|
||||||
|
tasks.forEach { (k, t) ->
|
||||||
|
t.kill()
|
||||||
|
tasks.remove(k)
|
||||||
|
println("removed task with id: $tid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
package net.projecttl.p.x32.api.util
|
|
||||||
|
|
||||||
import net.dv8tion.jda.api.EmbedBuilder
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
fun EmbedBuilder.colour(): EmbedBuilder {
|
|
||||||
val rand = Random.nextInt(0x000001, 0xffffff)
|
|
||||||
return this.setColor(rand)
|
|
||||||
}
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package net.projecttl.p.x32.api.util
|
||||||
|
|
||||||
|
import net.dv8tion.jda.api.EmbedBuilder
|
||||||
|
import net.dv8tion.jda.api.entities.User
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
fun EmbedBuilder.colour(): EmbedBuilder {
|
||||||
|
val rand = Random.nextInt(0x000001, 0xffffff)
|
||||||
|
return this.setColor(rand)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun EmbedBuilder.footer(user: User): EmbedBuilder {
|
||||||
|
val date = LocalDateTime.now()
|
||||||
|
val mon = if (date.month.value > 9) {
|
||||||
|
date.month.value
|
||||||
|
} else {
|
||||||
|
"0${date.month.value}"
|
||||||
|
}
|
||||||
|
val str = "${user.name} • ${date.year}-${mon}-${date.dayOfMonth} ${date.hour}:${date.minute}"
|
||||||
|
|
||||||
|
return this.setFooter(str, "${user.avatarUrl}?size=1024")
|
||||||
|
}
|
|
@ -14,9 +14,7 @@ repositories {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":${rootProject.name}-api"))
|
implementation(project(":${rootProject.name}-api"))
|
||||||
implementation(project(":px32-bot-module"))
|
implementation(project(":${rootProject.name}-module"))
|
||||||
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|
|
@ -1,34 +1,62 @@
|
||||||
package net.projecttl.p.x32
|
package net.projecttl.p.x32
|
||||||
|
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
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.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)
|
||||||
|
|
||||||
fun main() {
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
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 {
|
||||||
|
val list = jda.retrieveCommands().complete()
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
list.forEach { command ->
|
||||||
|
logger.info("unregister command: /${command.name}")
|
||||||
|
command.jda.deleteCommandById(command.id).complete()
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.addCommand(Info)
|
||||||
handler.addCommand(Reload)
|
handler.addCommand(Reload)
|
||||||
handler.addCommand(PluginCommand)
|
handler.addCommand(PluginCommand)
|
||||||
|
|
||||||
jda = kernel.build()
|
jda = kernel.build()
|
||||||
|
GlobalScope.launch {
|
||||||
|
kernel.register(jda)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Px32
|
object Px32
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
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.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
|
||||||
|
import java.lang.management.ManagementFactory
|
||||||
|
|
||||||
|
object Info : GlobalCommand {
|
||||||
|
override val data: CommandData = useCommand {
|
||||||
|
name = "info"
|
||||||
|
description = "봇의 정보를 표시 합니다"
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun execute(ev: SlashCommandInteractionEvent) {
|
||||||
|
val rb = ManagementFactory.getRuntimeMXBean()
|
||||||
|
val r = Runtime.getRuntime()
|
||||||
|
|
||||||
|
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")}`
|
||||||
|
|
||||||
|
Process Started on <t:${(System.currentTimeMillis() - rb.uptime)/ 1000L}:R>
|
||||||
|
Bot Process Running on PID `${rb.pid}`
|
||||||
|
|
||||||
|
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
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
ev.reply(info).queue()
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,14 +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.kernel.PluginLoader
|
import net.projecttl.p.x32.api.util.footer
|
||||||
|
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 {
|
||||||
|
@ -18,9 +21,10 @@ object PluginCommand : GlobalCommand {
|
||||||
setThumbnail(ev.jda.selfUser.avatarUrl)
|
setThumbnail(ev.jda.selfUser.avatarUrl)
|
||||||
|
|
||||||
colour()
|
colour()
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,34 @@
|
||||||
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) {
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
kernel.reload()
|
kernel.reload(ev.jda)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
ex.printStackTrace()
|
ex.printStackTrace()
|
||||||
ev.reply(":warning: 플러그인을 다시 불러오는 도중에 오류가 발생 했어요. 자세한 내용은 콘솔을 확인해 주세요!").queue()
|
ev.reply(":warning: 플러그인을 다시 불러오는 도중에 오류가 발생 했어요. 자세한 내용은 콘솔을 확인해 주세요!").queue()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.reply(":white_check_mark: 플러그인을 다시 불러 왔어요!\n불러온 플러그인 수: ${kernel.plugins().size}").queue()
|
ev.reply(":white_check_mark: 플러그인을 다시 불러 왔어요!\n불러온 플러그인 수: ${kernel.plugins.size}").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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,112 +1,221 @@
|
||||||
package net.projecttl.p.x32.kernel
|
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.JDA
|
||||||
import net.dv8tion.jda.api.JDABuilder
|
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.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.config.Config
|
import net.projecttl.p.x32.api.model.PluginConfig
|
||||||
import net.projecttl.p.x32.func.General
|
import net.projecttl.p.x32.config.DefaultConfig
|
||||||
import net.projecttl.p.x32.jda
|
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) {
|
class CoreKernel(token: String) {
|
||||||
var memLock = false
|
lateinit var jda: JDA
|
||||||
private set
|
private set
|
||||||
private val builder = JDABuilder.createDefault(token)
|
|
||||||
private val handlers = mutableListOf<ListenerAdapter>()
|
|
||||||
private val commandContainer = CommandHandler()
|
|
||||||
|
|
||||||
private fun include() {
|
private val builder = JDABuilder.createDefault(token, listOf(
|
||||||
if (Config.bundle) {
|
GatewayIntent.GUILD_MEMBERS,
|
||||||
val b = General()
|
GatewayIntent.GUILD_MESSAGES,
|
||||||
PluginLoader.putModule(b.config, b)
|
GatewayIntent.MESSAGE_CONTENT,
|
||||||
|
GatewayIntent.GUILD_PRESENCES,
|
||||||
|
GatewayIntent.SCHEDULED_EVENTS,
|
||||||
|
GatewayIntent.GUILD_VOICE_STATES,
|
||||||
|
GatewayIntent.GUILD_EMOJIS_AND_STICKERS
|
||||||
|
)).setMemberCachePolicy(MemberCachePolicy.ALL)
|
||||||
|
|
||||||
|
val memLock = Mutex()
|
||||||
|
val commandContainer = CommandHandler()
|
||||||
|
var plugins = mutableMapOf<PluginConfig, Plugin>()
|
||||||
|
private set
|
||||||
|
var isActive = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val parentDir = File("./plugins").apply {
|
||||||
|
if (!exists()) {
|
||||||
|
mkdirs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 plugins(): List<Plugin> {
|
|
||||||
return PluginLoader.getPlugins().map { it.value }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun build(): JDA {
|
|
||||||
include()
|
|
||||||
|
|
||||||
PluginLoader.load()
|
|
||||||
plugins().forEach { plugin ->
|
|
||||||
plugin.handlers.forEach { handler ->
|
|
||||||
handlers.add(handler)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers.map {
|
builder.removeEventListeners(handler)
|
||||||
builder.addEventListeners(it)
|
}
|
||||||
}
|
|
||||||
builder.addEventListeners(commandContainer)
|
|
||||||
|
|
||||||
val jda = builder.build()
|
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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun reload(jda: JDA) {
|
||||||
|
if (!memLock.isLocked) {
|
||||||
|
memLock.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy()
|
||||||
|
load()
|
||||||
|
handlers.filterIsInstance<CommandHandler>().forEach { h ->
|
||||||
|
h.register(jda)
|
||||||
|
}
|
||||||
|
|
||||||
|
memLock.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kill() {
|
||||||
|
isActive = false
|
||||||
|
|
||||||
|
destroy()
|
||||||
|
logger.info("shutdown now Px32 kernel v${DefaultConfig.version}")
|
||||||
|
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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cnt = 0
|
||||||
|
plugins.forEach { (_, plugin) ->
|
||||||
|
plugin.handlers.forEach {
|
||||||
|
addHandler(it)
|
||||||
|
logger.info("Load event listener: ${it::class.simpleName}")
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
plugin.destroy()
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
logger.error("failed to destroy ${config.name} plugin")
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
kill()
|
||||||
})
|
})
|
||||||
|
|
||||||
return jda
|
return jda
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reload() {
|
|
||||||
if (!memLock) {
|
|
||||||
memLock = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val newHandlers = mutableListOf<ListenerAdapter>()
|
|
||||||
PluginLoader.destroy()
|
|
||||||
plugins().forEach { plugin ->
|
|
||||||
plugin.handlers.forEach { handler ->
|
|
||||||
if (handlers.contains(handler)) {
|
|
||||||
jda.removeEventListener(handler)
|
|
||||||
handlers.remove(handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
include()
|
|
||||||
PluginLoader.load()
|
|
||||||
|
|
||||||
plugins().forEach { plugin ->
|
|
||||||
plugin.handlers.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memLock = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,8 +1,11 @@
|
||||||
token=<discord_bot_token>
|
token=<discord_bot_token>
|
||||||
owner=
|
owner=
|
||||||
|
|
||||||
|
# Px32's Default module activation
|
||||||
|
# setting value is 1 or 0
|
||||||
bundle_func=1
|
bundle_func=1
|
||||||
|
|
||||||
|
# Database connection settings
|
||||||
db_driver=org.sqlite.JDBC
|
db_driver=org.sqlite.JDBC
|
||||||
db_url=jdbc:sqlite:data.db
|
db_url=jdbc:sqlite:data.db
|
||||||
db_username=
|
db_username=
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
version=${project.version}
|
version=${rootProject.version}
|
||||||
|
|
|
@ -2,8 +2,8 @@ plugins {
|
||||||
id("java")
|
id("java")
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "net.projecttl"
|
group = rootProject.group
|
||||||
version = "0.1.0-SNAPSHOT"
|
version = rootProject.version
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package net.projecttl.p.x32.func
|
||||||
|
|
||||||
|
import net.projecttl.p.x32.api.Plugin
|
||||||
|
import net.projecttl.p.x32.api.command.commandHandler
|
||||||
|
import net.projecttl.p.x32.func.command.*
|
||||||
|
import net.projecttl.p.x32.func.handler.Ready
|
||||||
|
|
||||||
|
lateinit var instance: BundleModule
|
||||||
|
|
||||||
|
class BundleModule : Plugin() {
|
||||||
|
override fun onLoad() {
|
||||||
|
instance = this
|
||||||
|
|
||||||
|
logger.info("Created by Project_IO")
|
||||||
|
logger.info("Hello! This is Px32's general module!")
|
||||||
|
|
||||||
|
addHandler(Ready)
|
||||||
|
addHandler(commandHandler { handler ->
|
||||||
|
handler.addCommand(Avatar)
|
||||||
|
handler.addCommand(Bmi)
|
||||||
|
handler.addCommand(Dice)
|
||||||
|
handler.addCommand(MsgLength)
|
||||||
|
handler.addCommand(MsgPurge)
|
||||||
|
handler.addCommand(Ping)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun destroy() {
|
||||||
|
logger.info("bye!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
|
package net.projecttl.p.x32.func
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
object Conf {
|
object Conf {
|
||||||
|
private fun useDef(): DefDel {
|
||||||
|
return DefDel()
|
||||||
|
}
|
||||||
|
|
||||||
private fun useConf(): ConfDel {
|
private fun useConf(): ConfDel {
|
||||||
return ConfDel()
|
return ConfDel()
|
||||||
}
|
}
|
||||||
|
|
||||||
val owner: String by useConf()
|
val owner: String by useConf()
|
||||||
|
val version: String by useDef()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ConfDel {
|
private class ConfDel {
|
||||||
|
@ -23,3 +30,15 @@ private class ConfDel {
|
||||||
return props.getProperty(property.name).toString()
|
return props.getProperty(property.name).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DefDel {
|
||||||
|
private val props = Properties()
|
||||||
|
|
||||||
|
init {
|
||||||
|
props.load(this.javaClass.getResourceAsStream("/default.properties"))
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
|
||||||
|
return props.getProperty(property.name).toString()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,33 +0,0 @@
|
||||||
package net.projecttl.p.x32.func
|
|
||||||
|
|
||||||
import net.projecttl.p.x32.api.Plugin
|
|
||||||
import net.projecttl.p.x32.api.command.CommandHandler
|
|
||||||
import net.projecttl.p.x32.func.command.Avatar
|
|
||||||
import net.projecttl.p.x32.func.command.MsgLength
|
|
||||||
import net.projecttl.p.x32.func.command.MsgPurge
|
|
||||||
import net.projecttl.p.x32.func.command.Ping
|
|
||||||
import net.projecttl.p.x32.func.handler.Ready
|
|
||||||
|
|
||||||
|
|
||||||
class General : Plugin() {
|
|
||||||
override fun onLoad() {
|
|
||||||
logger.info("Created by Project_IO")
|
|
||||||
logger.info("Hello! This is Px32's general module!")
|
|
||||||
|
|
||||||
addHandler(Ready)
|
|
||||||
addHandler(with(CommandHandler()) {
|
|
||||||
addCommand(Avatar)
|
|
||||||
addCommand(MsgLength)
|
|
||||||
addCommand(Ping)
|
|
||||||
addCommand(MsgPurge)
|
|
||||||
|
|
||||||
this
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun destroy() {
|
|
||||||
logger.info("bye!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,23 @@ 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
|
||||||
|
|
||||||
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()
|
||||||
embed.setTitle(":frame_photo: ${ev.target.name}'s Avatar")
|
embed.setTitle(":frame_photo: ${ev.target.name}'s Avatar")
|
||||||
embed.setImage("${ev.target.effectiveAvatarUrl}?size=512")
|
embed.setImage("${ev.target.effectiveAvatarUrl}?size=512")
|
||||||
embed.colour()
|
embed.colour()
|
||||||
|
embed.footer(ev.user)
|
||||||
|
|
||||||
ev.replyEmbeds(embed.build()).queue()
|
ev.replyEmbeds(embed.build()).queue()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
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.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
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
object Bmi : GlobalCommand {
|
||||||
|
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
|
||||||
|
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,19 @@
|
||||||
|
package net.projecttl.p.x32.func.command
|
||||||
|
|
||||||
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
|
||||||
|
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 = useCommand {
|
||||||
|
name = "dice"
|
||||||
|
description = "랜덤으로 주사위를 굴립니다"
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun execute(ev: SlashCommandInteractionEvent) {
|
||||||
|
val rand = Random.nextInt(1..6)
|
||||||
|
ev.reply(":game_die: **${rand}**").queue()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,17 @@
|
||||||
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
|
||||||
ev.reply("${target.jumpUrl} 메시지의 길이:\n\t${target.contentRaw.split("\\s+").size}").queue()
|
ev.reply("${target.jumpUrl} 메시지의 길이: ${target.contentRaw.length}").queue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
package net.projecttl.p.x32.func.command
|
package net.projecttl.p.x32.func.command
|
||||||
|
|
||||||
import Conf
|
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -3,17 +3,19 @@ package net.projecttl.p.x32.func.command
|
||||||
import net.dv8tion.jda.api.EmbedBuilder
|
import net.dv8tion.jda.api.EmbedBuilder
|
||||||
import net.dv8tion.jda.api.JDA
|
import net.dv8tion.jda.api.JDA
|
||||||
import net.dv8tion.jda.api.entities.MessageEmbed
|
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.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
|
||||||
|
|
||||||
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()
|
||||||
|
@ -25,17 +27,18 @@ object Ping : GlobalCommand {
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
ev.replyEmbeds(embed).queue {
|
ev.replyEmbeds(embed).queue {
|
||||||
embed = measure(started, ev.jda)
|
embed = measure(started, ev.user, ev.jda)
|
||||||
it.editOriginalEmbeds(embed).queue()
|
it.editOriginalEmbeds(embed).queue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun measure(started: Long, jda: JDA): MessageEmbed {
|
private fun measure(started: Long, user: User, jda: JDA): MessageEmbed {
|
||||||
val embed = EmbedBuilder()
|
val embed = EmbedBuilder()
|
||||||
embed.setTitle(":ping_pong: **Pong!**")
|
embed.setTitle(":ping_pong: **Pong!**")
|
||||||
embed.addField(":electric_plug: **API**", "`${jda.gatewayPing}ms`", true)
|
embed.addField(":electric_plug: **API**", "`${jda.gatewayPing}ms`", true)
|
||||||
embed.addField(":robot: **BOT**", "`${System.currentTimeMillis() - started}ms`", true)
|
embed.addField(":robot: **BOT**", "`${System.currentTimeMillis() - started}ms`", true)
|
||||||
embed.colour()
|
embed.colour()
|
||||||
|
embed.footer(user)
|
||||||
|
|
||||||
return embed.build()
|
return embed.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,36 @@
|
||||||
package net.projecttl.p.x32.func.handler
|
package net.projecttl.p.x32.func.handler
|
||||||
|
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import net.dv8tion.jda.api.OnlineStatus
|
||||||
|
import net.dv8tion.jda.api.entities.Activity
|
||||||
import net.dv8tion.jda.api.events.session.ReadyEvent
|
import net.dv8tion.jda.api.events.session.ReadyEvent
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||||
|
import net.projecttl.p.x32.func.Conf
|
||||||
|
import net.projecttl.p.x32.func.instance
|
||||||
|
|
||||||
object Ready : ListenerAdapter() {
|
object Ready : ListenerAdapter() {
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
override fun onReady(ev: ReadyEvent) {
|
override fun onReady(ev: ReadyEvent) {
|
||||||
|
val list = listOf(
|
||||||
|
Activity.playing("${ev.jda.selfUser.name} v${Conf.version}"),
|
||||||
|
Activity.listening("${ev.guildTotalCount}개의 서버와 함께 서비스 하는 중")
|
||||||
|
)
|
||||||
println("Logged in as ${ev.jda.selfUser.asTag}")
|
println("Logged in as ${ev.jda.selfUser.asTag}")
|
||||||
|
|
||||||
|
GlobalScope.launch {
|
||||||
|
do {
|
||||||
|
list.forEach { act ->
|
||||||
|
try {
|
||||||
|
ev.jda.presence.setPresence(OnlineStatus.ONLINE, act)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
instance.logger.error(ex.message)
|
||||||
|
}
|
||||||
|
delay(1000 * 10L)
|
||||||
|
}
|
||||||
|
} while (true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "${project.name}",
|
"name": "bundle-module",
|
||||||
"version": "${project.version}",
|
"version": "${rootProject.version}",
|
||||||
"main": "net.projecttl.p.x32.func.General"
|
"main": "net.projecttl.p.x32.func.BundleModule"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ class CorePlugin extends Plugin {
|
||||||
logger.info "Hello, World!"
|
logger.info "Hello, World!"
|
||||||
CommandHandler handler = new CommandHandler()
|
CommandHandler handler = new CommandHandler()
|
||||||
handler.addCommand new Greeting()
|
handler.addCommand new Greeting()
|
||||||
|
|
||||||
addHandler handler
|
addHandler handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue