mirror of
https://github.com/jie65535/JGrasscutterCommand.git
synced 2025-06-01 17:29:13 +08:00
264 lines
13 KiB
Kotlin
264 lines
13 KiB
Kotlin
/*
|
||
* JGrasscutterCommand
|
||
* Copyright (C) 2022 jie65535
|
||
*
|
||
* This program is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU Affero General Public License as published
|
||
* by the Free Software Foundation, either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU Affero General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Affero General Public License
|
||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
*/
|
||
package top.jie65535.mirai
|
||
|
||
import kotlinx.coroutines.TimeoutCancellationException
|
||
import kotlinx.coroutines.isActive
|
||
import kotlinx.coroutines.withTimeout
|
||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||
import net.mamoe.mirai.contact.nameCardOrNick
|
||
import net.mamoe.mirai.event.*
|
||
import net.mamoe.mirai.event.events.GroupMessageEvent
|
||
import net.mamoe.mirai.event.events.MessageEvent
|
||
import net.mamoe.mirai.message.data.At
|
||
import net.mamoe.mirai.message.data.MessageSource.Key.quote
|
||
import net.mamoe.mirai.message.data.PlainText
|
||
import net.mamoe.mirai.message.data.firstIsInstance
|
||
import net.mamoe.mirai.message.data.firstIsInstanceOrNull
|
||
import top.jie65535.mirai.model.User
|
||
import top.jie65535.mirai.opencommand.OpenCommandApi
|
||
import java.time.LocalDateTime
|
||
|
||
object JGrasscutterCommand : KotlinPlugin(
|
||
JvmPluginDescription(
|
||
id = "top.jie65535.mirai.grasscutter-command",
|
||
name = "J Grasscutter Command",
|
||
version = "0.1.0",
|
||
) {
|
||
author("jie65535")
|
||
info("""聊天执行GC命令""")
|
||
}
|
||
) {
|
||
override fun onEnable() {
|
||
PluginConfig.reload()
|
||
PluginData.reload()
|
||
PluginCommands.register()
|
||
|
||
val eventChannel = GlobalEventChannel.parentScope(this)
|
||
// 监听群消息
|
||
eventChannel.subscribeAlways<GroupMessageEvent> {
|
||
// 忽略被拉黑的用户发送的消息
|
||
if (PluginConfig.blacklist.contains(sender.id))
|
||
return@subscribeAlways
|
||
|
||
// 忽略未启用的群消息
|
||
val groupConfig = PluginData.groups.find { it.id == group.id }
|
||
if (groupConfig == null || !groupConfig.isEnabled)
|
||
return@subscribeAlways
|
||
|
||
// 忽略未启用的服务器
|
||
val server = PluginData.servers.find { it.id == groupConfig.serverId }
|
||
if (server == null || !server.isEnabled)
|
||
return@subscribeAlways
|
||
|
||
// 解析消息
|
||
val message = this.message.joinToString("") {
|
||
if (it is At) {
|
||
// 替换@群员为@其绑定的Uid
|
||
val user = PluginData.users.find { user -> user.id == it.target && user.serverId == server.id }
|
||
if (user == null) { it.contentToString() } else { "@${user.uid}" }
|
||
} else {
|
||
it.contentToString()
|
||
}
|
||
}
|
||
|
||
// 处理绑定命令
|
||
if (message.startsWith(PluginConfig.bindCommand)) {
|
||
val input = message.removePrefix(PluginConfig.bindCommand).trim()
|
||
val bindUid = input.toIntOrNull()
|
||
if (bindUid == null) {
|
||
this.subject.sendMessage(this.message.quote() + "请输入正确的UID")
|
||
return@subscribeAlways
|
||
}
|
||
logger.info("绑定用户 ${sender.nameCardOrNick}(${sender.id}) Uid $bindUid 到服务器 ${server.name} ${server.address}")
|
||
var user = PluginData.users.find { it.id == sender.id && it.serverId == server.id }
|
||
if (user == null) {
|
||
user = User(sender.id, server.id, bindUid)
|
||
PluginData.users.add(user)
|
||
} else {
|
||
if (user.uid != bindUid) {
|
||
user.uid = bindUid
|
||
// 更换绑定将重置token
|
||
user.token = ""
|
||
}
|
||
}
|
||
// 发送验证码,并提示要求回复验证码
|
||
sendCode(server.address, user)
|
||
}
|
||
// 处理执行游戏命令
|
||
else if (message.startsWith(PluginConfig.commandPrefix)) {
|
||
var command = message.removePrefix(PluginConfig.commandPrefix).trim()
|
||
if (command.isEmpty()) {
|
||
return@subscribeAlways
|
||
}
|
||
// 检查是否使用别名
|
||
val t = PluginConfig.commandAlias[command]
|
||
if (!t.isNullOrEmpty()) command = t
|
||
// 如果是斜杠开头,则移除斜杠,在控制台执行不需要斜杠
|
||
if (command[0] == '/') {
|
||
command = command.substring(1)
|
||
if (command.isEmpty())
|
||
return@subscribeAlways
|
||
}
|
||
|
||
// 执行的用户
|
||
var user: User? = null
|
||
// 获取token
|
||
// 若设置了控制台令牌,则验证是否为管理员执行
|
||
val token = if (server.consoleToken.isNotEmpty() && PluginConfig.administrators.contains(sender.id)) {
|
||
logger.info("管理员 ${sender.nameCardOrNick}(${sender.id}) 执行命令:$command")
|
||
// 设置控制台令牌
|
||
server.consoleToken
|
||
} else {
|
||
// 普通用户
|
||
user = PluginData.users.find { it.id == sender.id && it.serverId == server.id }
|
||
if (user == null || user.token.isEmpty()) {
|
||
return@subscribeAlways
|
||
}
|
||
logger.info("用户 ${sender.nameCardOrNick}(${sender.id}) 执行命令:$command")
|
||
// 使用用户缓存令牌
|
||
user.token
|
||
}
|
||
try {
|
||
// 调用接口执行命令
|
||
val response = OpenCommandApi.runCommand(server.address, token, command)
|
||
if (response.isNullOrEmpty()) {
|
||
subject.sendMessage(this.message.quote() + "OK")
|
||
} else {
|
||
subject.sendMessage(this.message.quote() + response)
|
||
}
|
||
if (user != null) {
|
||
// 计数并更新最后运行时间
|
||
++user.runCount
|
||
user.lastRunTime = LocalDateTime.now()
|
||
}
|
||
} catch (e: OpenCommandApi.InvokeException) {
|
||
when (e.code) {
|
||
404 -> {
|
||
subject.sendMessage(this.message.quote() + "玩家不存在或未上线")
|
||
}
|
||
403 -> {
|
||
logger.warning("${sender.nameCardOrNick}(${sender.id}) 的命令执行失败,服务器已收到命令,但不做处理,可能是未验证通过")
|
||
// 403不理会用户
|
||
}
|
||
401 -> {
|
||
logger.warning("${sender.nameCardOrNick}(${sender.id}) 的命令执行失败,未授权或已过期的令牌,可以修改插件配置以延长令牌过期时间")
|
||
subject.sendMessage(this.message.quote() + "令牌未授权或已过期,请重新绑定账号以更新令牌")
|
||
// TODO 此处可以重新发送验证码要求验证,但目前直接报错并要求重新绑定
|
||
}
|
||
500 -> {
|
||
logger.warning("${sender.nameCardOrNick}(${sender.id}) 的命令执行失败,服务器内部错误:${e.message}")
|
||
subject.sendMessage(this.message.quote() + "服务器内部发生错误,命令执行失败")
|
||
}
|
||
else -> {
|
||
logger.warning("${sender.nameCardOrNick}(${sender.id}) 的命令执行失败,发生预期外异常:${e.message}")
|
||
}
|
||
}
|
||
} catch (e: Throwable) {
|
||
logger.warning("${sender.nameCardOrNick}(${sender.id}) 在执行命令时发生异常", e)
|
||
}
|
||
}
|
||
// 否则如果启用了同步消息,且控制台令牌不为空
|
||
else if (server.consoleToken.isNotEmpty() && server.syncMessage) {
|
||
try {
|
||
OpenCommandApi.runCommand(
|
||
server.address,
|
||
server.consoleToken,
|
||
"say <color=green>${sender.nameCardOrNick}</color>:\n${this.message.contentToString()}")
|
||
} catch (e: Throwable) {
|
||
server.syncMessage = false
|
||
logger.warning("同步发送聊天消息失败,自动禁用同步消息,请手动重新启用", e)
|
||
}
|
||
}
|
||
}
|
||
|
||
logger.info("Plugin loaded")
|
||
}
|
||
|
||
/**
|
||
* 发送验证码,并监听用户回复
|
||
* @param host 服务器地址
|
||
* @param user 用户实例
|
||
*/
|
||
private suspend fun MessageEvent.sendCode(host: String, user: User) {
|
||
try {
|
||
logger.info("${sender.nameCardOrNick}(${sender.id}) 正在请求向服务器[${user.serverId}] @${user.uid} 发送验证码")
|
||
// 请求发送验证码
|
||
user.token = OpenCommandApi.sendCode(host, user.uid)
|
||
// 提示用户
|
||
subject.sendMessage(message.quote() + "验证码已发送,请在一分钟内将游戏中收到的验证码发送以验证身份。")
|
||
|
||
// 最多等待1分钟
|
||
withTimeout(60 * 1000) {
|
||
// 当验证失败时循环重试,最多重试3次
|
||
var retry = 3
|
||
while (isActive && retry --> 0) {
|
||
// 监听消息事件
|
||
val nextEvent = GlobalEventChannel.nextEvent<MessageEvent>(priority = EventPriority.HIGH) { event ->
|
||
// 仅监听该用户的消息,并且消息内容为4位数字
|
||
event.sender.id == user.id && event.message.firstIsInstanceOrNull<PlainText>()
|
||
?.content?.trim()
|
||
?.toIntOrNull()
|
||
?.let { it in 1000..9999 } == true
|
||
}
|
||
|
||
// 得到消息中的4位数字代码
|
||
val code = nextEvent.message.firstIsInstance<PlainText>().content.trim().toInt()
|
||
try {
|
||
// 请求验证
|
||
OpenCommandApi.verify(host, user.token, code)
|
||
logger.info("${nextEvent.sender.nameCardOrNick}(${nextEvent.sender.id}) 在服务器[${user.serverId}]验证通过,其游戏Uid为 ${user.uid}")
|
||
// 若无异常则验证通过
|
||
nextEvent.subject.sendMessage(nextEvent.message.quote() + "验证通过")
|
||
// 停止监听
|
||
return@withTimeout
|
||
} catch (e: OpenCommandApi.InvokeException) {
|
||
logger.warning("${nextEvent.sender.nameCardOrNick}(${nextEvent.sender.id}) 在服务器[${user.serverId}] @${user.uid} 验证失败,信息:${e.message}")
|
||
// 400为验证失败
|
||
if (e.code == 400) {
|
||
nextEvent.subject.sendMessage(nextEvent.message.quote() + "验证失败,请重试")
|
||
} else {
|
||
nextEvent.subject.sendMessage(nextEvent.message.quote() + e.message!!)
|
||
}
|
||
// 不返回,继续监听
|
||
} catch (e: Throwable) {
|
||
// 预期外异常,停止监听器
|
||
logger.warning("${nextEvent.sender.nameCardOrNick}(${nextEvent.sender.id}) 在向服务器[${user.serverId}] @${user.uid} 发起验证时发生了预期外异常", e)
|
||
user.token = ""
|
||
nextEvent.subject.sendMessage(nextEvent.message.quote() + "发生内部错误,已取消验证,请重试")
|
||
return@withTimeout
|
||
}
|
||
}
|
||
}
|
||
} catch (e: OpenCommandApi.InvokeException) {
|
||
subject.sendMessage(message.quote() +
|
||
when (e.code) {
|
||
404 -> "目标玩家不存在或不在线"
|
||
403 -> "请求太频繁,请稍后再试"
|
||
else -> e.message!!
|
||
})
|
||
} catch (e: TimeoutCancellationException) {
|
||
subject.sendMessage(message.quote() + "等待验证超时,请重试")
|
||
} catch (e: Throwable) {
|
||
logger.warning("发送验证码出现预期外的异常", e)
|
||
if (e.message != null) subject.sendMessage(message.quote() + e.message!!)
|
||
}
|
||
}
|
||
} |