feat: 添加通用SQL命令
This commit is contained in:
parent
9cb6bcef50
commit
79caa2b56e
|
|
@ -3,5 +3,5 @@ org.gradle.downloadSources=false
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.degree_of_parallelism=16
|
org.gradle.degree_of_parallelism=16
|
||||||
project_group=top.r3944realms.ltdmanager
|
project_group=top.r3944realms.ltdmanager
|
||||||
project_version=1.20-SNAPSHOT
|
project_version=1.21-SNAPSHOT
|
||||||
dg_lab_version=4.4.14.19
|
dg_lab_version=4.4.14.19
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ data class ModuleConfig(
|
||||||
STATE_MODULE(Modules.STATE),
|
STATE_MODULE(Modules.STATE),
|
||||||
HELP_MODULE(Modules.HELP),
|
HELP_MODULE(Modules.HELP),
|
||||||
GITEA_WEBHOOK_MODULE(Modules.GITEA_WEBHOOK),
|
GITEA_WEBHOOK_MODULE(Modules.GITEA_WEBHOOK),
|
||||||
|
RCON_COMMAND_MODULE(Modules.RCON_COMMAND),
|
||||||
UNKNOWN_MODULE("UnknownModule");
|
UNKNOWN_MODULE("UnknownModule");
|
||||||
}
|
}
|
||||||
// 基础获取方法
|
// 基础获取方法
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
||||||
import top.r3944realms.ltdmanager.module.exception.ConfigError
|
import top.r3944realms.ltdmanager.module.exception.ConfigError
|
||||||
import top.r3944realms.ltdmanager.module.gitea.GiteaEventType
|
import top.r3944realms.ltdmanager.module.gitea.GiteaEventType
|
||||||
import top.r3944realms.ltdmanager.module.gitea.GiteaWebhookModule
|
import top.r3944realms.ltdmanager.module.gitea.GiteaWebhookModule
|
||||||
|
import top.r3944realms.ltdmanager.module.RconCommandModule
|
||||||
|
|
||||||
object ModuleFactory {
|
object ModuleFactory {
|
||||||
fun createModule(config: ModuleConfig.Module): BaseModule {
|
fun createModule(config: ModuleConfig.Module): BaseModule {
|
||||||
|
|
@ -23,6 +24,7 @@ object ModuleFactory {
|
||||||
MOD_GROUP_HANDLER_MODULE -> createModGroupHandler(config)
|
MOD_GROUP_HANDLER_MODULE -> createModGroupHandler(config)
|
||||||
HELP_MODULE -> createHelpModule(config)
|
HELP_MODULE -> createHelpModule(config)
|
||||||
GITEA_WEBHOOK_MODULE -> createGiteaWebhook(config)
|
GITEA_WEBHOOK_MODULE -> createGiteaWebhook(config)
|
||||||
|
RCON_COMMAND_MODULE -> createRconCommand(config)
|
||||||
UNKNOWN_MODULE -> throw ConfigError(ConfigError.Type.INVALID_PARAMETER, "unknown module")
|
UNKNOWN_MODULE -> throw ConfigError(ConfigError.Type.INVALID_PARAMETER, "unknown module")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -214,4 +216,29 @@ object ModuleFactory {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createRconCommand(config: ModuleConfig.Module): RconCommandModule {
|
||||||
|
val toolConfig = YamlConfigLoader.loadToolConfig()
|
||||||
|
val selfId = config.long("self-id")
|
||||||
|
val selfNickName = config.string("self-nick-name")
|
||||||
|
val allowedUsers = config.list<Long>("admin-ids").toSet()
|
||||||
|
val commandBlocklist = config.stringList("command-blocklist").toSet()
|
||||||
|
val commandPrefix = config.string("command-prefix")
|
||||||
|
val rconTimeoutSec = config.getOrDefault("rcon-timeout-sec", 5L)
|
||||||
|
val groupMessagePollingModule = resolveDependency(
|
||||||
|
config.findDependency(GROUP_MESSAGE_POLLING_MODULE), "groupMessagePolling"
|
||||||
|
) as GroupMessagePollingModule
|
||||||
|
return RconCommandModule(
|
||||||
|
config.name,
|
||||||
|
groupMessagePollingModule,
|
||||||
|
toolConfig.rcon.mcRconToolPath.toString(),
|
||||||
|
toolConfig.rcon.mcRconToolConfigPath.toString(),
|
||||||
|
rconTimeoutSec,
|
||||||
|
selfId,
|
||||||
|
selfNickName,
|
||||||
|
allowedUsers,
|
||||||
|
commandBlocklist,
|
||||||
|
commandPrefix
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ object Modules {
|
||||||
val INVITATION_CODE: String = register("InvitationCodeModule")
|
val INVITATION_CODE: String = register("InvitationCodeModule")
|
||||||
val STATE: String = register("StateModule")
|
val STATE: String = register("StateModule")
|
||||||
val GITEA_WEBHOOK: String = register("GiteaWebhookModule")
|
val GITEA_WEBHOOK: String = register("GiteaWebhookModule")
|
||||||
|
val RCON_COMMAND: String = register("RconCommandModule")
|
||||||
fun register(name: String): String {
|
fun register(name: String): String {
|
||||||
MODULES.add(name)
|
MODULES.add(name)
|
||||||
return name
|
return name
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
package top.r3944realms.ltdmanager.module
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import top.r3944realms.ltdmanager.module.common.filter.TriggerMessageFilter
|
||||||
|
import top.r3944realms.ltdmanager.module.common.filter.type.IgnoreSelfFilter
|
||||||
|
import top.r3944realms.ltdmanager.module.common.filter.type.KeywordFilter
|
||||||
|
import top.r3944realms.ltdmanager.module.common.filter.type.NewMessageFilter
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||||
|
import top.r3944realms.ltdmanager.utils.CmdUtil
|
||||||
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
|
||||||
|
class RconCommandModule(
|
||||||
|
moduleName: String,
|
||||||
|
private val groupMessagePollingModule: GroupMessagePollingModule,
|
||||||
|
private val rconPath: String,
|
||||||
|
private val rconConfigPath: String,
|
||||||
|
private val rconTimeoutSec: Long,
|
||||||
|
private val selfId: Long,
|
||||||
|
private val selfNickName: String,
|
||||||
|
private val allowedUsers: Set<Long>,
|
||||||
|
private val commandBlocklist: Set<String>,
|
||||||
|
private val commandPrefix: String,
|
||||||
|
) : BaseModule(Modules.RCON_COMMAND, moduleName) {
|
||||||
|
|
||||||
|
private var scope: CoroutineScope? = null
|
||||||
|
|
||||||
|
private var lastTriggeredRealId: Long = -1
|
||||||
|
private var lastTriggerTime: Long = 0
|
||||||
|
|
||||||
|
private val triggerFilter by lazy {
|
||||||
|
TriggerMessageFilter(
|
||||||
|
listOf(
|
||||||
|
IgnoreSelfFilter(selfId),
|
||||||
|
NewMessageFilter { lastTriggerTime to lastTriggeredRealId },
|
||||||
|
KeywordFilter(setOf(commandPrefix)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoad() {
|
||||||
|
LoggerUtil.logger.info("[$name] RCON命令模块已装载")
|
||||||
|
LoggerUtil.logger.info("[$name] 允许用户: $allowedUsers")
|
||||||
|
LoggerUtil.logger.info("[$name] 命令黑名单: $commandBlocklist")
|
||||||
|
|
||||||
|
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
scope!!.launch {
|
||||||
|
groupMessagePollingModule.messagesFlow.collect { messages ->
|
||||||
|
if (loaded) handleMessages(messages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onUnload() {
|
||||||
|
scope?.cancel()
|
||||||
|
LoggerUtil.logger.info("[$name] RCON命令模块已卸载")
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||||
|
val filtered = triggerFilter.filter(messages)
|
||||||
|
val triggerMsg = filtered.maxByOrNull { it.time } ?: return
|
||||||
|
|
||||||
|
try {
|
||||||
|
processCommand(triggerMsg)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.error("[$name] 处理RCON命令失败", e)
|
||||||
|
sendReply(triggerMsg, "命令执行异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun processCommand(msg: MsgHistorySpecificMsg) {
|
||||||
|
val text = msg.message.firstOrNull { it.type == MessageType.Text }?.data?.text ?: return
|
||||||
|
val rconCommand = text.removePrefix(commandPrefix).trim()
|
||||||
|
|
||||||
|
if (rconCommand.isEmpty()) {
|
||||||
|
sendReply(msg, buildHelpMessage())
|
||||||
|
updateTriggerState(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
if (msg.userId !in allowedUsers) {
|
||||||
|
LoggerUtil.logger.warn("[$name] 用户 ${msg.userId} 无权限执行RCON: $rconCommand")
|
||||||
|
sendReply(msg, "你没有权限执行 RCON 命令")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 危险命令检查
|
||||||
|
val blocked = findBlocklistMatch(rconCommand)
|
||||||
|
if (blocked != null) {
|
||||||
|
LoggerUtil.logger.warn("[$name] 阻止危险命令: '$rconCommand' (匹配黑名单: $blocked)")
|
||||||
|
sendReply(msg, "命令已被阻止 (匹配黑名单规则: $blocked)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行RCON
|
||||||
|
LoggerUtil.logger.info("[$name] 用户 ${msg.userId} 执行RCON: $rconCommand")
|
||||||
|
val output = runRconCommand(rconCommand)
|
||||||
|
sendResult(msg, rconCommand, output)
|
||||||
|
updateTriggerState(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findBlocklistMatch(command: String): String? {
|
||||||
|
val lower = command.lowercase().trimStart('/')
|
||||||
|
return commandBlocklist.firstOrNull { blocked ->
|
||||||
|
lower == blocked.lowercase() ||
|
||||||
|
lower.startsWith(blocked.lowercase() + " ") ||
|
||||||
|
lower.startsWith(blocked.lowercase() + "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runRconCommand(command: String): String {
|
||||||
|
return CmdUtil.runExeCommand(
|
||||||
|
rconPath,
|
||||||
|
"-c", rconConfigPath,
|
||||||
|
"-T", "${rconTimeoutSec}s",
|
||||||
|
command
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendResult(msg: MsgHistorySpecificMsg, command: String, output: String) {
|
||||||
|
val trimmed = output.trim()
|
||||||
|
val maxLen = 3000
|
||||||
|
|
||||||
|
if (trimmed.length <= maxLen) {
|
||||||
|
sendReply(msg, buildString {
|
||||||
|
appendLine("执行: $command")
|
||||||
|
appendLine("─".repeat(24))
|
||||||
|
append(trimmed.ifEmpty { "(无输出)" })
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 长输出 → 合并转发
|
||||||
|
val chunks = trimmed.chunked(maxLen)
|
||||||
|
val messages = chunks.map { chunk ->
|
||||||
|
SendForwardMsgRequest.Message(
|
||||||
|
data = SendForwardMsgRequest.PurpleData(chunk),
|
||||||
|
type = MessageType.Text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val topMessage = SendForwardMsgRequest.TopForwardMsg(
|
||||||
|
data = SendForwardMsgRequest.MessageData(
|
||||||
|
content = messages,
|
||||||
|
nickname = selfNickName,
|
||||||
|
userId = ID.long(selfId),
|
||||||
|
),
|
||||||
|
type = MessageType.Node
|
||||||
|
)
|
||||||
|
|
||||||
|
val request = SendForwardMsgRequest(
|
||||||
|
groupId = ID.long(groupMessagePollingModule.targetGroupId),
|
||||||
|
messages = listOf(topMessage),
|
||||||
|
news = listOf(
|
||||||
|
SendForwardMsgRequest.ForwardModelNews("RCON: $command"),
|
||||||
|
SendForwardMsgRequest.ForwardModelNews("输出 ${trimmed.length} 字符"),
|
||||||
|
),
|
||||||
|
prompt = "RCON命令执行结果",
|
||||||
|
source = "RCON",
|
||||||
|
summary = "RCON: $command",
|
||||||
|
)
|
||||||
|
|
||||||
|
napCatClient.sendUnit(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendReply(msg: MsgHistorySpecificMsg, text: String) {
|
||||||
|
napCatClient.sendUnit(
|
||||||
|
SendGroupMsgRequest(
|
||||||
|
MessageElement.reply(ID.long(msg.realId), text),
|
||||||
|
ID.long(groupMessagePollingModule.targetGroupId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTriggerState(msg: MsgHistorySpecificMsg) {
|
||||||
|
lastTriggeredRealId = msg.realId
|
||||||
|
lastTriggerTime = msg.time
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildHelpMessage(): String = buildString {
|
||||||
|
appendLine("RCON 命令模块")
|
||||||
|
appendLine("用法: $commandPrefix <MC命令>")
|
||||||
|
appendLine("─".repeat(16))
|
||||||
|
appendLine("示例:")
|
||||||
|
appendLine(" $commandPrefix list")
|
||||||
|
appendLine(" $commandPrefix forge tps")
|
||||||
|
appendLine(" $commandPrefix difficulty peaceful")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun info(): String = "RCON命令模块 - 前缀: $commandPrefix, 允许用户: ${allowedUsers.size}人, 黑名单规则: ${commandBlocklist.size}条"
|
||||||
|
override fun help(): String = buildString {
|
||||||
|
appendLine("RCON命令模块 - 通过QQ群执行Minecraft RCON命令")
|
||||||
|
appendLine("前缀: $commandPrefix")
|
||||||
|
appendLine("权限: 仅以下QQ号可执行: ${allowedUsers.joinToString()}")
|
||||||
|
appendLine("黑名单命令前缀: ${commandBlocklist.joinToString()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user