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
group=net.projecttl
version=0.2.3-SNAPSHOT
version=0.3.0-SNAPSHOT
ktor_version=2.3.12
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.launch
import net.dv8tion.jda.api.JDA
import net.projecttl.p.x32.api.BotConfig
import net.projecttl.p.x32.command.Info
import net.projecttl.p.x32.command.PluginCommand
import net.projecttl.p.x32.command.Reload
import net.projecttl.p.x32.config.Config
import net.projecttl.p.x32.config.DefaultConfig
import net.projecttl.p.x32.kernel.CoreKernel
import org.jetbrains.exposed.sql.Database
import org.slf4j.Logger
import org.slf4j.LoggerFactory
lateinit var jda: JDA
private set
lateinit var kernel: CoreKernel
lateinit var database: Database
private set
val logger: Logger = LoggerFactory.getLogger(Px32::class.java)
@OptIn(DelicateCoroutinesApi::class)
fun main() {
fun main(args: Array<out String>) {
println("Px32 version v${DefaultConfig.version}")
if (Config.owner.isBlank() || Config.owner.isEmpty()) {
if (BotConfig.owner.isBlank() || BotConfig.owner.isEmpty()) {
logger.warn("owner option is blank or empty!")
}
kernel = CoreKernel(Config.token)
val handler = kernel.getCommandContainer()
kernel = CoreKernel(BotConfig.token)
val handler = kernel.commandContainer
if (args.contains("--remove-cmd")) {
jda = kernel.build()
try {
jda.retrieveCommands().queue {
if (it == null) {
return@queue
}
it.forEach { command ->
logger.info("unregister command: /${command.name}")
command.jda.deleteCommandById(command.id).queue()
}
}
} catch (ex: Exception) {
ex.printStackTrace()
}
kernel.kill()
return
}
handler.addCommand(Info)
handler.addCommand(Reload)

View file

@ -3,23 +3,23 @@ package net.projecttl.p.x32.command
import net.dv8tion.jda.api.JDAInfo
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.interactions.commands.build.CommandData
import net.dv8tion.jda.internal.interactions.CommandDataImpl
import net.projecttl.p.x32.api.command.GlobalCommand
import net.projecttl.p.x32.api.command.useCommand
import net.projecttl.p.x32.config.DefaultConfig
import net.projecttl.p.x32.kernel.CoreKernel.PluginLoader
import net.projecttl.p.x32.kernel
import java.lang.management.ManagementFactory
object Info : GlobalCommand {
override val data: CommandData = CommandData.fromData(
CommandDataImpl("info","봇의 정보를 표시 합니다").toData()
)
override val data: CommandData = useCommand {
name = "info"
description = "봇의 정보를 표시 합니다"
}
override suspend fun execute(ev: SlashCommandInteractionEvent) {
val rb = ManagementFactory.getRuntimeMXBean()
val r = Runtime.getRuntime()
val size = PluginLoader.getPlugins().size
val size = kernel.plugins.size
val info = """
Px32Bot v${DefaultConfig.version}, JDA `v${JDAInfo.VERSION}`,
`Java ${System.getProperty("java.version")}` and `Kotlin ${KotlinVersion.CURRENT}` System on `${System.getProperty("os.name")}`

View file

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

View file

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

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.requests.GatewayIntent
import net.dv8tion.jda.api.utils.MemberCachePolicy
import net.projecttl.p.x32.api.BotConfig
import net.projecttl.p.x32.api.Plugin
import net.projecttl.p.x32.api.command.CommandHandler
import net.projecttl.p.x32.api.model.PluginConfig
import net.projecttl.p.x32.config.Config
import net.projecttl.p.x32.config.DefaultConfig
import net.projecttl.p.x32.func.BundleModule
import net.projecttl.p.x32.logger
import java.io.File
@ -19,69 +20,67 @@ import java.nio.charset.Charset
import java.util.jar.JarFile
class CoreKernel(token: String) {
lateinit var jda: JDA
private set
private val builder = JDABuilder.createDefault(token, listOf(
GatewayIntent.GUILD_PRESENCES,
GatewayIntent.GUILD_MEMBERS,
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.MESSAGE_CONTENT,
GatewayIntent.GUILD_PRESENCES,
GatewayIntent.SCHEDULED_EVENTS,
GatewayIntent.GUILD_VOICE_STATES,
GatewayIntent.GUILD_EMOJIS_AND_STICKERS,
GatewayIntent.SCHEDULED_EVENTS
GatewayIntent.GUILD_EMOJIS_AND_STICKERS
)).setMemberCachePolicy(MemberCachePolicy.ALL)
private val handlers = mutableListOf<ListenerAdapter>()
private val commandContainer = CommandHandler()
val memLock = Mutex()
val plugins get() = PluginLoader.getPlugins().map { it.value }
val commandContainer = CommandHandler()
var plugins = mutableMapOf<PluginConfig, Plugin>()
private set
var isActive = false
private set
private fun include() {
if (Config.bundle) {
val b = BundleModule()
PluginLoader.putModule(b.config, b)
private val parentDir = File("./plugins").apply {
if (!exists()) {
mkdirs()
}
}
fun getCommandContainer(): CommandHandler {
return commandContainer
val handlers: List<ListenerAdapter>
get() {
if (!isActive) {
return listOf()
}
return jda.eventManager.registeredListeners.map { it as ListenerAdapter }
}
fun addHandler(handler: ListenerAdapter) {
handlers.add(handler)
if (isActive) {
jda.addEventListener(handler)
return
}
builder.addEventListeners(handler)
}
fun delHandler(handler: ListenerAdapter) {
handlers.remove(handler)
if (isActive) {
jda.removeEventListener(handler)
return
}
fun build(): JDA {
include()
PluginLoader.load()
plugins.forEach { plugin ->
plugin.handlers.forEach { handler ->
handlers.add(handler)
}
}
handlers.map {
logger.info("Load event listener: ${it::class.simpleName}")
builder.addEventListeners(it)
}
builder.addEventListeners(commandContainer)
return builder.build()
builder.removeEventListeners(handler)
}
fun register(jda: JDA) {
commandContainer.register(jda)
handlers.forEach { h ->
if (h is CommandHandler) {
jda.eventManager.registeredListeners.filterIsInstance<CommandHandler>().forEach { h ->
h.register(jda)
}
}
Runtime.getRuntime().addShutdownHook(Thread {
PluginLoader.destroy()
destroy()
})
}
@ -90,60 +89,26 @@ class CoreKernel(token: String) {
memLock.lock()
}
PluginLoader.destroy()
plugins.forEach { plugin ->
plugin.handlers.filter { handlers.contains(it) }.map {
handlers.remove(it)
}
}
include()
PluginLoader.load()
plugins.forEach { plugin ->
plugin.handlers.forEach { handler ->
if (!handlers.contains(handler)) {
handlers.add(handler)
jda.addEventListener(handler)
}
}
}
handlers.forEach { h ->
if (h is CommandHandler) {
destroy()
load()
handlers.filterIsInstance<CommandHandler>().forEach { h ->
h.register(jda)
}
}
memLock.unlock()
}
object PluginLoader {
private val plugins = mutableMapOf<PluginConfig, Plugin>()
private val parentDir = File("./plugins").apply {
if (!exists()) {
mkdirs()
}
fun kill() {
destroy()
jda.shutdownNow()
}
fun getPlugins(): Map<PluginConfig, Plugin> {
return plugins.toMap()
private fun load() {
if (BotConfig.bundle) {
val b = BundleModule()
loadModule(b.config, b)
}
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 ->
try {
loadPlugin(file)
@ -152,28 +117,35 @@ class CoreKernel(token: String) {
}
}
logger.info("Loaded ${plugins.size} plugins")
var cnt = 0
plugins.forEach { (_, plugin) ->
plugin.handlers.forEach {
addHandler(it)
logger.info("Load event listener: ${it::class.simpleName}")
cnt++
}
}
fun destroy() {
val unloaded = mutableListOf<PluginConfig>()
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()
}
unloaded += config
}
unloaded.forEach {
plugins.remove(it)
}
plugins = mutableMapOf()
}
private fun loadPlugin(file: File) {
@ -188,7 +160,7 @@ class CoreKernel(token: String) {
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")
throw IllegalAccessException("${file.name} is not a plugin. aborted")
val stream = jar.getInputStream(cnf)
val raw = stream.use {
@ -200,17 +172,50 @@ class CoreKernel(token: String) {
val obj = cl.loadClass(config.main).getDeclaredConstructor().newInstance()
if (obj !is Plugin)
return logger.error("${config.name} is not valid main class. aborted")
throw IllegalAccessException("${config.name} is not valid plugin class. aborted")
try {
logger.info("Load plugin ${config.name} v${config.version}")
obj.onLoad()
loadModule(config, obj)
} catch (ex: Exception) {
ex.printStackTrace()
return logger.error("Failed to load plugin ${config.name}")
throw ex
}
}
plugins[config] = obj
}
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.events.interaction.command.UserContextInteractionEvent
import net.dv8tion.jda.api.interactions.commands.build.CommandData
import net.dv8tion.jda.internal.interactions.CommandDataImpl
import net.projecttl.p.x32.api.command.UserContext
import net.projecttl.p.x32.api.command.useCommand
import net.projecttl.p.x32.api.util.colour
import net.projecttl.p.x32.api.util.footer
object Avatar : UserContext {
override val data = CommandData.fromData(CommandDataImpl("avatar", "유저의 프로필 이미지를 가져 옵니다").toData())
override val data = useCommand {
name = "avatar"
description = "유저의 프로필 이미지를 가져 옵니다"
}
override suspend fun execute(ev: UserContextInteractionEvent) {
val embed = EmbedBuilder()

View file

@ -3,9 +3,8 @@ package net.projecttl.p.x32.func.command
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.interactions.commands.OptionType
import net.dv8tion.jda.api.interactions.commands.build.CommandData
import net.dv8tion.jda.internal.interactions.CommandDataImpl
import net.projecttl.p.x32.api.command.GlobalCommand
import net.projecttl.p.x32.api.command.useCommand
import net.projecttl.p.x32.api.util.colour
import net.projecttl.p.x32.api.util.footer
import java.math.RoundingMode
@ -13,10 +12,24 @@ import java.text.DecimalFormat
import kotlin.math.pow
object Bmi : GlobalCommand {
override val data = CommandData.fromData(CommandDataImpl("bmi", "키와 몸무게 기반으로 bmi지수를 계산할 수 있어요").apply {
addOption(OptionType.NUMBER, "height", "신장 길이를 적어 주세요 (cm)", true)
addOption(OptionType.NUMBER, "weight", "몸무게를 적어 주세요 (kg)", true)
}.toData())
override val data = useCommand {
name = "bmi"
description = "키와 몸무게 기반으로 bmi 지수를 계산해요"
option {
type = OptionType.NUMBER
name = "height"
description = "신장 길이를 적어 주세요 (cm)"
required = true
}
option {
type = OptionType.NUMBER
name = "weight"
description = "몸무게를 적어 주세요 (kg)"
required = true
}
}
override suspend fun execute(ev: SlashCommandInteractionEvent) {
val height = ev.getOption("height")!!.asDouble

View file

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

View file

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

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

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