Compare commits

...

15 Commits

Author SHA1 Message Date
8f5de4eed8 Impl OP Command (#7)
Update README
(尚未测试!)
2022-10-04 00:29:13 +08:00
c668ba7bcf Update version to v0.4.0 2022-10-03 23:44:09 +08:00
41934a9aa8 Impl alias args (#8) 2022-10-03 23:37:08 +08:00
3859236f43 Fix Sync message issue (#9) 2022-10-03 23:16:31 +08:00
781979f937 Update to allow batch execution of commands (#4) 2022-09-17 12:46:23 +08:00
93f3235dd0 Update to allow alias commands as public commands 2022-09-17 09:16:36 +08:00
33134ad01f Update to group message sync only 2022-09-17 09:07:54 +08:00
25498086a2 Update version to v0.3.0
Add user message support
Add default server setting
2022-09-16 23:38:08 +08:00
78a3c09b43 Fix default config issue (remove / prefix)
Fix public command not handling aliases
2022-09-13 21:16:15 +08:00
137bf7d4f5 Update README 2022-09-12 23:33:30 +08:00
962b439d47 Rename publicCommand to publicCommands 2022-09-12 23:31:17 +08:00
caaf10b813 Update version to v0.2.1 2022-09-12 23:29:42 +08:00
7e50624261 Add Public Commands 2022-09-12 23:29:34 +08:00
08231d4c27 Update version to v0.2.0 2022-09-11 23:58:24 +08:00
4662731543 Fix Https cert issue(#1) 2022-09-11 23:58:10 +08:00
8 changed files with 407 additions and 126 deletions

101
README.md
View File

@ -1,18 +1,24 @@
# 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) 开发的插件
- 服务端必须使用 [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)
本项目**不会教你**如何安装和登录机器人请自行了解Mirai相关信息。
- 服务端必须使用 [OpenCommand](https://github.com/jie65535/gc-opencommand-plugin) 插件
- 若使用后觉得满意,可以给我一个 Star 作为鼓励 ; )
- 若有问题或者建议,欢迎提出 Issue 进行反馈。
- 建议 Watch 本项目以接收更新推送。
# 插件用法
## 首先使用指令添加一个服务器
```shell
# 用法
/jgc addServer <address> [name] [description] # 添加服务器
/jgc addServer <address> [name] [description] # 添加服务器,首个服务器为默认服务器,可切换默认服务器
# 示例
/jgc addServer http://127.0.0.1:443 测试服 本地测试服务器
# 成功返回 "服务器已添加ID为[1]使用servers子命令查看服务器列表"
@ -33,18 +39,22 @@ Mirai机器人相关文档请参阅 [用户手册](https://github.com/mamoe/mira
## 将服务器绑定到机器人所在群聊
```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"
```
在聊天环境执行 Mirai-Console 命令需要另一个插件 [Chat Command](https://github.com/project-mirai/chat-command)
执行GC命令不需要这个见后文
## 绑定账号
玩家想要在群里执行命令需要绑定自己的游戏UID需要在群里发送 `绑定 <uid>` 来向目标账号发送验证码,然后将验证码发到群里完成验证,如图所示
玩家想要在群里执行命令需要绑定自己的游戏UID
需要在群里发送 `绑定 <uid>` 来向目标账号发送验证码,
然后将验证码发到群里完成验证,如图所示
![群内验证示例图](screenshot/verification.png)
@ -79,6 +89,38 @@ _可以通过 `/jgc setBindCommand <prefix>` 来修改执行命令前缀 _
![At群员示例图](screenshot/runAt.png)
---
你还可以一次性执行多条命令,并且可以通过在别名中设置多行命令来实现组合命令
例如:
```shell
!give 102 9999
give 203 999
```
![多行命令示例图](screenshot/batch.jpg)
还可以设置别名为多条命令,用`|`分隔,例如:
`/jgc setCommand 新手礼包 give 102 9999give 202 99give 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>` 可以解除黑名单
_只是一个凭想象增加的功能也许能用上呢_
@ -111,18 +153,27 @@ bindCommand: 绑定
# 示例1!give 1096 lv90
# 示例2!位置
commandPrefix: !
# 执行GC控制台命令前缀用法与普通版本相同区别是仅管理员可用
opCommandPrefix: op
# 命令别名
commandAlias:
无敌: '/prop god on'
关闭无敌: '/prop god off'
无限体力: '/prop ns on'
关闭无限体力: '/prop ns off'
无限能量: '/prop ue on'
关闭无限能量: '/prop ue off'
点亮地图: '/prop unlockmap 1'
解锁地图: '/prop unlockmap 1'
位置: '/pos'
坐标: '/pos'
无敌: 'prop god on'
关闭无敌: 'prop god off'
无限体力: 'prop ns on'
关闭无限体力: 'prop ns off'
无限能量: 'prop ue on'
关闭无限能量: 'prop ue off'
点亮地图: 'prop unlockmap 1'
解锁地图: 'prop unlockmap 1'
位置: 'pos'
坐标: 'pos'
# 公开命令,无需绑定账号也可以执行(可用别名)(必须绑定了控制台令牌才可使用)
publicCommands:
- 'list'
- 'list uid'
# 默认服务器ID未指定服务器ID的命令将使用默认服务器执行。
# 私聊默认使用该服务器。
defaultServerId: 1
```
# 指令列表
@ -131,6 +182,7 @@ commandAlias:
/jgc help # 插件命令用法
/jgc reload # 重载插件配置
/jgc setCommandPrefix <prefix> # 设置执行GC命令前缀
/jgc setOpCommandPrefix <prefix> # 设置执行GC控制台命令前缀
/jgc setBindCommand <prefix> # 设置绑定命令前缀
/jgc op <user> # 设置管理员
/jgc setAdmin <user> # 设置管理员
@ -154,15 +206,18 @@ commandAlias:
## 群相关
```shell
/jgc linkGroup/bindGroup/addGroup <serverId> [group] # 绑定服务器到群,在群内执行可忽略群参数
/jgc enable [group] # 启用指定群执行,在群内执行可忽略群参数
/jgc linkGroup/bindGroup/addGroup [serverId] [group] # 绑定服务器到群若未指定服务器则使用默认服务器ID,在群内执行可忽略群参数
/jgc enable [group] # 启用指定群执行,若未绑定,则自动绑定到默认服务器,在群内执行可忽略群参数
/jgc disable [group] # 禁用指定群执行,在群内执行可忽略群参数
```
## 命令别名相关
## 命令相关
```shell
/jgc setCommand <alias> <command> # 添加命令别名
/jgc listCommands # 列出所有别名
/jgc setCommand <alias> <command> # 添加命令别名(聊天执行可传多行命令)
/jgc removeCommand <alias> # 删除命令别名
/jgc addPublicCommand <command> # 添加公开命令(可用别名)(游客可用)
/jgc removePublicCommand <command> # 删除公开命令
```
# 实体结构

View File

@ -7,7 +7,7 @@ plugins {
}
group = "top.jie65535.mirai"
version = "0.1.0"
version = "0.4.0"
repositories {
maven("https://maven.aliyun.com/repository/public")

BIN
screenshot/batch.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

View File

@ -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.firstIsInstance
import net.mamoe.mirai.message.data.firstIsInstanceOrNull
import top.jie65535.mirai.model.Server
import top.jie65535.mirai.model.User
import top.jie65535.mirai.opencommand.OpenCommandApi
import java.time.LocalDateTime
@ -40,7 +41,7 @@ object JGrasscutterCommand : KotlinPlugin(
JvmPluginDescription(
id = "top.jie65535.mirai.grasscutter-command",
name = "J Grasscutter Command",
version = "0.1.0",
version = "0.4.0",
) {
author("jie65535")
info("""聊天执行GC命令""")
@ -53,23 +54,29 @@ object JGrasscutterCommand : KotlinPlugin(
val eventChannel = GlobalEventChannel.parentScope(this)
// 监听群消息
eventChannel.subscribeAlways<GroupMessageEvent> {
eventChannel.subscribeAlways<MessageEvent> {
// 忽略被拉黑的用户发送的消息
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 = if (this is GroupMessageEvent) {
// 若为群消息,忽略未启用的群
val groupConfig = PluginData.groups.find { it.id == group.id }
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)
return@subscribeAlways
// 解析消息
val message = this.message.joinToString("") {
var message = this.message.joinToString("") {
if (it is At) {
// 替换@群员为@其绑定的Uid
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)
}
// 处理执行游戏控制台命令
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)) {
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)
}
message = message.removePrefix(PluginConfig.commandPrefix).trim()
if (message.isEmpty()) return@subscribeAlways
runMessageHandler(server, message)
}
// 否则如果启用了同步消息,且控制台令牌不为空
else if (server.consoleToken.isNotEmpty() && server.syncMessage) {
// 否则如果启用了同步消息,且控制台令牌不为空,且为群消息时
else if (server.consoleToken.isNotEmpty() && server.syncMessage && this is GroupMessageEvent) {
try {
OpenCommandApi.runCommand(
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!!)
}
}
/**
* 消息转为命令主要识别消息是否包含别名
* @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
}
}

View File

@ -49,7 +49,7 @@ object PluginCommands : CompositeCommand(
@Description("设置执行GC命令前缀")
suspend fun CommandSender.setCommandPrefix(prefix: String) {
if (prefix.isEmpty()) {
sendMessage("前缀不能为空,这会导致每条消息都作为命令处理")
sendMessage("前缀不能为空")
} else {
logger.info("设置执行GC命令前缀为 $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
@Description("设置绑定命令前缀")
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 服务器相关命令
// region 群相关命令
@SubCommand("linkGroup", "bindGroup", "addGroup")
@Description("绑定服务器到群")
suspend fun CommandSender.linkGroup(serverId: Int, group: Group? = getGroupOrNull()) {
@Description("绑定服务器到群若未指定服务器则使用默认服务器ID")
suspend fun CommandSender.linkGroup(serverId: Int = PluginConfig.defaultServerId, group: Group? = getGroupOrNull()) {
if (group == null) {
sendMessage("必须指定群")
return
@ -256,7 +281,7 @@ object PluginCommands : CompositeCommand(
val server = PluginData.servers.find { it.id == serverId }
if (server == null) {
sendMessage("指定服务器ID不存在,请先添加服务器(使用addServer子命令)")
sendMessage("指定服务器不存在,请先添加服务器(使用addServer子命令)")
return
}
@ -272,14 +297,23 @@ object PluginCommands : CompositeCommand(
}
@SubCommand
@Description("启用指定群执行")
@Description("启用指定群执行,若未绑定,则自动绑定到默认服务器")
suspend fun CommandSender.enable(group: Group? = getGroupOrNull()) {
if (group == null) {
sendMessage("必须指定群")
} else {
val g = PluginData.groups.find { it.id == group.id }
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 {
logger.info("启用插件在群 ${group.name}(${group.id})")
g.isEnabled = true
@ -310,7 +344,13 @@ object PluginCommands : CompositeCommand(
// region 命令别名部分
@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) {
if (alias.isEmpty() || command.isEmpty() || command[0].isEmpty()) {
sendMessage("参数不能为空")
@ -326,5 +366,18 @@ object PluginCommands : CompositeCommand(
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
}

View File

@ -34,19 +34,31 @@ object PluginConfig : AutoSavePluginConfig("config") {
"示例2!位置\n")
var commandPrefix: String by value("!")
@ValueDescription("执行GC控制台命令前缀用法与普通版本相同区别是仅管理员可用")
var opCommandPrefix: String by value("op")
@ValueDescription("命令别名")
val commandAlias: MutableMap<String, String> by value(mutableMapOf(
"无敌" to "/prop god on",
"关闭无敌" to "/prop god off",
"无限体力" to "/prop ns on",
"关闭无限体力" to "/prop ns off",
"无限能量" to "/prop ue on",
"关闭无限能量" to "/prop ue off",
"点亮地图" to "/prop unlockmap 1",
"解锁地图" to "/prop unlockmap 1",
"位置" to "/pos",
"坐标" to "/pos",
"无敌" to "prop god on",
"关闭无敌" to "prop god off",
"无限体力" to "prop ns on",
"关闭无限体力" to "prop ns off",
"无限能量" to "prop ue on",
"关闭无限能量" to "prop ue off",
"点亮地图" to "prop unlockmap 1",
"解锁地图" to "prop unlockmap 1",
"位置" to "pos",
"坐标" to "pos",
// TODO ...
))
@ValueDescription("公开命令,无需绑定账号也可以执行(可用别名)(必须绑定了控制台令牌才可使用)")
val publicCommands: MutableSet<String> by value(mutableSetOf(
"list", "list uid"
))
@ValueDescription("默认服务器ID未指定服务器ID的命令将使用默认服务器执行。\n" +
"私聊默认使用该服务器。")
var defaultServerId: Int by value(1)
}

View File

@ -26,13 +26,13 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import top.jie65535.mirai.utils.UnsafeOkHttpClient
@OptIn(ExperimentalSerializationApi::class)
object OpenCommandApi {
private val httpClient = OkHttpClient.Builder().build()
private val httpClient = UnsafeOkHttpClient.getUnsafeOkHttpClient().build()
private val json = Json {
ignoreUnknownKeys = true
encodeDefaults = true
@ -146,7 +146,54 @@ object OpenCommandApi {
* @return 命令执行结果
* @see doRequest
*/
suspend fun runCommand(host: String, token: String, command: String): String? {
return doRequest(host, json.encodeToString(CommandRequest(token, command)))
suspend fun runCommand(host: String, token: String, command: String): String {
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()
}
}
}

View 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)
}
}
}
}