From 73b417775a2d6ac0a3bf9803ceb94d562d03f0f5 Mon Sep 17 00:00:00 2001 From: RTAkland Date: Sun, 13 Oct 2024 14:16:00 +0800 Subject: [PATCH] feat: add rcon command --- build.gradle.kts | 2 + gradle/libs.versions.toml | 2 + src/main/kotlin/cn/rtast/fancybot/Const.kt | 4 +- .../{lookup => misc}/MinecraftCommand.kt | 92 ++++++++++++++++++- .../kotlin/cn/rtast/fancybot/db/RCONTable.kt | 29 ++++++ .../kotlin/cn/rtast/fancybot/util/Database.kt | 2 + .../rtast/fancybot/util/file/RCONManager.kt | 66 +++++++++++++ 7 files changed, 195 insertions(+), 2 deletions(-) rename src/main/kotlin/cn/rtast/fancybot/commands/{lookup => misc}/MinecraftCommand.kt (85%) create mode 100644 src/main/kotlin/cn/rtast/fancybot/db/RCONTable.kt create mode 100644 src/main/kotlin/cn/rtast/fancybot/util/file/RCONManager.kt diff --git a/build.gradle.kts b/build.gradle.kts index d4c97ff..cbf80af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,7 @@ repositories { maven("https://oss.sonatype.org/content/repositories/snapshots") maven("https://repo.rtast.cn/api/v4/projects/33/packages/maven") maven("https://repo.rtast.cn/api/v4/projects/19/packages/maven") + maven("https://repo.rtast.cn/api/v4/projects/3/packages/maven") } tasks.compileKotlin { @@ -46,6 +47,7 @@ dependencies { implementation(libs.logback.classic) implementation(libs.slf4j.api) implementation(libs.aws.s3.sdk) + implementation(libs.rconlib) } tasks.build { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 754541a..7fb0bb4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ zxingVersion = "3.5.3" logbackClassicVersion = "1.5.8" sl4jVersion = "2.0.16" awsS3SDKVersion = "2.28.18" +rconLibVersion = "1.1.0" [libraries] ronebot = { group = "cn.rtast", name = "ROneBot", version.ref = "ronebotVersion" } @@ -31,6 +32,7 @@ zxing-javase = { group = "com.google.zxing", name = "javase", version.ref = "zxi logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logbackClassicVersion" } slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "sl4jVersion" } aws-s3-sdk = { group = "software.amazon.awssdk", name = "s3", version.ref = "awsS3SDKVersion" } +rconlib = { group = "cn.rtast", name = "RCONLib", version.ref = "rconLibVersion" } [bundles] diff --git a/src/main/kotlin/cn/rtast/fancybot/Const.kt b/src/main/kotlin/cn/rtast/fancybot/Const.kt index 86a8d8b..d556128 100644 --- a/src/main/kotlin/cn/rtast/fancybot/Const.kt +++ b/src/main/kotlin/cn/rtast/fancybot/Const.kt @@ -30,6 +30,7 @@ import cn.rtast.fancybot.util.file.BlackListManager import cn.rtast.fancybot.util.file.ConfigManager import cn.rtast.fancybot.util.file.NiuziBankManager import cn.rtast.fancybot.util.file.NiuziManager +import cn.rtast.fancybot.util.file.RCONManager import cn.rtast.fancybot.util.item.ItemManager import cn.rtast.rob.ROneBotFactory import com.google.gson.Gson @@ -89,6 +90,7 @@ val itemManager = ItemManager() val niuziManager = NiuziManager() val niuziBankManager = NiuziBankManager() val blackListManager = BlackListManager() +val rconManager = RCONManager() val START_UP_TIME = Instant.now().epochSecond @@ -124,5 +126,5 @@ val commands = listOf( ZDJDCommand(), ScanQRCodeCommand(), TTSCommand(), SlimeChunkHelperCommand(), SupportMeCommand(), TheCatCommand(), TheHistoryOfTodayCommand(), MCVersionCommand(), MinecraftWikiCommand(), GaoKaoDaysRemainCommand(), IdiomExplainCommand(), NiuziManagerCommand(), - TodayEatCommand(), TodayDrinkCommand(), MCLoginCommand() + TodayEatCommand(), TodayDrinkCommand(), MCLoginCommand(), RCONCommand() ) \ No newline at end of file diff --git a/src/main/kotlin/cn/rtast/fancybot/commands/lookup/MinecraftCommand.kt b/src/main/kotlin/cn/rtast/fancybot/commands/misc/MinecraftCommand.kt similarity index 85% rename from src/main/kotlin/cn/rtast/fancybot/commands/lookup/MinecraftCommand.kt rename to src/main/kotlin/cn/rtast/fancybot/commands/misc/MinecraftCommand.kt index 895ed79..bee1abe 100644 --- a/src/main/kotlin/cn/rtast/fancybot/commands/lookup/MinecraftCommand.kt +++ b/src/main/kotlin/cn/rtast/fancybot/commands/misc/MinecraftCommand.kt @@ -6,7 +6,7 @@ @file:Suppress("KDocUnresolvedReference") -package cn.rtast.fancybot.commands.lookup +package cn.rtast.fancybot.commands.misc import cn.rtast.fancybot.annotations.CommandDescription import cn.rtast.fancybot.commands.lookup.WikipediaCommand.Companion.extractPlainTextFromHtml @@ -27,6 +27,7 @@ import cn.rtast.fancybot.entity.mc.oauth.ObtainXSTSPayload import cn.rtast.fancybot.entity.mc.oauth.ObtainXSTSResponse import cn.rtast.fancybot.entity.mc.oauth.RedeemCodeResponse import cn.rtast.fancybot.enums.mc.MCVersionType +import cn.rtast.fancybot.rconManager import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Logger import cn.rtast.fancybot.util.misc.isFullyTransparent @@ -41,6 +42,8 @@ import cn.rtast.fancybot.util.str.toJson import cn.rtast.fancybot.util.str.uriEncode import cn.rtast.motdpinger.BedrockPing import cn.rtast.motdpinger.JavaPing +import cn.rtast.rcon.RCon +import cn.rtast.rcon.exceptions.AuthFailedException import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.PrivateMessage import cn.rtast.rob.util.BaseCommand @@ -503,4 +506,91 @@ class MCLoginCommand : BaseCommand() { message.reply(msg) } } +} + +@CommandDescription("通过RCON来控制MC服务器(仅限私聊执行)") +class RCONCommand : BaseCommand() { + override val commandNames = listOf("/rcon", "/r") + + override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List) { + message.reply("请在私聊中使用此命令") + } + + override suspend fun executePrivate(listener: OneBotListener, message: PrivateMessage, args: List) { + if (args.isEmpty()) { + val msg = MessageChain.Builder() + .addText("这条指令用于在群内操作mc服务器") + .addNewLine() + .addText("使用`/r add ` 可以创建一条配置") + .addNewLine() + .addText("使用`/r `可以执行命令") + .addNewLine() + .addText("使用`/r remove `可以移除这条RCON配置") + .build() + message.reply(msg) + return + } + val action = args.first() + when (action) { + "get" -> { + val allRcons = rconManager.getAllRconsById(message.sender.userId) + if (allRcons.isEmpty()) { + message.reply("你还没有RCON配置呢, 发送`/r add `来创建一个配置吧") + return + } + val messages = mutableListOf() + .also { it.add(listOf("所有配置如下").asMessageChain()) } + allRcons.forEach { + val msg = MessageChain.Builder() + .addText("名称: ${it.name}") + .addNewLine() + .addText("地址: ${it.host}:${it.port}") + .addNewLine() + .addText("密码: *********") + .build() + messages.add(msg) + } + message.reply(messages.asNode(configManager.selfId)) + } + + "add" -> { + val name = args[1] + val host = args[2] + val port = args[3].toInt() + val password = args[4] + rconManager.insertConfig(message.sender.userId, name, host, port, password) + message.reply("成功增加一条记录: $name") + } + + "remove" -> { + val name = args.last() + val count = rconManager.removeRRCON(message.sender.userId, name) + if (count == 0) { + message.reply("RCON($name)不存在") + } else { + message.reply("成功移除了$name") + } + } + + else -> { + val name = args.first().trim() + val command = args.drop(1).joinToString(" ").trim() + val rconData = rconManager.getRcon(message.sender.userId, name) + if (rconData == null) { + message.reply("RCON($name)配置不存在") + } else { + try { + val result = rconManager.executeCommand(message.sender.userId, name, command) + val messages = mutableListOf() + .also { it.add(listOf("执行结果如下").asMessageChain()) } + .also { it.add(listOf(result).asMessageChain()) } + .asNode(configManager.selfId) + message.reply(messages) + } catch (_: AuthFailedException) { + message.reply("执行失败, 密码不正确") + } + } + } + } + } } \ No newline at end of file diff --git a/src/main/kotlin/cn/rtast/fancybot/db/RCONTable.kt b/src/main/kotlin/cn/rtast/fancybot/db/RCONTable.kt new file mode 100644 index 0000000..ab5ff89 --- /dev/null +++ b/src/main/kotlin/cn/rtast/fancybot/db/RCONTable.kt @@ -0,0 +1,29 @@ +/* + * Copyright © 2024 RTAkland + * Author: RTAkland + * Date: 2024/10/13 + */ + + +package cn.rtast.fancybot.db + +import org.jetbrains.exposed.sql.Table + +object RCONTable : Table("rcon") { + val id = integer("id").autoIncrement() + val host = varchar("host", 50) + val port = integer("port") + val userId = long("user_id") + val password = varchar("password", 128) + val name = varchar("name", 50) + override val primaryKey = PrimaryKey(id) +} + +data class RCONEntity( + val host: String, + val port: Int, + val userId: Long, + val name: String, + val password: String +) + diff --git a/src/main/kotlin/cn/rtast/fancybot/util/Database.kt b/src/main/kotlin/cn/rtast/fancybot/util/Database.kt index 432a9b0..cef7867 100644 --- a/src/main/kotlin/cn/rtast/fancybot/util/Database.kt +++ b/src/main/kotlin/cn/rtast/fancybot/util/Database.kt @@ -14,6 +14,7 @@ import cn.rtast.fancybot.db.JrrpTable import cn.rtast.fancybot.db.MinecraftAccessTokenTable import cn.rtast.fancybot.db.NiuziBankTable import cn.rtast.fancybot.db.NiuziTable +import cn.rtast.fancybot.db.RCONTable import kotlinx.coroutines.Dispatchers import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils @@ -28,6 +29,7 @@ suspend fun initDatabase() { SchemaUtils.createMissingTablesAndColumns(ActionTable) SchemaUtils.createMissingTablesAndColumns(BlackListTable) SchemaUtils.createMissingTablesAndColumns(MinecraftAccessTokenTable) + SchemaUtils.createMissingTablesAndColumns(RCONTable) } } diff --git a/src/main/kotlin/cn/rtast/fancybot/util/file/RCONManager.kt b/src/main/kotlin/cn/rtast/fancybot/util/file/RCONManager.kt new file mode 100644 index 0000000..aefb91e --- /dev/null +++ b/src/main/kotlin/cn/rtast/fancybot/util/file/RCONManager.kt @@ -0,0 +1,66 @@ +/* + * Copyright © 2024 RTAkland + * Author: RTAkland + * Date: 2024/10/13 + */ + + +package cn.rtast.fancybot.util.file + +import cn.rtast.fancybot.db.MinecraftAccessTokenTable +import cn.rtast.fancybot.db.RCONEntity +import cn.rtast.fancybot.db.RCONTable +import cn.rtast.fancybot.db.RCONTable.host +import cn.rtast.fancybot.db.RCONTable.password +import cn.rtast.fancybot.db.RCONTable.port +import cn.rtast.fancybot.util.suspendedTransaction +import cn.rtast.rcon.RCon +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq + +class RCONManager { + + suspend fun getRcon(userId: Long, name: String): RCONEntity? { + return suspendedTransaction { + RCONTable.selectAll().where { (RCONTable.userId eq userId) and (RCONTable.name eq name) } + .map { RCONEntity(it[host], it[port], it[RCONTable.userId], it[RCONTable.name], it[password]) } + }.singleOrNull() + } + + suspend fun getAllRconsById(userId: Long): List { + return suspendedTransaction { + RCONTable.selectAll().where { (RCONTable.userId eq userId) } + .map { RCONEntity(it[host], it[port], it[RCONTable.userId], it[RCONTable.name], it[password]) } + } + } + + suspend fun executeCommand(userId: Long, name: String, command: String): String { + val rconData = getRcon(userId, name)!! + val rcon = RCon(rconData.host, rconData.port) + rcon.authenticate(rconData.password) + val result = rcon.executeCommand(command).body + rcon.close() + return result + } + + suspend fun insertConfig(userId: Long, name: String, host: String, port: Int, password: String) { + suspendedTransaction { + RCONTable.insert { + it[RCONTable.userId] = userId + it[RCONTable.name] = name + it[RCONTable.host] = host + it[RCONTable.port] = port + it[RCONTable.password] = password + } + } + } + + suspend fun removeRRCON(userId: Long, name: String): Int { + return suspendedTransaction { + MinecraftAccessTokenTable.deleteWhere { (RCONTable.userId eq userId) and (RCONTable.name eq name) } + } + } +} \ No newline at end of file