mirror of
https://github.com/jie65535/JGrasscutterCommand.git
synced 2025-06-11 17:39:13 +08:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
8f5de4eed8 | |||
c668ba7bcf | |||
41934a9aa8 | |||
3859236f43 | |||
781979f937 | |||
93f3235dd0 | |||
33134ad01f | |||
25498086a2 | |||
78a3c09b43 | |||
137bf7d4f5 | |||
962b439d47 | |||
caaf10b813 | |||
7e50624261 | |||
08231d4c27 | |||
4662731543 |
101
README.md
101
README.md
@ -1,18 +1,24 @@
|
|||||||
# J Grasscutter Command
|
# J Grasscutter Command
|
||||||
# 在QQ群里远程执行命令的插件
|
|
||||||
|
This repo is only used for the Chinese social software QQ, so only the Chinese version is available.
|
||||||
|
|
||||||
|
# 用QQ执行GC命令的机器人插件
|
||||||
|
|
||||||
- 基于 [Mirai-Console](https://github.com/mamoe/mirai-console) 开发的插件
|
- 基于 [Mirai-Console](https://github.com/mamoe/mirai-console) 开发的插件
|
||||||
- 服务端必须使用 [OpenCommand](https://github.com/jie65535/gc-opencommand-plugin) 插件
|
> Mirai机器人相关文档请参阅 [用户手册](https://github.com/mamoe/mirai/blob/dev/docs/UserManual.md),
|
||||||
|
> 本项目**不会教你**如何安装和登录机器人,请自行了解Mirai相关信息。_目前暂不考虑其它框架或平台,有意者可自行移植_
|
||||||
|
|
||||||
Mirai机器人相关文档请参阅 [用户手册](https://github.com/mamoe/mirai/blob/dev/docs/UserManual.md),
|
- 服务端必须使用 [OpenCommand](https://github.com/jie65535/gc-opencommand-plugin) 插件
|
||||||
本项目**不会教你**如何安装和登录机器人,请自行了解Mirai相关信息。
|
- 若使用后觉得满意,可以给我一个 Star 作为鼓励 ; )
|
||||||
|
- 若有问题或者建议,欢迎提出 Issue 进行反馈。
|
||||||
|
- 建议 Watch 本项目以接收更新推送。
|
||||||
|
|
||||||
# 插件用法
|
# 插件用法
|
||||||
|
|
||||||
## 首先使用指令添加一个服务器
|
## 首先使用指令添加一个服务器
|
||||||
```shell
|
```shell
|
||||||
# 用法
|
# 用法
|
||||||
/jgc addServer <address> [name] [description] # 添加服务器
|
/jgc addServer <address> [name] [description] # 添加服务器,首个服务器为默认服务器,可切换默认服务器
|
||||||
# 示例
|
# 示例
|
||||||
/jgc addServer http://127.0.0.1:443 测试服 本地测试服务器
|
/jgc addServer http://127.0.0.1:443 测试服 本地测试服务器
|
||||||
# 成功返回 "服务器已添加,ID为[1],使用servers子命令查看服务器列表"
|
# 成功返回 "服务器已添加,ID为[1],使用servers子命令查看服务器列表"
|
||||||
@ -33,18 +39,22 @@ Mirai机器人相关文档请参阅 [用户手册](https://github.com/mamoe/mira
|
|||||||
## 将服务器绑定到机器人所在群聊
|
## 将服务器绑定到机器人所在群聊
|
||||||
```shell
|
```shell
|
||||||
# 用法
|
# 用法
|
||||||
/jgc linkGroup/bindGroup/addGroup <serverId> [group] # 绑定服务器到群,在群内执行可忽略群参数
|
/jgc linkGroup/bindGroup/addGroup [serverId] [group] # 绑定服务器到群,若未指定服务器则使用默认服务器ID,在群内执行可忽略群参数
|
||||||
# 示例(控制台)
|
# 示例(控制台)
|
||||||
/jgc linkGroup 1 群号
|
/jgc linkGroup 1 123456 # 指定将服务器[1]绑定到群[123456]
|
||||||
# 示例(群里)
|
# 示例(群里)
|
||||||
/jgc linkGroup 1
|
/jgc linkGroup 1 # 指定绑定到服务器[1]
|
||||||
|
/jgc linkGroup # 忽略服务器参数,将使用默认服务器
|
||||||
|
/jgc enable # 若启用的群未绑定,将自动绑定到默认服务器
|
||||||
# 成功返回 "OK"
|
# 成功返回 "OK"
|
||||||
```
|
```
|
||||||
在聊天环境执行 Mirai-Console 命令需要另一个插件 [Chat Command](https://github.com/project-mirai/chat-command)
|
在聊天环境执行 Mirai-Console 命令需要另一个插件 [Chat Command](https://github.com/project-mirai/chat-command)
|
||||||
执行GC命令不需要这个,见后文
|
执行GC命令不需要这个,见后文
|
||||||
|
|
||||||
## 绑定账号
|
## 绑定账号
|
||||||
玩家想要在群里执行命令,需要绑定自己的游戏UID,需要在群里发送 `绑定 <uid>` 来向目标账号发送验证码,然后将验证码发到群里完成验证,如图所示
|
玩家想要在群里执行命令,需要绑定自己的游戏UID,
|
||||||
|
需要在群里发送 `绑定 <uid>` 来向目标账号发送验证码,
|
||||||
|
然后将验证码发到群里完成验证,如图所示
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -79,6 +89,38 @@ _可以通过 `/jgc setBindCommand <prefix>` 来修改执行命令前缀 _(例
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
你还可以一次性执行多条命令,并且可以通过在别名中设置多行命令来实现组合命令
|
||||||
|
|
||||||
|
例如:
|
||||||
|
```shell
|
||||||
|
!give 102 9999
|
||||||
|
give 203 999
|
||||||
|
```
|
||||||
|

|
||||||
|
|
||||||
|
还可以设置别名为多条命令,用`|`分隔,例如:
|
||||||
|
|
||||||
|
`/jgc setCommand 新手礼包 give 102 9999|give 202 99|give 203 99`
|
||||||
|
|
||||||
|
然后通过别名批量执行命令,例如:`!新手礼包`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
v0.4.0 开始,你可以在别名后跟额外参数,会附加到每一行命令,例如:`!新手礼包 @张三`
|
||||||
|
|
||||||
|
将会执行
|
||||||
|
```shell
|
||||||
|
give 102 9999 @10001
|
||||||
|
give 202 99 @10001
|
||||||
|
give 203 99 @10001
|
||||||
|
```
|
||||||
|
|
||||||
|
## 私聊执行
|
||||||
|
v0.3.0 开始,玩家可以**私聊机器人**进行账号的绑定和命令的执行,
|
||||||
|
但是目前只能在**默认服务器**中执行,无法指定执行的服务器。
|
||||||
|
|
||||||
## 拉黑用户
|
## 拉黑用户
|
||||||
如果你想禁止某个用户使用本插件执行命令,可以使用 `/ban <qq>` 来拉黑,使用 `/unban <qq>` 可以解除黑名单
|
如果你想禁止某个用户使用本插件执行命令,可以使用 `/ban <qq>` 来拉黑,使用 `/unban <qq>` 可以解除黑名单
|
||||||
_(只是一个凭想象增加的功能,也许能用上呢)_
|
_(只是一个凭想象增加的功能,也许能用上呢)_
|
||||||
@ -111,18 +153,27 @@ bindCommand: 绑定
|
|||||||
# 示例1:!give 1096 lv90
|
# 示例1:!give 1096 lv90
|
||||||
# 示例2:!位置
|
# 示例2:!位置
|
||||||
commandPrefix: !
|
commandPrefix: !
|
||||||
|
# 执行GC控制台命令前缀,用法与普通版本相同,区别是仅管理员可用
|
||||||
|
opCommandPrefix: op
|
||||||
# 命令别名
|
# 命令别名
|
||||||
commandAlias:
|
commandAlias:
|
||||||
无敌: '/prop god on'
|
无敌: 'prop god on'
|
||||||
关闭无敌: '/prop god off'
|
关闭无敌: 'prop god off'
|
||||||
无限体力: '/prop ns on'
|
无限体力: 'prop ns on'
|
||||||
关闭无限体力: '/prop ns off'
|
关闭无限体力: 'prop ns off'
|
||||||
无限能量: '/prop ue on'
|
无限能量: 'prop ue on'
|
||||||
关闭无限能量: '/prop ue off'
|
关闭无限能量: 'prop ue off'
|
||||||
点亮地图: '/prop unlockmap 1'
|
点亮地图: 'prop unlockmap 1'
|
||||||
解锁地图: '/prop unlockmap 1'
|
解锁地图: 'prop unlockmap 1'
|
||||||
位置: '/pos'
|
位置: 'pos'
|
||||||
坐标: '/pos'
|
坐标: 'pos'
|
||||||
|
# 公开命令,无需绑定账号也可以执行(可用别名)(必须绑定了控制台令牌才可使用)
|
||||||
|
publicCommands:
|
||||||
|
- 'list'
|
||||||
|
- 'list uid'
|
||||||
|
# 默认服务器ID,未指定服务器ID的命令将使用默认服务器执行。
|
||||||
|
# 私聊默认使用该服务器。
|
||||||
|
defaultServerId: 1
|
||||||
```
|
```
|
||||||
|
|
||||||
# 指令列表
|
# 指令列表
|
||||||
@ -131,6 +182,7 @@ commandAlias:
|
|||||||
/jgc help # 插件命令用法
|
/jgc help # 插件命令用法
|
||||||
/jgc reload # 重载插件配置
|
/jgc reload # 重载插件配置
|
||||||
/jgc setCommandPrefix <prefix> # 设置执行GC命令前缀
|
/jgc setCommandPrefix <prefix> # 设置执行GC命令前缀
|
||||||
|
/jgc setOpCommandPrefix <prefix> # 设置执行GC控制台命令前缀
|
||||||
/jgc setBindCommand <prefix> # 设置绑定命令前缀
|
/jgc setBindCommand <prefix> # 设置绑定命令前缀
|
||||||
/jgc op <user> # 设置管理员
|
/jgc op <user> # 设置管理员
|
||||||
/jgc setAdmin <user> # 设置管理员
|
/jgc setAdmin <user> # 设置管理员
|
||||||
@ -154,15 +206,18 @@ commandAlias:
|
|||||||
|
|
||||||
## 群相关
|
## 群相关
|
||||||
```shell
|
```shell
|
||||||
/jgc linkGroup/bindGroup/addGroup <serverId> [group] # 绑定服务器到群,在群内执行可忽略群参数
|
/jgc linkGroup/bindGroup/addGroup [serverId] [group] # 绑定服务器到群,若未指定服务器则使用默认服务器ID,在群内执行可忽略群参数
|
||||||
/jgc enable [group] # 启用指定群执行,在群内执行可忽略群参数
|
/jgc enable [group] # 启用指定群执行,若未绑定,则自动绑定到默认服务器,在群内执行可忽略群参数
|
||||||
/jgc disable [group] # 禁用指定群执行,在群内执行可忽略群参数
|
/jgc disable [group] # 禁用指定群执行,在群内执行可忽略群参数
|
||||||
```
|
```
|
||||||
|
|
||||||
## 命令别名相关
|
## 命令相关
|
||||||
```shell
|
```shell
|
||||||
/jgc setCommand <alias> <command> # 添加命令别名
|
/jgc listCommands # 列出所有别名
|
||||||
|
/jgc setCommand <alias> <command> # 添加命令别名(聊天执行可传多行命令)
|
||||||
/jgc removeCommand <alias> # 删除命令别名
|
/jgc removeCommand <alias> # 删除命令别名
|
||||||
|
/jgc addPublicCommand <command> # 添加公开命令(可用别名)(游客可用)
|
||||||
|
/jgc removePublicCommand <command> # 删除公开命令
|
||||||
```
|
```
|
||||||
|
|
||||||
# 实体结构
|
# 实体结构
|
||||||
|
@ -7,7 +7,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "top.jie65535.mirai"
|
group = "top.jie65535.mirai"
|
||||||
version = "0.1.0"
|
version = "0.4.0"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven("https://maven.aliyun.com/repository/public")
|
maven("https://maven.aliyun.com/repository/public")
|
||||||
|
BIN
screenshot/batch.jpg
Normal file
BIN
screenshot/batch.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 180 KiB |
@ -32,6 +32,7 @@ import net.mamoe.mirai.message.data.MessageSource.Key.quote
|
|||||||
import net.mamoe.mirai.message.data.PlainText
|
import net.mamoe.mirai.message.data.PlainText
|
||||||
import net.mamoe.mirai.message.data.firstIsInstance
|
import net.mamoe.mirai.message.data.firstIsInstance
|
||||||
import net.mamoe.mirai.message.data.firstIsInstanceOrNull
|
import net.mamoe.mirai.message.data.firstIsInstanceOrNull
|
||||||
|
import top.jie65535.mirai.model.Server
|
||||||
import top.jie65535.mirai.model.User
|
import top.jie65535.mirai.model.User
|
||||||
import top.jie65535.mirai.opencommand.OpenCommandApi
|
import top.jie65535.mirai.opencommand.OpenCommandApi
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@ -40,7 +41,7 @@ object JGrasscutterCommand : KotlinPlugin(
|
|||||||
JvmPluginDescription(
|
JvmPluginDescription(
|
||||||
id = "top.jie65535.mirai.grasscutter-command",
|
id = "top.jie65535.mirai.grasscutter-command",
|
||||||
name = "J Grasscutter Command",
|
name = "J Grasscutter Command",
|
||||||
version = "0.1.0",
|
version = "0.4.0",
|
||||||
) {
|
) {
|
||||||
author("jie65535")
|
author("jie65535")
|
||||||
info("""聊天执行GC命令""")
|
info("""聊天执行GC命令""")
|
||||||
@ -53,23 +54,29 @@ object JGrasscutterCommand : KotlinPlugin(
|
|||||||
|
|
||||||
val eventChannel = GlobalEventChannel.parentScope(this)
|
val eventChannel = GlobalEventChannel.parentScope(this)
|
||||||
// 监听群消息
|
// 监听群消息
|
||||||
eventChannel.subscribeAlways<GroupMessageEvent> {
|
eventChannel.subscribeAlways<MessageEvent> {
|
||||||
// 忽略被拉黑的用户发送的消息
|
// 忽略被拉黑的用户发送的消息
|
||||||
if (PluginConfig.blacklist.contains(sender.id))
|
if (PluginConfig.blacklist.contains(sender.id))
|
||||||
return@subscribeAlways
|
return@subscribeAlways
|
||||||
|
|
||||||
// 忽略未启用的群消息
|
val server = if (this is GroupMessageEvent) {
|
||||||
val groupConfig = PluginData.groups.find { it.id == group.id }
|
// 若为群消息,忽略未启用的群
|
||||||
if (groupConfig == null || !groupConfig.isEnabled)
|
val groupConfig = PluginData.groups.find { it.id == group.id }
|
||||||
return@subscribeAlways
|
if (groupConfig == null || !groupConfig.isEnabled)
|
||||||
|
return@subscribeAlways
|
||||||
|
// 获取群绑定的服务器
|
||||||
|
PluginData.servers.find { it.id == groupConfig.serverId }
|
||||||
|
} else {
|
||||||
|
// 否则为私聊消息,使用默认服务器
|
||||||
|
PluginData.servers.find { it.id == PluginConfig.defaultServerId }
|
||||||
|
}
|
||||||
|
|
||||||
// 忽略未启用的服务器
|
// 忽略未启用的服务器
|
||||||
val server = PluginData.servers.find { it.id == groupConfig.serverId }
|
|
||||||
if (server == null || !server.isEnabled)
|
if (server == null || !server.isEnabled)
|
||||||
return@subscribeAlways
|
return@subscribeAlways
|
||||||
|
|
||||||
// 解析消息
|
// 解析消息
|
||||||
val message = this.message.joinToString("") {
|
var message = this.message.joinToString("") {
|
||||||
if (it is At) {
|
if (it is At) {
|
||||||
// 替换@群员为@其绑定的Uid
|
// 替换@群员为@其绑定的Uid
|
||||||
val user = PluginData.users.find { user -> user.id == it.target && user.serverId == server.id }
|
val user = PluginData.users.find { user -> user.id == it.target && user.serverId == server.id }
|
||||||
@ -102,81 +109,20 @@ object JGrasscutterCommand : KotlinPlugin(
|
|||||||
// 发送验证码,并提示要求回复验证码
|
// 发送验证码,并提示要求回复验证码
|
||||||
sendCode(server.address, user)
|
sendCode(server.address, user)
|
||||||
}
|
}
|
||||||
|
// 处理执行游戏控制台命令
|
||||||
|
else if (message.startsWith(PluginConfig.opCommandPrefix)) {
|
||||||
|
message = message.removePrefix(PluginConfig.opCommandPrefix).trim()
|
||||||
|
if (message.isEmpty()) return@subscribeAlways
|
||||||
|
runOpMessageHandler(server, message)
|
||||||
|
}
|
||||||
// 处理执行游戏命令
|
// 处理执行游戏命令
|
||||||
else if (message.startsWith(PluginConfig.commandPrefix)) {
|
else if (message.startsWith(PluginConfig.commandPrefix)) {
|
||||||
var command = message.removePrefix(PluginConfig.commandPrefix).trim()
|
message = message.removePrefix(PluginConfig.commandPrefix).trim()
|
||||||
if (command.isEmpty()) {
|
if (message.isEmpty()) return@subscribeAlways
|
||||||
return@subscribeAlways
|
runMessageHandler(server, message)
|
||||||
}
|
|
||||||
// 检查是否使用别名
|
|
||||||
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) {
|
else if (server.consoleToken.isNotEmpty() && server.syncMessage && this is GroupMessageEvent) {
|
||||||
try {
|
try {
|
||||||
OpenCommandApi.runCommand(
|
OpenCommandApi.runCommand(
|
||||||
server.address,
|
server.address,
|
||||||
@ -189,7 +135,7 @@ object JGrasscutterCommand : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Plugin loaded")
|
logger.info("Plugin loaded. Github: https://github.com/jie65535/JGrasscutterCommand")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -261,4 +207,128 @@ object JGrasscutterCommand : KotlinPlugin(
|
|||||||
if (e.message != null) subject.sendMessage(message.quote() + e.message!!)
|
if (e.message != null) subject.sendMessage(message.quote() + e.message!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息转为命令(主要识别消息是否包含别名)
|
||||||
|
* @param message 要执行命令的消息原文
|
||||||
|
* @return 处理后的命令文本
|
||||||
|
*/
|
||||||
|
private fun toCommands(message: String): String {
|
||||||
|
// 检查是否使用别名
|
||||||
|
val sp = message.indexOf(' ')
|
||||||
|
val command = if (sp > 0) { // 如果中间存在空格,则取空格前的内容匹配别名,空格后的内容作为参数附加到命令
|
||||||
|
PluginConfig.commandAlias[message.substring(0 until sp)]
|
||||||
|
} else {
|
||||||
|
PluginConfig.commandAlias[message]
|
||||||
|
}
|
||||||
|
return if (command.isNullOrEmpty()) {
|
||||||
|
message
|
||||||
|
} else {
|
||||||
|
if (sp in 1 until message.length-1) { // 如果命令存在额外参数
|
||||||
|
val args = message.substring(sp)
|
||||||
|
command.replace("|", "$args\n") + args // 为每一行附加参数
|
||||||
|
} else {
|
||||||
|
command.replace('|', '\n') // 若为多行命令,替换为换行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行命令消息处理器
|
||||||
|
* @param server 执行的服务器
|
||||||
|
* @param message 执行的命令消息
|
||||||
|
*/
|
||||||
|
private suspend fun MessageEvent.runMessageHandler(server: Server, message: String) {
|
||||||
|
val commands = toCommands(message)
|
||||||
|
|
||||||
|
// 执行的用户
|
||||||
|
val user: User? = PluginData.users.find { it.id == sender.id && it.serverId == server.id }
|
||||||
|
val token = if (user == null || user.token.isEmpty()) {
|
||||||
|
if (server.consoleToken.isEmpty()) // 如果未找到用户且控制台令牌未设置,则直接忽略
|
||||||
|
return
|
||||||
|
// 检查执行者是否为管理员
|
||||||
|
if (PluginConfig.administrators.contains(sender.id)) {
|
||||||
|
logger.info("管理员 ${sender.nameCardOrNick}(${sender.id}) 执行命令:$commands")
|
||||||
|
// 设置控制台令牌
|
||||||
|
server.consoleToken
|
||||||
|
} else if (PluginConfig.publicCommands.contains(commands) // 检测执行的命令是否为公开命令
|
||||||
|
|| PluginConfig.publicCommands.contains(message) // 检测执行命令的原文是否为公开命令
|
||||||
|
) {
|
||||||
|
// 允许游客执行控制台命令
|
||||||
|
logger.info("游客用户 ${sender.nameCardOrNick}(${sender.id}) 执行公开命令:$commands")
|
||||||
|
server.consoleToken
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("用户 ${sender.nameCardOrNick}(${sender.id}) 执行命令:$commands")
|
||||||
|
// 使用用户缓存令牌
|
||||||
|
user.token
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行命令
|
||||||
|
if (runCommands(server.address, token, commands) && user != null) {
|
||||||
|
// 计数并更新最后运行时间
|
||||||
|
++user.runCount
|
||||||
|
user.lastRunTime = LocalDateTime.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行管理员命令消息处理器
|
||||||
|
* @param server 执行的服务器
|
||||||
|
* @param message 执行的命令消息
|
||||||
|
*/
|
||||||
|
private suspend fun MessageEvent.runOpMessageHandler(server: Server, message: String) {
|
||||||
|
// 如果未设置控制台令牌,则直接忽略
|
||||||
|
if (server.consoleToken.isEmpty())
|
||||||
|
return
|
||||||
|
// 如果用户不是管理员,也直接忽略(可提示,但没必要)
|
||||||
|
if (!PluginConfig.administrators.contains(sender.id))
|
||||||
|
return
|
||||||
|
val commands = toCommands(message)
|
||||||
|
logger.info("管理员 ${sender.nameCardOrNick}(${sender.id}) 执行命令:$commands")
|
||||||
|
runCommands(server.address, server.consoleToken, commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行命令并向执行者发送结果
|
||||||
|
* @param host 服务器地址
|
||||||
|
* @param token 令牌
|
||||||
|
* @param commands 命令行
|
||||||
|
* @return 返回是否正常执行
|
||||||
|
*/
|
||||||
|
private suspend fun MessageEvent.runCommands(host: String, token: String, commands: String): Boolean {
|
||||||
|
try {
|
||||||
|
// 调用接口执行命令
|
||||||
|
val response = OpenCommandApi.runCommands(host, token, commands)
|
||||||
|
subject.sendMessage(this.message.quote() + response)
|
||||||
|
return true
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
@ -49,7 +49,7 @@ object PluginCommands : CompositeCommand(
|
|||||||
@Description("设置执行GC命令前缀")
|
@Description("设置执行GC命令前缀")
|
||||||
suspend fun CommandSender.setCommandPrefix(prefix: String) {
|
suspend fun CommandSender.setCommandPrefix(prefix: String) {
|
||||||
if (prefix.isEmpty()) {
|
if (prefix.isEmpty()) {
|
||||||
sendMessage("前缀不能为空,这会导致每条消息都作为命令处理")
|
sendMessage("前缀不能为空")
|
||||||
} else {
|
} else {
|
||||||
logger.info("设置执行GC命令前缀为 $prefix")
|
logger.info("设置执行GC命令前缀为 $prefix")
|
||||||
PluginConfig.commandPrefix = prefix
|
PluginConfig.commandPrefix = prefix
|
||||||
@ -57,6 +57,18 @@ object PluginCommands : CompositeCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
@Description("设置执行GC控制台命令前缀")
|
||||||
|
suspend fun CommandSender.setOpCommandPrefix(prefix: String) {
|
||||||
|
if (prefix.isEmpty()) {
|
||||||
|
sendMessage("前缀不能为空")
|
||||||
|
} else {
|
||||||
|
logger.info("设置执行GC控制台命令前缀为 $prefix")
|
||||||
|
PluginConfig.opCommandPrefix = prefix
|
||||||
|
sendMessage("OK")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SubCommand
|
@SubCommand
|
||||||
@Description("设置绑定命令前缀")
|
@Description("设置绑定命令前缀")
|
||||||
suspend fun CommandSender.setBindCommand(prefix: String) {
|
suspend fun CommandSender.setBindCommand(prefix: String) {
|
||||||
@ -242,13 +254,26 @@ object PluginCommands : CompositeCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
@Description("设置默认服务器(初始值为首个创建的服务器)")
|
||||||
|
suspend fun CommandSender.setDefaultServer(id: Int) {
|
||||||
|
val server = PluginData.servers.find { it.id == id }
|
||||||
|
if (server == null) {
|
||||||
|
sendMessage("未找到指定服务器")
|
||||||
|
} else {
|
||||||
|
PluginConfig.defaultServerId = id
|
||||||
|
logger.info("已将 [$id] ${server.name} 设置为默认服务器")
|
||||||
|
sendMessage("OK")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// endregion 服务器相关命令
|
// endregion 服务器相关命令
|
||||||
|
|
||||||
// region 群相关命令
|
// region 群相关命令
|
||||||
|
|
||||||
@SubCommand("linkGroup", "bindGroup", "addGroup")
|
@SubCommand("linkGroup", "bindGroup", "addGroup")
|
||||||
@Description("绑定服务器到群")
|
@Description("绑定服务器到群,若未指定服务器则使用默认服务器ID")
|
||||||
suspend fun CommandSender.linkGroup(serverId: Int, group: Group? = getGroupOrNull()) {
|
suspend fun CommandSender.linkGroup(serverId: Int = PluginConfig.defaultServerId, group: Group? = getGroupOrNull()) {
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
sendMessage("必须指定群")
|
sendMessage("必须指定群")
|
||||||
return
|
return
|
||||||
@ -256,7 +281,7 @@ object PluginCommands : CompositeCommand(
|
|||||||
|
|
||||||
val server = PluginData.servers.find { it.id == serverId }
|
val server = PluginData.servers.find { it.id == serverId }
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
sendMessage("指定服务器ID不存在,请先添加服务器(使用addServer子命令)")
|
sendMessage("指定服务器不存在,请先添加服务器(使用addServer子命令)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,14 +297,23 @@ object PluginCommands : CompositeCommand(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SubCommand
|
@SubCommand
|
||||||
@Description("启用指定群执行")
|
@Description("启用指定群执行,若未绑定,则自动绑定到默认服务器")
|
||||||
suspend fun CommandSender.enable(group: Group? = getGroupOrNull()) {
|
suspend fun CommandSender.enable(group: Group? = getGroupOrNull()) {
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
sendMessage("必须指定群")
|
sendMessage("必须指定群")
|
||||||
} else {
|
} else {
|
||||||
val g = PluginData.groups.find { it.id == group.id }
|
val g = PluginData.groups.find { it.id == group.id }
|
||||||
if (g == null) {
|
if (g == null) {
|
||||||
sendMessage("请先绑定群到服务器(使用linkGroup子命令)")
|
// 当启用的群是未初始化的群时,使用默认服务器进行初始化
|
||||||
|
val server = PluginData.servers.find { it.id == PluginConfig.defaultServerId }
|
||||||
|
if (server != null) {
|
||||||
|
// 绑定到默认服务器
|
||||||
|
logger.info("将默认服务器[${server.id}] ${server.name} ${server.address} 绑定到群 ${group.name}(${group.id})")
|
||||||
|
PluginData.groups.add(GroupConfig(group.id, server.id))
|
||||||
|
sendMessage("OK,已绑定到默认服务器")
|
||||||
|
} else {
|
||||||
|
sendMessage("请先绑定群到服务器(使用linkGroup子命令)")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.info("启用插件在群 ${group.name}(${group.id})")
|
logger.info("启用插件在群 ${group.name}(${group.id})")
|
||||||
g.isEnabled = true
|
g.isEnabled = true
|
||||||
@ -310,7 +344,13 @@ object PluginCommands : CompositeCommand(
|
|||||||
// region 命令别名部分
|
// region 命令别名部分
|
||||||
|
|
||||||
@SubCommand
|
@SubCommand
|
||||||
@Description("添加命令别名")
|
@Description("列出所有别名")
|
||||||
|
suspend fun CommandSender.listCommands() {
|
||||||
|
sendMessage(PluginConfig.commandAlias.map { "[${it.key}] ${it.value}" }.joinToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
@Description("添加命令别名,多条命令用|隔开")
|
||||||
suspend fun CommandSender.setCommand(alias: String, vararg command: String) {
|
suspend fun CommandSender.setCommand(alias: String, vararg command: String) {
|
||||||
if (alias.isEmpty() || command.isEmpty() || command[0].isEmpty()) {
|
if (alias.isEmpty() || command.isEmpty() || command[0].isEmpty()) {
|
||||||
sendMessage("参数不能为空")
|
sendMessage("参数不能为空")
|
||||||
@ -326,5 +366,18 @@ object PluginCommands : CompositeCommand(
|
|||||||
sendMessage("OK")
|
sendMessage("OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
@Description("添加公开命令(游客可执行)(可用别名)")
|
||||||
|
suspend fun CommandSender.addPublicCommand(command: String) {
|
||||||
|
PluginConfig.publicCommands.add(command)
|
||||||
|
sendMessage("OK")
|
||||||
|
}
|
||||||
|
@SubCommand
|
||||||
|
@Description("删除公开命令")
|
||||||
|
suspend fun CommandSender.removePublicCommand(alias: String) {
|
||||||
|
PluginConfig.publicCommands.remove(alias)
|
||||||
|
sendMessage("OK")
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
@ -34,19 +34,31 @@ object PluginConfig : AutoSavePluginConfig("config") {
|
|||||||
"示例2:!位置\n")
|
"示例2:!位置\n")
|
||||||
var commandPrefix: String by value("!")
|
var commandPrefix: String by value("!")
|
||||||
|
|
||||||
|
@ValueDescription("执行GC控制台命令前缀,用法与普通版本相同,区别是仅管理员可用")
|
||||||
|
var opCommandPrefix: String by value("op")
|
||||||
|
|
||||||
@ValueDescription("命令别名")
|
@ValueDescription("命令别名")
|
||||||
val commandAlias: MutableMap<String, String> by value(mutableMapOf(
|
val commandAlias: MutableMap<String, String> by value(mutableMapOf(
|
||||||
"无敌" to "/prop god on",
|
"无敌" to "prop god on",
|
||||||
"关闭无敌" to "/prop god off",
|
"关闭无敌" to "prop god off",
|
||||||
"无限体力" to "/prop ns on",
|
"无限体力" to "prop ns on",
|
||||||
"关闭无限体力" to "/prop ns off",
|
"关闭无限体力" to "prop ns off",
|
||||||
"无限能量" to "/prop ue on",
|
"无限能量" to "prop ue on",
|
||||||
"关闭无限能量" to "/prop ue off",
|
"关闭无限能量" to "prop ue off",
|
||||||
"点亮地图" to "/prop unlockmap 1",
|
"点亮地图" to "prop unlockmap 1",
|
||||||
"解锁地图" to "/prop unlockmap 1",
|
"解锁地图" to "prop unlockmap 1",
|
||||||
"位置" to "/pos",
|
"位置" to "pos",
|
||||||
"坐标" to "/pos",
|
"坐标" to "pos",
|
||||||
|
|
||||||
// TODO ...
|
// TODO ...
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@ValueDescription("公开命令,无需绑定账号也可以执行(可用别名)(必须绑定了控制台令牌才可使用)")
|
||||||
|
val publicCommands: MutableSet<String> by value(mutableSetOf(
|
||||||
|
"list", "list uid"
|
||||||
|
))
|
||||||
|
|
||||||
|
@ValueDescription("默认服务器ID,未指定服务器ID的命令将使用默认服务器执行。\n" +
|
||||||
|
"私聊默认使用该服务器。")
|
||||||
|
var defaultServerId: Int by value(1)
|
||||||
}
|
}
|
@ -26,13 +26,13 @@ import kotlinx.serialization.encodeToString
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import top.jie65535.mirai.utils.UnsafeOkHttpClient
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
object OpenCommandApi {
|
object OpenCommandApi {
|
||||||
private val httpClient = OkHttpClient.Builder().build()
|
private val httpClient = UnsafeOkHttpClient.getUnsafeOkHttpClient().build()
|
||||||
private val json = Json {
|
private val json = Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
@ -146,7 +146,54 @@ object OpenCommandApi {
|
|||||||
* @return 命令执行结果
|
* @return 命令执行结果
|
||||||
* @see doRequest
|
* @see doRequest
|
||||||
*/
|
*/
|
||||||
suspend fun runCommand(host: String, token: String, command: String): String? {
|
suspend fun runCommand(host: String, token: String, command: String): String {
|
||||||
return doRequest(host, json.encodeToString(CommandRequest(token, command)))
|
val ret = doRequest(host, json.encodeToString(CommandRequest(token, command)))
|
||||||
|
return if (ret.isNullOrEmpty()) "OK" else ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行命令,成功时返回命令执行结果,失败时抛出异常,异常详情参考doRequest描述
|
||||||
|
* 允许单次执行多条命令,用换行(\n)分隔
|
||||||
|
* @param host 服务器地址
|
||||||
|
* @param token 持久令牌
|
||||||
|
* @param rawCommands 命令行
|
||||||
|
* @return 命令执行结果
|
||||||
|
* @see doRequest
|
||||||
|
*/
|
||||||
|
suspend fun runCommands(host: String, token: String, rawCommands: String): String {
|
||||||
|
// 去除首尾空白、命令前缀。使用api执行命令不需要前缀
|
||||||
|
val commands = rawCommands.splitToSequence('\n')
|
||||||
|
.map { it.trim().trimStart('/').trimStart('!') }
|
||||||
|
.toList()
|
||||||
|
return if (commands.isEmpty())
|
||||||
|
throw IllegalArgumentException("命令不能为空!")
|
||||||
|
else if (commands.size == 1) {
|
||||||
|
val ret = doRequest(host, json.encodeToString(CommandRequest(token, commands[0])))
|
||||||
|
if (ret.isNullOrEmpty()) "OK" else ret
|
||||||
|
} else {
|
||||||
|
val msg = StringBuilder()
|
||||||
|
var okCount = 0
|
||||||
|
for (cmd in commands) {
|
||||||
|
val ret = doRequest(host, json.encodeToString(CommandRequest(token, cmd)))
|
||||||
|
if (ret.isNullOrEmpty()) {
|
||||||
|
if (okCount++ == 0)
|
||||||
|
msg.append("OK")
|
||||||
|
} else {
|
||||||
|
if (okCount > 0) {
|
||||||
|
if (okCount > 1) {
|
||||||
|
msg.append('*').append(okCount)
|
||||||
|
}
|
||||||
|
msg.appendLine()
|
||||||
|
okCount = 0
|
||||||
|
}
|
||||||
|
msg.appendLine(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (okCount > 1) // OK*n
|
||||||
|
msg.append('*').append(okCount)
|
||||||
|
else if (msg[msg.length-1] == '\n') // 移除额外的换行
|
||||||
|
msg.deleteCharAt(msg.length-1)
|
||||||
|
msg.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
44
src/main/kotlin/utils/UnsafeOkHttpClient.kt
Normal file
44
src/main/kotlin/utils/UnsafeOkHttpClient.kt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package top.jie65535.mirai.utils
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.security.cert.CertificateException
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.TrustManager
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
|
class UnsafeOkHttpClient {
|
||||||
|
companion object {
|
||||||
|
fun getUnsafeOkHttpClient(): OkHttpClient.Builder {
|
||||||
|
try {
|
||||||
|
// Create a trust manager that does not validate certificate chains
|
||||||
|
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
||||||
|
@Throws(CertificateException::class)
|
||||||
|
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(CertificateException::class)
|
||||||
|
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> {
|
||||||
|
return arrayOf()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Install the all-trusting trust manager
|
||||||
|
val sslContext = SSLContext.getInstance("SSL")
|
||||||
|
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
|
||||||
|
// Create an ssl socket factory with our all-trusting manager
|
||||||
|
val sslSocketFactory = sslContext.socketFactory
|
||||||
|
|
||||||
|
val builder = OkHttpClient.Builder()
|
||||||
|
builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
|
||||||
|
// builder.hostnameVerifier { _, _ -> true }
|
||||||
|
builder.hostnameVerifier { _, _ -> true }
|
||||||
|
|
||||||
|
return builder
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user