fix: 改进可能的重复响应问题
This commit is contained in:
parent
8fd9250af4
commit
d1afc51ad3
|
|
@ -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.22.4
|
project_version=1.22.5
|
||||||
dg_lab_version=4.4.14.19
|
dg_lab_version=4.4.14.19
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,8 @@ class HelpModule(
|
||||||
val filtered = triggerFilter.filter(messages)
|
val filtered = triggerFilter.filter(messages)
|
||||||
val triggerMsg = filtered.maxByOrNull { it.time } ?: return
|
val triggerMsg = filtered.maxByOrNull { it.time } ?: return
|
||||||
|
|
||||||
|
updateTriggerState(triggerMsg) // 先更新防止后续 sendUnit 异常丢失或 cmdPair 不匹配漏更
|
||||||
|
|
||||||
val cmdPair = commandParser.parseCommand(triggerMsg.textContent)
|
val cmdPair = commandParser.parseCommand(triggerMsg.textContent)
|
||||||
if (cmdPair != null) {
|
if (cmdPair != null) {
|
||||||
val (_, arg) = cmdPair
|
val (_, arg) = cmdPair
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,9 @@ class InvitationCodesModule(
|
||||||
createAndSearchInvitationCodeIdsThenUpdateDate(needNewCode)
|
createAndSearchInvitationCodeIdsThenUpdateDate(needNewCode)
|
||||||
hadVaildCodeButNotUseListHandler(hadValidCodeButNotUsed + needNewCode)
|
hadVaildCodeButNotUseListHandler(hadValidCodeButNotUsed + needNewCode)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
sendFailedMessage(napCatClient, text = "系统错误,请联系管理员: $e")
|
val first = triggerMsgs.firstOrNull()
|
||||||
|
if (first != null) sendFailedMessage(napCatClient, first.userId, first.realId, first.time, "系统错误,请联系管理员: $e")
|
||||||
|
else sendFailedMessage(napCatClient, text = "系统错误,请联系管理员: $e")
|
||||||
} finally {
|
} finally {
|
||||||
saveState(lastTriggerMapState)
|
saveState(lastTriggerMapState)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,14 +170,15 @@ class McServerStatusModule(
|
||||||
|
|
||||||
|
|
||||||
private suspend fun processCommand(msg: MsgHistorySpecificMsg) {
|
private suspend fun processCommand(msg: MsgHistorySpecificMsg) {
|
||||||
// 找出文本内容
|
// 先更新触发状态防止提前返回或 sendUnit 异常丢失
|
||||||
|
cooldownState = cooldownState.updateLastTrigger(msg.userId, msg.realId, msg.time)
|
||||||
|
|
||||||
val text = msg.message
|
val text = msg.message
|
||||||
.firstOrNull { it.type == MessageType.Text }
|
.firstOrNull { it.type == MessageType.Text }
|
||||||
?.data?.text
|
?.data?.text
|
||||||
?.trim()
|
?.trim()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
// 使用命令解析器解析命令
|
|
||||||
val parsedCommand = commandParser.parseCommand(text) ?: return
|
val parsedCommand = commandParser.parseCommand(text) ?: return
|
||||||
val (_, address) = parsedCommand
|
val (_, address) = parsedCommand
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,7 @@ object ModuleFactory {
|
||||||
val groupMessagePollingModule = resolveDependency(
|
val groupMessagePollingModule = resolveDependency(
|
||||||
config.findDependency(GROUP_MESSAGE_POLLING_MODULE), "groupMessagePolling"
|
config.findDependency(GROUP_MESSAGE_POLLING_MODULE), "groupMessagePolling"
|
||||||
) as GroupMessagePollingModule
|
) as GroupMessagePollingModule
|
||||||
|
val maxBlockRecords = config.getOrDefault("max-block-records", 200)
|
||||||
return RconCommandModule(
|
return RconCommandModule(
|
||||||
config.name,
|
config.name,
|
||||||
groupMessagePollingModule,
|
groupMessagePollingModule,
|
||||||
|
|
@ -260,7 +261,8 @@ object ModuleFactory {
|
||||||
selfNickName,
|
selfNickName,
|
||||||
allowedUsers,
|
allowedUsers,
|
||||||
commandBlocklist,
|
commandBlocklist,
|
||||||
commandPrefix
|
commandPrefix,
|
||||||
|
maxBlockRecords
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import top.r3944realms.ltdmanager.module.common.filter.TriggerMessageFilter
|
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.IgnoreSelfFilter
|
||||||
import top.r3944realms.ltdmanager.module.common.filter.type.KeywordFilter
|
import top.r3944realms.ltdmanager.module.common.filter.type.KeywordFilter
|
||||||
|
|
@ -17,6 +20,7 @@ import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
||||||
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||||
import top.r3944realms.ltdmanager.utils.CmdUtil
|
import top.r3944realms.ltdmanager.utils.CmdUtil
|
||||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class RconCommandModule(
|
class RconCommandModule(
|
||||||
moduleName: String,
|
moduleName: String,
|
||||||
|
|
@ -29,13 +33,22 @@ class RconCommandModule(
|
||||||
private val allowedUsers: Set<Long>,
|
private val allowedUsers: Set<Long>,
|
||||||
private val commandBlocklist: Set<String>,
|
private val commandBlocklist: Set<String>,
|
||||||
private val commandPrefix: String,
|
private val commandPrefix: String,
|
||||||
) : BaseModule(Modules.RCON_COMMAND, moduleName) {
|
private val maxBlockRecords: Int = 200,
|
||||||
|
) : BaseModule(Modules.RCON_COMMAND, moduleName), PersistentState<RconCommandModule.BlockState> {
|
||||||
|
|
||||||
private var scope: CoroutineScope? = null
|
private var scope: CoroutineScope? = null
|
||||||
|
|
||||||
private var lastTriggeredRealId: Long = -1
|
private var lastTriggeredRealId: Long = -1
|
||||||
private var lastTriggerTime: Long = 0
|
private var lastTriggerTime: Long = 0
|
||||||
|
|
||||||
|
private val stateFile: File = getStateFileInternal("rcon_block_state.json", name)
|
||||||
|
override fun getStateFileInternal(): File = stateFile
|
||||||
|
|
||||||
|
private val json = Json { ignoreUnknownKeys = true; coerceInputValues = true }
|
||||||
|
|
||||||
|
private var blockState: BlockState = loadState()
|
||||||
|
override fun getState(): BlockState = blockState
|
||||||
|
|
||||||
private val triggerFilter by lazy {
|
private val triggerFilter by lazy {
|
||||||
TriggerMessageFilter(
|
TriggerMessageFilter(
|
||||||
listOf(
|
listOf(
|
||||||
|
|
@ -50,6 +63,7 @@ class RconCommandModule(
|
||||||
LoggerUtil.logger.info("[$name] RCON命令模块已装载")
|
LoggerUtil.logger.info("[$name] RCON命令模块已装载")
|
||||||
LoggerUtil.logger.info("[$name] 允许用户: $allowedUsers")
|
LoggerUtil.logger.info("[$name] 允许用户: $allowedUsers")
|
||||||
LoggerUtil.logger.info("[$name] 命令黑名单: $commandBlocklist")
|
LoggerUtil.logger.info("[$name] 命令黑名单: $commandBlocklist")
|
||||||
|
LoggerUtil.logger.info("[$name] 阻止记录数: ${blockState.records.size}")
|
||||||
|
|
||||||
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
scope!!.launch {
|
scope!!.launch {
|
||||||
|
|
@ -60,6 +74,7 @@ class RconCommandModule(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun onUnload() {
|
override suspend fun onUnload() {
|
||||||
|
saveState(blockState)
|
||||||
scope?.cancel()
|
scope?.cancel()
|
||||||
LoggerUtil.logger.info("[$name] RCON命令模块已卸载")
|
LoggerUtil.logger.info("[$name] RCON命令模块已卸载")
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +88,7 @@ class RconCommandModule(
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LoggerUtil.logger.error("[$name] 处理RCON命令失败", e)
|
LoggerUtil.logger.error("[$name] 处理RCON命令失败", e)
|
||||||
sendReply(triggerMsg, "命令执行异常: ${e.message}")
|
sendReply(triggerMsg, "命令执行异常: ${e.message}")
|
||||||
|
updateTriggerState(triggerMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,28 +102,40 @@ class RconCommandModule(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 权限检查
|
|
||||||
if (msg.userId !in allowedUsers) {
|
if (msg.userId !in allowedUsers) {
|
||||||
LoggerUtil.logger.warn("[$name] 用户 ${msg.userId} 无权限执行RCON: $rconCommand")
|
LoggerUtil.logger.warn("[$name] 用户 ${msg.userId} 无权限执行RCON: $rconCommand")
|
||||||
sendReply(msg, "你没有权限执行 RCON 命令")
|
sendReply(msg, "你没有权限执行 RCON 命令")
|
||||||
|
updateTriggerState(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 危险命令检查
|
|
||||||
val blocked = findBlocklistMatch(rconCommand)
|
val blocked = findBlocklistMatch(rconCommand)
|
||||||
if (blocked != null) {
|
if (blocked != null) {
|
||||||
LoggerUtil.logger.warn("[$name] 阻止危险命令: '$rconCommand' (匹配黑名单: $blocked)")
|
LoggerUtil.logger.warn("[$name] 阻止危险命令: '$rconCommand' (匹配黑名单: $blocked) 来自 ${msg.userId}")
|
||||||
sendReply(msg, "命令已被阻止 (匹配黑名单规则: $blocked)")
|
sendReply(msg, "命令已被阻止 (匹配黑名单规则: $blocked)")
|
||||||
|
recordBlock(msg.userId, rconCommand, blocked)
|
||||||
|
updateTriggerState(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行RCON
|
|
||||||
LoggerUtil.logger.info("[$name] 用户 ${msg.userId} 执行RCON: $rconCommand")
|
LoggerUtil.logger.info("[$name] 用户 ${msg.userId} 执行RCON: $rconCommand")
|
||||||
val output = runRconCommand(rconCommand)
|
val output = runRconCommand(rconCommand)
|
||||||
sendResult(msg, rconCommand, output)
|
sendResult(msg, rconCommand, output)
|
||||||
updateTriggerState(msg)
|
updateTriggerState(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun recordBlock(qq: Long, command: String, matchedRule: String) {
|
||||||
|
val record = BlockRecord(
|
||||||
|
qq = qq,
|
||||||
|
command = command,
|
||||||
|
matchedRule = matchedRule,
|
||||||
|
time = System.currentTimeMillis()
|
||||||
|
)
|
||||||
|
val records = (listOf(record) + blockState.records).take(maxBlockRecords)
|
||||||
|
blockState = BlockState(records)
|
||||||
|
saveState(blockState)
|
||||||
|
}
|
||||||
|
|
||||||
private fun findBlocklistMatch(command: String): String? {
|
private fun findBlocklistMatch(command: String): String? {
|
||||||
val lower = command.lowercase().trimStart('/')
|
val lower = command.lowercase().trimStart('/')
|
||||||
return commandBlocklist.firstOrNull { blocked ->
|
return commandBlocklist.firstOrNull { blocked ->
|
||||||
|
|
@ -139,7 +167,6 @@ class RconCommandModule(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 长输出 → 合并转发
|
|
||||||
val chunks = trimmed.chunked(maxLen)
|
val chunks = trimmed.chunked(maxLen)
|
||||||
val messages = chunks.map { chunk ->
|
val messages = chunks.map { chunk ->
|
||||||
SendForwardMsgRequest.Message(
|
SendForwardMsgRequest.Message(
|
||||||
|
|
@ -196,7 +223,46 @@ class RconCommandModule(
|
||||||
appendLine(" $commandPrefix difficulty peaceful")
|
appendLine(" $commandPrefix difficulty peaceful")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun info(): String = "RCON命令模块 - 前缀: $commandPrefix, 允许用户: ${allowedUsers.size}人, 黑名单规则: ${commandBlocklist.size}条"
|
// ======== 持久化 ========
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BlockState(val records: List<BlockRecord> = emptyList())
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BlockRecord(
|
||||||
|
val qq: Long,
|
||||||
|
val command: String,
|
||||||
|
val matchedRule: String,
|
||||||
|
val time: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun loadState(): BlockState {
|
||||||
|
return try {
|
||||||
|
if (!stateFile.exists()) BlockState()
|
||||||
|
else json.decodeFromString(stateFile.readText())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.warn("[$name] 读取阻止记录失败", e)
|
||||||
|
BlockState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveState(state: BlockState) {
|
||||||
|
try {
|
||||||
|
stateFile.writeText(json.encodeToString(state))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.error("[$name] 保存阻止记录失败", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun info(): String = buildString {
|
||||||
|
appendLine("RCON命令模块 - 前缀: $commandPrefix")
|
||||||
|
appendLine(" 允许用户: ${allowedUsers.size}人, 黑名单规则: ${commandBlocklist.size}条")
|
||||||
|
appendLine(" 阻止记录: ${blockState.records.size}条 (最近5条):")
|
||||||
|
blockState.records.take(5).forEach {
|
||||||
|
appendLine(" • ${it.qq} → ${it.command} (${it.matchedRule})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun help(): String = buildString {
|
override fun help(): String = buildString {
|
||||||
appendLine("RCON命令模块 - 通过QQ群执行Minecraft RCON命令")
|
appendLine("RCON命令模块 - 通过QQ群执行Minecraft RCON命令")
|
||||||
appendLine("前缀: $commandPrefix")
|
appendLine("前缀: $commandPrefix")
|
||||||
|
|
|
||||||
|
|
@ -289,11 +289,10 @@ class WhitelistAuditModule(
|
||||||
|
|
||||||
private suspend fun handleReActivationMessages(messages: List<MsgHistorySpecificMsg>) {
|
private suspend fun handleReActivationMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||||
val msg = messages.maxByOrNull { it.time } ?: return
|
val msg = messages.maxByOrNull { it.time } ?: return
|
||||||
|
updateMsgState(msg)
|
||||||
|
|
||||||
val key = msg.userId.toString()
|
val key = msg.userId.toString()
|
||||||
val entry = auditState.entries[key] ?: return
|
val entry = auditState.entries[key] ?: return
|
||||||
|
|
||||||
updateMsgState(msg)
|
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val graceMs = gracePeriodDays * 24 * 60 * 60 * 1000L
|
val graceMs = gracePeriodDays * 24 * 60 * 60 * 1000L
|
||||||
|
|
||||||
|
|
@ -339,6 +338,9 @@ class WhitelistAuditModule(
|
||||||
private suspend fun handleAuditCommand(messages: List<MsgHistorySpecificMsg>) {
|
private suspend fun handleAuditCommand(messages: List<MsgHistorySpecificMsg>) {
|
||||||
val msg = messages.maxByOrNull { it.time } ?: return
|
val msg = messages.maxByOrNull { it.time } ?: return
|
||||||
|
|
||||||
|
lastAuditRealId = msg.realId
|
||||||
|
lastAuditTime = msg.time
|
||||||
|
|
||||||
if (msg.userId !in auditAllowedUsers) {
|
if (msg.userId !in auditAllowedUsers) {
|
||||||
napCatClient.sendUnit(
|
napCatClient.sendUnit(
|
||||||
SendGroupMsgRequest(
|
SendGroupMsgRequest(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user