mirror of
https://github.com/jie65535/JGrasscutterCommand.git
synced 2025-06-01 17:29:13 +08:00
Initial commit
This commit is contained in:
parent
10fa82b900
commit
ea83aec168
275
README.md
275
README.md
@ -0,0 +1,275 @@
|
||||
# J Grasscutter Command
|
||||
# 在QQ群里远程执行命令的插件
|
||||
|
||||
- 基于 [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相关信息。
|
||||
|
||||
# 插件用法
|
||||
|
||||
## 首先使用指令添加一个服务器
|
||||
```shell
|
||||
# 用法
|
||||
/jgc addServer <address> [name] [description] # 添加服务器
|
||||
# 示例
|
||||
/jgc addServer http://127.0.0.1:443 测试服 本地测试服务器
|
||||
# 成功返回 "服务器已添加,ID为[1],使用servers子命令查看服务器列表"
|
||||
# 失败返回 "只能设置装有 [OpenCommand](https://github.com/jie65535/gc-opencommand-plugin) 插件的服务器"
|
||||
# 失败的原因可能包含无法链接服务器、服务器没有安装插件等,具体可以参考日志
|
||||
|
||||
# 如果你是服主,你可以添加OpenCommand的ConsoleToken
|
||||
/jgc setServerConsoleToken <id> <consoleToken>
|
||||
# 添加完成后,你可以将自己设置为管理员
|
||||
/jgc op <user>
|
||||
# 例如设置123456为管理员
|
||||
/jgc op 123456
|
||||
# 取消管理的子命令是 deop
|
||||
|
||||
# 管理员执行命令将默认使用控制台Token,无需验证(见后文)
|
||||
```
|
||||
|
||||
## 将服务器绑定到机器人所在群聊
|
||||
```shell
|
||||
# 用法
|
||||
/jgc linkGroup/bindGroup/addGroup <serverId> [group] # 绑定服务器到群,在群内执行可忽略群参数
|
||||
# 示例(控制台)
|
||||
/jgc linkGroup 1 群号
|
||||
# 示例(群里)
|
||||
/jgc linkGroup 1
|
||||
# 成功返回 "OK"
|
||||
```
|
||||
在聊天环境执行 Mirai-Console 命令需要另一个插件 [Chat Command](https://github.com/project-mirai/chat-command)
|
||||
执行GC命令不需要这个,见后文
|
||||
|
||||
## 绑定账号
|
||||
玩家想要在群里执行命令,需要绑定自己的游戏UID,需要在群里发送 `绑定 <uid>` 来向目标账号发送验证码,然后将验证码发到群里完成验证,如图所示
|
||||
|
||||

|
||||
|
||||
_管理员无需绑定,默认使用控制台令牌执行命令_
|
||||
|
||||
绑定命令的触发关键字可以通过 `/jgc setBindCommand <prefix>` 来修改
|
||||
|
||||
## 执行命令
|
||||
默认执行GC命令前缀为 `!` ,用法是 `!<命令|别名>`
|
||||
|
||||
_可以通过 `/jgc setBindCommand <prefix>` 来修改执行命令前缀 _(例如以下示例图中使用`run`作为前缀)__
|
||||
|
||||
---
|
||||
|
||||
执行命令示例:`!prop unlockmap 1`
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
执行别名示例:`!解锁地图`
|
||||
|
||||

|
||||
|
||||
命令别名可以通过 `/jgc setCommand <alias> <command>` 来设置,通过 `/jgc removeCommand <alias>` 来删除
|
||||
|
||||
---
|
||||
|
||||
命令还可以通过 @群员 来替代原先命令中的 @UID,前提是这个群员绑定了它的UID
|
||||
|
||||
例如:`!permission list @张三`,其中`@张三`会被替换成其绑定的UID
|
||||
|
||||

|
||||
|
||||
## 拉黑用户
|
||||
如果你想禁止某个用户使用本插件执行命令,可以使用 `/ban <qq>` 来拉黑,使用 `/unban <qq>` 可以解除黑名单
|
||||
_(只是一个凭想象增加的功能,也许能用上呢)_
|
||||
|
||||
## 群消息同步
|
||||
|
||||
当你设置了服务器的 consoleToken,那么你可以使用 `/jgc setServerSyncMessage <id> <sync>` 命令来开启群消息同步功能。
|
||||
|
||||
示例:`/jgc setServerSyncMessage 1 true` 表示启用1号服务器的消息同步功能
|
||||
|
||||
当群里收到消息时,会执行命令 `/say 用户名 消息内容` 来将消息发送到服务器,因此玩家收到的消息也是来自服务器发送的。
|
||||
|
||||
注意,为了避免徒劳的尝试,当任何一次消息发送失败时,都会自动关闭同步消息功能,需要手动重新启用。
|
||||
你可以自己更改相关规则,只需要修改源代码。
|
||||
|
||||

|
||||
|
||||
**注意,玩家在游戏内发送的消息是不会同步到群的,这只是单向同步!!**
|
||||
|
||||
# 配置文件(config.yml)
|
||||
```yml
|
||||
# 管理员列表,仅管理员可以执行控制台命令
|
||||
administrators:
|
||||
- 123456
|
||||
# 用户黑名单
|
||||
blacklist: []
|
||||
# 绑定命令:绑定 <UID> 示例:绑定 10001
|
||||
bindCommand: 绑定
|
||||
# 聊天中执行GC命令前缀:!<命令|别名>
|
||||
# 示例1:!give 1096 lv90
|
||||
# 示例2:!位置
|
||||
commandPrefix: !
|
||||
# 命令别名
|
||||
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'
|
||||
```
|
||||
|
||||
# 指令列表
|
||||
## 管理相关
|
||||
```shell
|
||||
/jgc help # 插件命令用法
|
||||
/jgc reload # 重载插件配置
|
||||
/jgc setCommandPrefix <prefix> # 设置执行GC命令前缀
|
||||
/jgc setBindCommand <prefix> # 设置绑定命令前缀
|
||||
/jgc op <user> # 设置管理员
|
||||
/jgc setAdmin <user> # 设置管理员
|
||||
/jgc deop <user> # 解除管理员
|
||||
/jgc removeAdmin <user> # 解除管理员
|
||||
/jgc ban <user> # 禁止指定QQ使用插件
|
||||
/jgc unban <user> # 解除禁止指定QQ使用插件
|
||||
```
|
||||
|
||||
## 服务器相关
|
||||
```shell
|
||||
/jgc ping <address|id> # 测试指定服务器是否安装插件
|
||||
/jgc addServer <address> [name] [description] # 添加服务器
|
||||
/jgc servers # 列出服务器
|
||||
/jgc setServerIsEnabled <id> <isEnabled> # 设置服务器是否启用
|
||||
/jgc setServerAddress <id> <address> # 修改服务器地址
|
||||
/jgc setServerInfo <id> <name> <description> # 设置服务器信息
|
||||
/jgc setServerConsoleToken <id> <consoleToken> # 设置服务器控制台令牌
|
||||
/jgc setServerSyncMessage <id> <sync> # 设置是否同步群消息到服务器
|
||||
```
|
||||
|
||||
## 群相关
|
||||
```shell
|
||||
/jgc linkGroup/bindGroup/addGroup <serverId> [group] # 绑定服务器到群,在群内执行可忽略群参数
|
||||
/jgc enable [group] # 启用指定群执行,在群内执行可忽略群参数
|
||||
/jgc disable [group] # 禁用指定群执行,在群内执行可忽略群参数
|
||||
```
|
||||
|
||||
## 命令别名相关
|
||||
```shell
|
||||
/jgc setCommand <alias> <command> # 添加命令别名
|
||||
/jgc removeCommand <alias> # 删除命令别名
|
||||
```
|
||||
|
||||
# 实体结构
|
||||
|
||||
Server
|
||||
```kotlin
|
||||
@Serializable
|
||||
data class Server(
|
||||
/**
|
||||
* 服务器ID
|
||||
* 自动递增
|
||||
*/
|
||||
val id: Int,
|
||||
|
||||
/**
|
||||
* 服务器地址
|
||||
*/
|
||||
var address: String,
|
||||
|
||||
/**
|
||||
* 服务器名称
|
||||
*/
|
||||
var name: String = "",
|
||||
|
||||
/**
|
||||
* 服务器说明
|
||||
*/
|
||||
var description: String = "",
|
||||
|
||||
/**
|
||||
* 控制台令牌
|
||||
*/
|
||||
var consoleToken: String = "",
|
||||
|
||||
/**
|
||||
* 服务器是否已启用
|
||||
*/
|
||||
var isEnabled: Boolean = true,
|
||||
|
||||
/**
|
||||
* 同步群消息到服务器,必须设置了控制台令牌
|
||||
*/
|
||||
var syncMessage: Boolean = false,
|
||||
)
|
||||
```
|
||||
---
|
||||
GroupConfig
|
||||
```kotlin
|
||||
@Serializable
|
||||
data class GroupConfig(
|
||||
/**
|
||||
* 群ID(QQ群号)
|
||||
*/
|
||||
val id: Long,
|
||||
|
||||
/**
|
||||
* 服务器ID
|
||||
*/
|
||||
var serverId: Int,
|
||||
|
||||
/**
|
||||
* 是否启用(用于临时关闭)
|
||||
*/
|
||||
var isEnabled: Boolean = true,
|
||||
)
|
||||
```
|
||||
---
|
||||
User
|
||||
```kotlin
|
||||
@Serializable
|
||||
data class User(
|
||||
/**
|
||||
* 用户ID(QQ帐号)
|
||||
*/
|
||||
val id: Long,
|
||||
|
||||
/**
|
||||
* 服务器ID
|
||||
*/
|
||||
val serverId: Int,
|
||||
|
||||
/**
|
||||
* 游戏UID
|
||||
*/
|
||||
var uid: Int,
|
||||
) {
|
||||
/**
|
||||
* 令牌,失效时清空
|
||||
*/
|
||||
var token: String = ""
|
||||
|
||||
/**
|
||||
* 用户添加时间
|
||||
*/
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val createTime: LocalDateTime = LocalDateTime.now()
|
||||
|
||||
/**
|
||||
* 运行命令计数
|
||||
*/
|
||||
var runCount: Int = 0
|
||||
|
||||
/**
|
||||
* 最后运行时间
|
||||
*/
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
var lastRunTime: LocalDateTime? = null
|
||||
}
|
||||
```
|
||||
|
@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
val kotlinVersion = "1.7.20"
|
||||
val kotlinVersion = "1.7.10"
|
||||
kotlin("jvm") version kotlinVersion
|
||||
kotlin("plugin.serialization") version kotlinVersion
|
||||
|
||||
|
@ -1,8 +1,40 @@
|
||||
/*
|
||||
* 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.utils.info
|
||||
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(
|
||||
@ -15,6 +47,218 @@ object JGrasscutterCommand : KotlinPlugin(
|
||||
}
|
||||
) {
|
||||
override fun onEnable() {
|
||||
logger.info { "Plugin loaded" }
|
||||
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!!)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,330 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
object PluginCommands {
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.contact.*
|
||||
import top.jie65535.mirai.JGrasscutterCommand.reload
|
||||
import top.jie65535.mirai.model.GroupConfig
|
||||
import top.jie65535.mirai.model.Server
|
||||
import top.jie65535.mirai.opencommand.OpenCommandApi
|
||||
|
||||
object PluginCommands : CompositeCommand(
|
||||
JGrasscutterCommand, "jgc",
|
||||
description = "管理插件设置"
|
||||
) {
|
||||
private val logger = JGrasscutterCommand.logger
|
||||
|
||||
|
||||
@SubCommand
|
||||
@Description("插件命令用法")
|
||||
suspend fun CommandSender.help() {
|
||||
sendMessage(usage)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("重载插件配置")
|
||||
suspend fun CommandSender.reload() {
|
||||
logger.info("重载插件配置")
|
||||
PluginConfig.reload()
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("设置执行GC命令前缀")
|
||||
suspend fun CommandSender.setCommandPrefix(prefix: String) {
|
||||
if (prefix.isEmpty()) {
|
||||
sendMessage("前缀不能为空,这会导致每条消息都作为命令处理")
|
||||
} else {
|
||||
logger.info("设置执行GC命令前缀为 $prefix")
|
||||
PluginConfig.commandPrefix = prefix
|
||||
sendMessage("OK")
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("设置绑定命令前缀")
|
||||
suspend fun CommandSender.setBindCommand(prefix: String) {
|
||||
if (prefix.isEmpty()) {
|
||||
sendMessage("不能为空")
|
||||
} else {
|
||||
logger.info("设置绑定命令为 $prefix")
|
||||
PluginConfig.bindCommand = prefix
|
||||
sendMessage("OK")
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand("setAdmin", "op")
|
||||
@Description("设置管理员")
|
||||
suspend fun CommandSender.setAdmin(user: User) {
|
||||
logger.info("添加管理员 ${user.nameCardOrNick}(${user.id})")
|
||||
PluginConfig.administrators.add(user.id)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@SubCommand("removeAdmin", "deop")
|
||||
@Description("解除管理员")
|
||||
suspend fun CommandSender.removeAdmin(user: User) {
|
||||
PluginConfig.administrators.remove(user.id)
|
||||
logger.info("解除管理员 ${user.nameCardOrNick}(${user.id})")
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("禁止指定QQ使用插件")
|
||||
suspend fun CommandSender.ban(qq: Long) {
|
||||
logger.info("禁止${qq}使用插件")
|
||||
PluginConfig.blacklist.add(qq)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("解除禁止指定QQ使用插件")
|
||||
suspend fun CommandSender.unban(qq: Long) {
|
||||
logger.info("解除禁止${qq}使用插件")
|
||||
PluginConfig.blacklist.remove(qq)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
|
||||
// region 服务器相关命令
|
||||
|
||||
@SubCommand
|
||||
@Description("测试指定服务器是否安装插件")
|
||||
suspend fun CommandSender.ping(address: String) {
|
||||
val id = address.toIntOrNull()
|
||||
val serverAddress = if (id != null) {
|
||||
val server = PluginData.servers.find { it.id == id }
|
||||
if (server == null) {
|
||||
sendMessage("未找到指定服务器")
|
||||
return
|
||||
} else {
|
||||
server.address
|
||||
}
|
||||
} else {
|
||||
address
|
||||
}
|
||||
if (tryPing(serverAddress)) {
|
||||
sendMessage("OK")
|
||||
} else {
|
||||
sendMessage("Error")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryPing(address: String): Boolean {
|
||||
return try {
|
||||
logger.info("正在 ping $address")
|
||||
OpenCommandApi.ping(address)
|
||||
true
|
||||
} catch (e: Throwable) {
|
||||
logger.warning("ping $address 异常", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("添加服务器")
|
||||
suspend fun CommandSender.addServer(address: String, name: String = "", vararg description: String = arrayOf()) {
|
||||
if (address.isEmpty()) {
|
||||
sendMessage("服务器地址不能为空!")
|
||||
return
|
||||
}
|
||||
val descriptionStr = description.joinToString(" ")
|
||||
|
||||
if (tryPing(address)) {
|
||||
logger.info("添加服务器:$address\tname=$name\tdescription=$descriptionStr")
|
||||
val serverId = ++PluginData.lastServerId
|
||||
PluginData.servers.add(Server(serverId, address, name, descriptionStr))
|
||||
sendMessage("服务器已添加,ID为[$serverId],使用servers子命令查看服务器列表")
|
||||
} else {
|
||||
sendMessage("只能设置装有 [OpenCommand](https://github.com/jie65535/gc-opencommand-plugin) 插件的服务器")
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("列出服务器")
|
||||
suspend fun CommandSender.servers() {
|
||||
if (PluginData.servers.isEmpty()) {
|
||||
sendMessage("服务器列表为空,使用addServer子命令来添加服务器")
|
||||
} else {
|
||||
sendMessage(PluginData.servers.joinToString("\n") {
|
||||
if (it.description.isNotEmpty())
|
||||
"[${it.id}] ${it.name} ${it.address}\n${it.description}"
|
||||
else
|
||||
"[${it.id}] ${it.name} ${it.address}"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("设置服务器启用")
|
||||
suspend fun CommandSender.setServerIsEnabled(id: Int, isEnabled: Boolean) {
|
||||
val server = PluginData.servers.find { it.id == id }
|
||||
if (server == null) {
|
||||
sendMessage("未找到指定服务器")
|
||||
} else {
|
||||
logger.info("${if (isEnabled) "启用" else "禁用"}服务器[$id]")
|
||||
server.isEnabled = isEnabled
|
||||
sendMessage("OK")
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("修改服务器地址")
|
||||
suspend fun CommandSender.setServerAddress(id: Int, address: String) {
|
||||
val server = PluginData.servers.find { it.id == id }
|
||||
if (server == null) {
|
||||
sendMessage("未找到指定服务器")
|
||||
} else {
|
||||
if (tryPing(address)) {
|
||||
logger.info("修改服务器地址为:$address")
|
||||
server.address = address
|
||||
sendMessage("OK")
|
||||
} else {
|
||||
sendMessage("只能设置装有 [OpenCommand](https://github.com/jie65535/gc-opencommand-plugin) 插件的服务器")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("设置服务器信息")
|
||||
suspend fun CommandSender.setServerInfo(id: Int, name: String, vararg description: String) {
|
||||
val server = PluginData.servers.find { it.id == id }
|
||||
if (server == null) {
|
||||
sendMessage("未找到指定服务器")
|
||||
} else {
|
||||
val descriptionStr = description.joinToString(" ")
|
||||
logger.info("设置服务器信息为:name=$name\tdescription=$descriptionStr")
|
||||
server.name = name
|
||||
server.description = descriptionStr
|
||||
sendMessage("OK")
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("设置服务器控制台令牌")
|
||||
suspend fun CommandSender.setServerConsoleToken(id: Int, consoleToken: String) {
|
||||
val server = PluginData.servers.find { it.id == id }
|
||||
if (server == null) {
|
||||
sendMessage("未找到指定服务器")
|
||||
} else {
|
||||
logger.info("设置服务器控制台令牌为:$consoleToken")
|
||||
server.consoleToken = consoleToken
|
||||
sendMessage("OK")
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("设置是否同步群消息到服务器")
|
||||
suspend fun CommandSender.setServerSyncMessage(id: Int, sync: Boolean) {
|
||||
val server = PluginData.servers.find { it.id == id }
|
||||
if (server == null) {
|
||||
sendMessage("未找到指定服务器")
|
||||
} else {
|
||||
logger.info("服务器[$id]${if (sync) "启用" else "禁用"}消息同步")
|
||||
server.syncMessage = sync
|
||||
sendMessage("OK")
|
||||
}
|
||||
}
|
||||
|
||||
// endregion 服务器相关命令
|
||||
|
||||
// region 群相关命令
|
||||
|
||||
@SubCommand("linkGroup", "bindGroup", "addGroup")
|
||||
@Description("绑定服务器到群")
|
||||
suspend fun CommandSender.linkGroup(serverId: Int, group: Group? = getGroupOrNull()) {
|
||||
if (group == null) {
|
||||
sendMessage("必须指定群")
|
||||
return
|
||||
}
|
||||
|
||||
val server = PluginData.servers.find { it.id == serverId }
|
||||
if (server == null) {
|
||||
sendMessage("指定服务器ID不存在,请先添加服务器(使用addServer子命令)")
|
||||
return
|
||||
}
|
||||
|
||||
logger.info("将服务器[$serverId] ${server.name} ${server.address} 绑定到群 ${group.name}(${group.id})")
|
||||
val g = PluginData.groups.find { it.id == group.id }
|
||||
if (g == null) {
|
||||
PluginData.groups.add(GroupConfig(group.id, serverId))
|
||||
sendMessage("OK,默认已启用")
|
||||
} else {
|
||||
g.serverId = serverId
|
||||
sendMessage("OK")
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@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子命令)")
|
||||
} else {
|
||||
logger.info("启用插件在群 ${group.name}(${group.id})")
|
||||
g.isEnabled = true
|
||||
sendMessage("OK")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("禁用指定群执行")
|
||||
suspend fun CommandSender.disable(group: Group? = getGroupOrNull()) {
|
||||
if (group == null) {
|
||||
sendMessage("必须指定群")
|
||||
} else {
|
||||
val g = PluginData.groups.find { it.id == group.id }
|
||||
if (g == null) {
|
||||
sendMessage("请先绑定群到服务器(使用linkGroup子命令)")
|
||||
} else {
|
||||
logger.info("禁用插件在群 ${group.name}(${group.id})")
|
||||
g.isEnabled = false
|
||||
sendMessage("OK")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion 群相关命令
|
||||
|
||||
// region 命令别名部分
|
||||
|
||||
@SubCommand
|
||||
@Description("添加命令别名")
|
||||
suspend fun CommandSender.setCommand(alias: String, vararg command: String) {
|
||||
if (alias.isEmpty() || command.isEmpty() || command[0].isEmpty()) {
|
||||
sendMessage("参数不能为空")
|
||||
}
|
||||
PluginConfig.commandAlias[alias] = command.joinToString(" ")
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
@Description("删除命令别名")
|
||||
suspend fun CommandSender.removeCommand(alias: String) {
|
||||
PluginConfig.commandAlias.remove(alias)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
@ -1,4 +1,52 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
object PluginConfig {
|
||||
import net.mamoe.mirai.console.data.*
|
||||
|
||||
object PluginConfig : AutoSavePluginConfig("config") {
|
||||
@ValueDescription("管理员列表,仅管理员可以执行控制台命令")
|
||||
val administrators: MutableSet<Long> by value()
|
||||
|
||||
@ValueDescription("用户黑名单")
|
||||
val blacklist: MutableSet<Long> by value()
|
||||
|
||||
@ValueDescription("绑定命令:绑定 <UID> 示例:绑定 10001")
|
||||
var bindCommand: String by value("绑定")
|
||||
|
||||
@ValueDescription("聊天中执行GC命令前缀:!<命令|别名>\n" +
|
||||
"示例1:!give 1096 lv90\n" +
|
||||
"示例2:!位置\n")
|
||||
var commandPrefix: String by value("!")
|
||||
|
||||
@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",
|
||||
|
||||
// TODO ...
|
||||
))
|
||||
}
|
@ -1,4 +1,40 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
object PluginData {
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginData
|
||||
import net.mamoe.mirai.console.data.ValueDescription
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import top.jie65535.mirai.model.GroupConfig
|
||||
import top.jie65535.mirai.model.Server
|
||||
import top.jie65535.mirai.model.User
|
||||
|
||||
object PluginData : AutoSavePluginData("data") {
|
||||
|
||||
@ValueDescription("服务器列表")
|
||||
val servers: MutableList<Server> by value()
|
||||
|
||||
@ValueDescription("最后服务器ID,用于递增")
|
||||
var lastServerId: Int by value()
|
||||
|
||||
@ValueDescription("玩家列表")
|
||||
val users: MutableList<User> by value()
|
||||
|
||||
@ValueDescription("群列表")
|
||||
val groups: MutableList<GroupConfig> by value()
|
||||
}
|
@ -1,3 +1,20 @@
|
||||
/*
|
||||
* 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.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -6,7 +23,7 @@ import kotlinx.serialization.Serializable
|
||||
* 群类型
|
||||
*/
|
||||
@Serializable
|
||||
data class Group(
|
||||
data class GroupConfig(
|
||||
/**
|
||||
* 群ID(QQ群号)
|
||||
*/
|
||||
@ -20,5 +37,5 @@ data class Group(
|
||||
/**
|
||||
* 是否启用(用于临时关闭)
|
||||
*/
|
||||
var enabled: Boolean = true,
|
||||
var isEnabled: Boolean = true,
|
||||
)
|
@ -1,10 +1,62 @@
|
||||
package top.jie65535.mirai
|
||||
/*
|
||||
* 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.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 服务器类型
|
||||
*/
|
||||
@Serializable
|
||||
data class ServerConfig(
|
||||
data class Server(
|
||||
/**
|
||||
* 服务器ID
|
||||
* 自动递增
|
||||
*/
|
||||
val id: Int,
|
||||
|
||||
/**
|
||||
* 服务器地址
|
||||
*/
|
||||
var address: String,
|
||||
var consoleToken: String,
|
||||
|
||||
/**
|
||||
* 服务器名称
|
||||
*/
|
||||
var name: String = "",
|
||||
|
||||
/**
|
||||
* 服务器说明
|
||||
*/
|
||||
var description: String = "",
|
||||
|
||||
/**
|
||||
* 控制台令牌
|
||||
*/
|
||||
var consoleToken: String = "",
|
||||
|
||||
/**
|
||||
* 服务器是否已启用
|
||||
*/
|
||||
var isEnabled: Boolean = true,
|
||||
|
||||
/**
|
||||
* 同步群消息到服务器,必须设置了控制台令牌
|
||||
*/
|
||||
var syncMessage: Boolean = false,
|
||||
)
|
@ -1,4 +1,62 @@
|
||||
/*
|
||||
* 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.model
|
||||
|
||||
class User {
|
||||
import kotlinx.serialization.Serializable
|
||||
import top.jie65535.mirai.serializers.LocalDateTimeSerializer
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Serializable
|
||||
data class User(
|
||||
/**
|
||||
* 用户ID(QQ帐号)
|
||||
*/
|
||||
val id: Long,
|
||||
|
||||
/**
|
||||
* 服务器ID
|
||||
*/
|
||||
val serverId: Int,
|
||||
|
||||
/**
|
||||
* 游戏UID
|
||||
*/
|
||||
var uid: Int,
|
||||
) {
|
||||
/**
|
||||
* 令牌,失效时清空
|
||||
*/
|
||||
var token: String = ""
|
||||
|
||||
/**
|
||||
* 用户添加时间
|
||||
*/
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val createTime: LocalDateTime = LocalDateTime.now()
|
||||
|
||||
/**
|
||||
* 运行命令计数
|
||||
*/
|
||||
var runCount: Int = 0
|
||||
|
||||
/**
|
||||
* 最后运行时间
|
||||
*/
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
var lastRunTime: LocalDateTime? = null
|
||||
}
|
@ -1,4 +1,152 @@
|
||||
/*
|
||||
* 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.opencommand
|
||||
|
||||
object OCApi {
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
object OpenCommandApi {
|
||||
private val httpClient = OkHttpClient.Builder().build()
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private data class PingRequest(
|
||||
val action: String = "ping"
|
||||
)
|
||||
@Serializable
|
||||
private data class SendCodeRequest(
|
||||
@SerialName("data")
|
||||
val uid: Int,
|
||||
val action: String = "sendCode"
|
||||
)
|
||||
@Serializable
|
||||
private data class VerifyRequest(
|
||||
val token: String,
|
||||
@SerialName("data")
|
||||
val code: Int,
|
||||
val action: String = "verify"
|
||||
)
|
||||
@Serializable
|
||||
private data class CommandRequest(
|
||||
val token: String,
|
||||
@SerialName("data")
|
||||
val command: String,
|
||||
val action: String = "command",
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
private data class StringResponse(
|
||||
val retcode: Int,
|
||||
val message: String,
|
||||
val data: String?
|
||||
)
|
||||
|
||||
class HttpException(val code: Int, message: String) : Exception(message)
|
||||
class InvokeException(val code: Int, message: String) : Exception(message)
|
||||
|
||||
private val JSON = "application/json; charset=utf-8".toMediaType()
|
||||
|
||||
/**
|
||||
* 向服务器发起opencommand请求
|
||||
* @param host 服务器地址
|
||||
* @param jsonBody 请求正文(Json)
|
||||
* @return 如果一切正常,返回相应数据,可能为空
|
||||
* @exception InvokeException HTTP请求执行成功,但opencommand插件调用失败
|
||||
* @exception HttpException HTTP请求完成,但HTTP响应错误代码(例如404、500等)
|
||||
* @exception IOException 如果由于取消、连接问题或超时而无法执行请求。由于网络可能在交换期间发生故障,因此远程服务器可能在故障之前接受了请求
|
||||
*/
|
||||
private suspend fun doRequest(host: String, jsonBody: String): String? {
|
||||
val api = "$host/opencommand/api"
|
||||
val request = Request.Builder()
|
||||
.url(api)
|
||||
.post(jsonBody.toRequestBody(JSON))
|
||||
.build()
|
||||
// JGrasscutterCommand.logger.debug("POST to $api Body $jsonBody")
|
||||
return withContext(Dispatchers.IO) {
|
||||
httpClient.newCall(request).execute().use { httpResponse ->
|
||||
val responseBody = httpResponse.body
|
||||
if (httpResponse.code == 200 && responseBody != null) {
|
||||
val response = json.decodeFromStream<StringResponse>(responseBody.byteStream())
|
||||
if (response.retcode != 200) {
|
||||
throw InvokeException(response.retcode, response.data ?: response.message)
|
||||
} else {
|
||||
return@use response.data
|
||||
}
|
||||
} else {
|
||||
throw HttpException(httpResponse.code, httpResponse.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试链接
|
||||
* @param host 服务器地址
|
||||
*/
|
||||
suspend fun ping(host: String) {
|
||||
doRequest(host, json.encodeToString(PingRequest()))
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* @param host 服务器地址
|
||||
* @param uid 目标玩家UID
|
||||
* @return 返回临时Token用于验证
|
||||
*/
|
||||
suspend fun sendCode(host: String, uid: Int): String {
|
||||
return doRequest(host, json.encodeToString(SendCodeRequest(uid)))!!
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证身份,验证失败时将抛出异常,异常详情参考doRequest描述
|
||||
* @param host 服务器地址
|
||||
* @param token 发送验证码时返回的临时令牌
|
||||
* @param code 用户输入的验证代码
|
||||
* @see doRequest
|
||||
*/
|
||||
suspend fun verify(host: String, token: String, code: Int) {
|
||||
doRequest(host, json.encodeToString(VerifyRequest(token, code)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行命令,成功时返回命令执行结果,失败时抛出异常,异常详情参考doRequest描述
|
||||
* @param host 服务器地址
|
||||
* @param token 持久令牌
|
||||
* @param command 命令行
|
||||
* @return 命令执行结果
|
||||
* @see doRequest
|
||||
*/
|
||||
suspend fun runCommand(host: String, token: String, command: String): String? {
|
||||
return doRequest(host, json.encodeToString(CommandRequest(token, command)))
|
||||
}
|
||||
}
|
@ -1,4 +1,38 @@
|
||||
/*
|
||||
* 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.serializers
|
||||
|
||||
class LocalDateTimeSerializer {
|
||||
}
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.descriptors.*
|
||||
import kotlinx.serialization.encoding.*
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Serializer(LocalDateTime::class)
|
||||
object LocalDateTimeSerializer: KSerializer<LocalDateTime> {
|
||||
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime =
|
||||
LocalDateTime.parse(decoder.decodeString())
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user