feat: 初步使用版本,目前系统很粗糙,未来考虑会向外扩展
This commit is contained in:
parent
bf6c5b9a46
commit
aa65f3fea8
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -41,3 +41,5 @@ bin/
|
|||
### Mac OS ###
|
||||
.DS_Store
|
||||
/logs/
|
||||
/config/
|
||||
/rcon_playerlist_state.json
|
||||
|
|
|
|||
17
.idea/dataSources.xml
Normal file
17
.idea/dataSources.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="@47.116.125.76" uuid="5b1b9d12-d8be-43ba-a647-9d6e467bf201">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://47.116.125.76:3308</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/sqldialects.xml
Normal file
7
.idea/sqldialects.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/src/main/kotlin/top/r3944realms/ltdmanager/module/GroupRequestHandlerModule.kt" dialect="GenericSQL" />
|
||||
<file url="PROJECT" dialect="MySQL" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1 +1,7 @@
|
|||
# 基于NapCat Websocket Server 框架开发
|
||||
# 基于NapCat Http/Websocket Server API开发
|
||||
## 目标1:实现白名单申请通过后加群自动通过
|
||||
### 拆分目标:
|
||||
轮询
|
||||
1. 获取指定加群请求
|
||||
2. 对比数据库中的以通过QQ号
|
||||
3. 批量通过/拒绝请求
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.9.23"
|
||||
kotlin("plugin.serialization") version "1.9.23" // 添加序列化插件
|
||||
application
|
||||
id("com.github.johnrengelman.shadow") version "8.0.0" // fat jar
|
||||
}
|
||||
|
||||
group = "top.r3944realms.ltdmanager"
|
||||
version = "1.0-SNAPSHOT"
|
||||
group = project.property("project_group") as String
|
||||
version = project.property("project_version") as String
|
||||
|
||||
repositories {
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenLocal()
|
||||
maven {
|
||||
url = uri("https://maven.aliyun.com/repository/public/")
|
||||
|
|
@ -42,7 +46,8 @@ repositories {
|
|||
implementation("org.apache.logging.log4j:log4j-api:2.20.0")
|
||||
|
||||
// 配置管理
|
||||
implementation("org.yaml:snakeyaml:2.2")
|
||||
implementation("org.yaml:snakeyaml:2.4")
|
||||
implementation("org.snakeyaml:snakeyaml-engine:2.10")
|
||||
implementation("com.typesafe:config:1.4.2") // 类型安全的配置库
|
||||
|
||||
// 协程
|
||||
|
|
@ -60,6 +65,21 @@ repositories {
|
|||
jvmToolchain(17)
|
||||
}
|
||||
application {
|
||||
mainClass.set("top.r3944realms.ltdmanager.main") // 设置主类
|
||||
mainClass.set("top.r3944realms.ltdmanager.MainKt") // 设置主类
|
||||
}
|
||||
}
|
||||
tasks {
|
||||
// ShadowJar 配置
|
||||
named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
|
||||
archiveClassifier.set("") // 去掉 -all 后缀
|
||||
mergeServiceFiles()
|
||||
manifest {
|
||||
attributes["Main-Class"] = "top.r3944realms.ltdmanager.MainKt"
|
||||
}
|
||||
}
|
||||
|
||||
// build 依赖 shadowJar
|
||||
build {
|
||||
dependsOn("shadowJar")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,3 @@
|
|||
# NapCat
|
||||
将回应抽象为event模型
|
||||
将请求抽象为request模型
|
||||
|
||||
优先级发送流程
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant PriorityQueue
|
||||
participant PendingResponses
|
||||
participant Server
|
||||
|
||||
Client->>PriorityQueue: sendRequest(高优先级)
|
||||
Client->>PriorityQueue: sendRequest(低优先级)
|
||||
PriorityQueue->>Server: 先发送高优先级请求
|
||||
Server->>PendingResponses: 返回响应1
|
||||
PendingResponses->>Client: 解除高优先级请求的await
|
||||
PriorityQueue->>Server: 发送低优先级请求
|
||||
Server->>PendingResponses: 返回响应2
|
||||
PendingResponses->>Client: 解除低优先级请求的await
|
||||
将请求抽象为request模型
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
kotlin.code.style=official
|
||||
org.gradle.downloadSources=false
|
||||
org.gradle.parallel=true
|
||||
org.gradle.degree_of_parallelism=16
|
||||
org.gradle.degree_of_parallelism=16
|
||||
project_group=top.r3944realms.ltdmanager
|
||||
project_version=1.2-SNAPSHOT
|
||||
|
|
|
|||
55
src/main/kotlin/top/r3944realms/ltdmanager/GlobalManager.kt
Normal file
55
src/main/kotlin/top/r3944realms/ltdmanager/GlobalManager.kt
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package top.r3944realms.ltdmanager
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import top.r3944realms.ltdmanager.core.mysql.MysqlHikariConnectPool
|
||||
import top.r3944realms.ltdmanager.module.ModuleManager
|
||||
import top.r3944realms.ltdmanager.napcat.NapCatClient
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
import java.sql.Connection
|
||||
|
||||
object GlobalManager {
|
||||
// 单例作用域,可在模块中使用协程
|
||||
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
// Hikari 数据源
|
||||
private val dataSource: MysqlHikariConnectPool by lazy {
|
||||
MysqlHikariConnectPool()
|
||||
}
|
||||
|
||||
// NapCat 客户端
|
||||
val napCatClient: NapCatClient by lazy {
|
||||
NapCatClient.create()
|
||||
}
|
||||
|
||||
val moduleManager: ModuleManager by lazy { ModuleManager() }
|
||||
|
||||
/**
|
||||
* 获取数据库连接
|
||||
* 使用 try-with-resources 时会自动关闭
|
||||
*/
|
||||
fun getConnection(): Connection {
|
||||
return dataSource.getConnection()
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭全局资源
|
||||
* 例如在应用退出时调用
|
||||
*/
|
||||
fun shutdown() {
|
||||
try {
|
||||
LoggerUtil.logger.info("关闭 NapCatClient")
|
||||
napCatClient.close()
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.warn("关闭 NapCatClient 失败", e)
|
||||
}
|
||||
|
||||
try {
|
||||
LoggerUtil.logger.info("关闭 Hikari 数据源")
|
||||
dataSource.close()
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.warn("关闭 Hikari 数据源失败", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package top.r3944realms.ltdmanager.core.config
|
|||
|
||||
import top.r3944realms.ltdmanager.utils.CryptoUtil
|
||||
import top.r3944realms.ltdmanager.utils.YamlUpdater
|
||||
import java.util.*
|
||||
|
||||
data class DatabaseConfig(
|
||||
var url: String? = null,
|
||||
|
|
@ -37,12 +36,8 @@ data class DatabaseConfig(
|
|||
}
|
||||
try {
|
||||
encryptedPassword = "ENC(${CryptoUtil.encrypt(encryptedPassword!!)})"
|
||||
YamlUpdater.updateYamlValue(
|
||||
Objects.requireNonNull(
|
||||
YamlConfigLoader::class.java
|
||||
.classLoader
|
||||
.getResource("application.yaml")
|
||||
).path,
|
||||
YamlUpdater.updateYaml(
|
||||
YamlConfigLoader.configFilePath.toString(),
|
||||
"database.encrypted-password",
|
||||
this.encryptedPassword!!
|
||||
)
|
||||
|
|
@ -54,7 +49,7 @@ data class DatabaseConfig(
|
|||
/**
|
||||
* 检查密码是否已加密
|
||||
*/
|
||||
fun isEncrypted(): Boolean {
|
||||
private fun isEncrypted(): Boolean {
|
||||
return encryptedPassword != null &&
|
||||
encryptedPassword!!.startsWith("ENC(") &&
|
||||
encryptedPassword!!.endsWith(")")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package top.r3944realms.ltdmanager.core.config
|
||||
|
||||
import top.r3944realms.ltdmanager.utils.CryptoUtil
|
||||
import top.r3944realms.ltdmanager.utils.YamlUpdater
|
||||
|
||||
data class HttpConfig(
|
||||
var url: String? = null,
|
||||
var encryptedToken: String? = null
|
||||
) {
|
||||
/**
|
||||
* 获取解密后的token(如果未加密,返回原值)
|
||||
*/
|
||||
val decryptedToken: String?
|
||||
get() {
|
||||
if (encryptedToken == null) {
|
||||
return null
|
||||
}
|
||||
if (!isEncrypted()) {
|
||||
return encryptedToken
|
||||
}
|
||||
try {
|
||||
val cipherText = encryptedToken!!.substring(4, encryptedToken!!.length - 1)
|
||||
return CryptoUtil.decrypt(cipherText)
|
||||
} catch (e: Exception) {
|
||||
throw IllegalStateException("token解密失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密密码(如果未加密),并返回是否成功加密
|
||||
*/
|
||||
fun encryptToken() {
|
||||
if (encryptedToken == null || isEncrypted()) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
encryptedToken = "ENC(${CryptoUtil.encrypt(encryptedToken!!)})"
|
||||
YamlUpdater.updateYaml(
|
||||
YamlConfigLoader.configFilePath.toString(),
|
||||
"http.encrypted-token",
|
||||
this.encryptedToken!!
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
throw IllegalStateException("密码加密失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Token是否已加密
|
||||
*/
|
||||
private fun isEncrypted(): Boolean {
|
||||
return encryptedToken != null &&
|
||||
encryptedToken!!.startsWith("ENC(") &&
|
||||
encryptedToken!!.endsWith(")")
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "HttpConfig(Url=$url, token=***)"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package top.r3944realms.ltdmanager.core.config
|
||||
|
||||
import top.r3944realms.ltdmanager.utils.ApiType
|
||||
import top.r3944realms.ltdmanager.utils.Environment
|
||||
|
||||
data class ModeConfig(
|
||||
var botApiType: ApiType? = null,
|
||||
var environment: Environment? = null,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "ModeConfig(botApiType=$botApiType, environment=$environment)"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package top.r3944realms.ltdmanager.core.config
|
||||
|
||||
import top.r3944realms.ltdmanager.utils.CryptoUtil
|
||||
import top.r3944realms.ltdmanager.utils.YamlUpdater
|
||||
|
||||
data class ToolConfig(
|
||||
var rcon: RconConfig = RconConfig()
|
||||
) {
|
||||
data class RconConfig(
|
||||
var mcRconToolPath: String? = null,
|
||||
var mcRconToolConfigPath: String? = null,
|
||||
var serverUrl: String? = null,
|
||||
var rconPassword: String? = null
|
||||
) {
|
||||
/**
|
||||
* 获取解密后的 rcon 密码(如果未加密,返回原值)
|
||||
*/
|
||||
val decryptedPassword: String?
|
||||
get() {
|
||||
if (rconPassword == null) return null
|
||||
if (!isEncrypted()) return rconPassword
|
||||
return try {
|
||||
val cipherText = rconPassword!!.substring(4, rconPassword!!.length - 1)
|
||||
CryptoUtil.decrypt(cipherText)
|
||||
} catch (e: Exception) {
|
||||
throw IllegalStateException("RCON 密码解密失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密 rcon 密码(如果未加密)
|
||||
*/
|
||||
fun encryptPassword(configFilePath: String) {
|
||||
if (rconPassword == null || isEncrypted()) return
|
||||
try {
|
||||
rconPassword = "ENC(${CryptoUtil.encrypt(rconPassword!!)})"
|
||||
YamlUpdater.updateYaml(
|
||||
configFilePath,
|
||||
"tools.rcon.rcon-password",
|
||||
rconPassword!!
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
throw IllegalStateException("RCON 密码加密失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEncrypted(): Boolean {
|
||||
return rconPassword != null &&
|
||||
rconPassword!!.startsWith("ENC(") &&
|
||||
rconPassword!!.endsWith(")")
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "RconConfig(path=$mcRconToolPath, configPath=$mcRconToolConfigPath, url=$serverUrl, password=***)"
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ToolConfig(rcon=$rcon)"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,59 @@
|
|||
package top.r3944realms.ltdmanager.core.config
|
||||
|
||||
import top.r3944realms.ltdmanager.utils.CryptoUtil
|
||||
import top.r3944realms.ltdmanager.utils.YamlUpdater
|
||||
|
||||
data class WebsocketConfig(
|
||||
var url: String? = null,
|
||||
var token: String? = null
|
||||
var encryptedToken: String? = null
|
||||
) {
|
||||
/**
|
||||
* 获取解密后的token(如果未加密,返回原值)
|
||||
*/
|
||||
val decryptedToken: String?
|
||||
get() {
|
||||
if (encryptedToken == null) {
|
||||
return null
|
||||
}
|
||||
if (!isEncrypted()) {
|
||||
return encryptedToken
|
||||
}
|
||||
try {
|
||||
val cipherText = encryptedToken!!.substring(4, encryptedToken!!.length - 1)
|
||||
return CryptoUtil.decrypt(cipherText)
|
||||
} catch (e: Exception) {
|
||||
throw IllegalStateException("token解密失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密密码(如果未加密),并返回是否成功加密
|
||||
*/
|
||||
fun encryptToken() {
|
||||
if (encryptedToken == null || isEncrypted()) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
encryptedToken = "ENC(${CryptoUtil.encrypt(encryptedToken!!)})"
|
||||
YamlUpdater.updateYaml(
|
||||
YamlConfigLoader.configFilePath.toString(),
|
||||
"websocket.encrypted-token",
|
||||
this.encryptedToken!!
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
throw IllegalStateException("密码加密失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Token是否已加密
|
||||
*/
|
||||
private fun isEncrypted(): Boolean {
|
||||
return encryptedToken != null &&
|
||||
encryptedToken!!.startsWith("ENC(") &&
|
||||
encryptedToken!!.endsWith(")")
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "WebsocketConfig(Url=$url, token=$token)"
|
||||
return "WebsocketConfig(Url=$url, token=***)"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,50 @@ import org.yaml.snakeyaml.Yaml
|
|||
import org.yaml.snakeyaml.constructor.Constructor
|
||||
import org.yaml.snakeyaml.introspector.Property
|
||||
import org.yaml.snakeyaml.introspector.PropertyUtils
|
||||
import top.r3944realms.ltdmanager.utils.ConfigInitializer
|
||||
import top.r3944realms.ltdmanager.utils.NamingConventionUtil
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
|
||||
object YamlConfigLoader {
|
||||
private val config: ConfigWrapper = loadConfig().also {
|
||||
ensureConfigEncrypted(it) // 初始化后立即加密
|
||||
val configFilePath = Paths.get("config/application.yaml") // 配置文件路径
|
||||
private val _config by lazy { loadConfig() } // 延迟初始化
|
||||
val config: ConfigWrapper get() = _config
|
||||
|
||||
init {
|
||||
// 第一次启动确保配置文件存在
|
||||
ConfigInitializer.initConfig("application.yaml", "config")
|
||||
|
||||
// 初始化后加密(确保只执行一次)
|
||||
runCatching {
|
||||
_config.database.encryptPassword()
|
||||
_config.websocket.encryptToken()
|
||||
_config.http.encryptToken()
|
||||
}.onFailure { e ->
|
||||
println("初始化加密失败: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
private fun ensureConfigEncrypted(config: ConfigWrapper?) {
|
||||
config?.database?.encryptPassword()
|
||||
config?.websocket?.encryptToken()
|
||||
config?.http?.encryptToken()
|
||||
}
|
||||
private fun loadConfig(): ConfigWrapper {
|
||||
YamlConfigLoader::class.java.classLoader.getResourceAsStream("application.yaml").use { inputStream ->
|
||||
if (inputStream == null) {
|
||||
throw RuntimeException("配置文件 application.yaml 未找到!")
|
||||
}
|
||||
return Yaml(getConstructor()).load(inputStream)
|
||||
if (!Files.exists(configFilePath)) {
|
||||
throw RuntimeException("配置文件未找到: $configFilePath")
|
||||
}
|
||||
|
||||
try {
|
||||
val yamlContent = Files.readString(configFilePath)
|
||||
|
||||
return Yaml(getConstructor()).load(yamlContent)
|
||||
?: throw RuntimeException("YAML解析返回null")
|
||||
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("YAML解析失败: ${e.message}", e)
|
||||
}
|
||||
|
||||
}
|
||||
private fun getConstructor(): Constructor {
|
||||
val propertyUtils = object : PropertyUtils() {
|
||||
|
|
@ -38,12 +66,20 @@ object YamlConfigLoader {
|
|||
setPropertyUtils(propertyUtils)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadDatabaseConfig(): DatabaseConfig = config.database
|
||||
fun loadCryptoConfig(): CryptoConfig = config.crypto
|
||||
fun loadWebsocketConfig(): WebsocketConfig = config.websocket
|
||||
fun loadHttpConfig(): HttpConfig = config.http
|
||||
fun loadModeConfig(): ModeConfig = config.mode
|
||||
fun loadToolConfig(): ToolConfig = config.tools
|
||||
data class ConfigWrapper(
|
||||
var database :DatabaseConfig,
|
||||
var crypto :CryptoConfig,
|
||||
var websocket :WebsocketConfig
|
||||
var database: DatabaseConfig = DatabaseConfig(),
|
||||
var crypto: CryptoConfig = CryptoConfig(),
|
||||
var mode: ModeConfig = ModeConfig(),
|
||||
var websocket: WebsocketConfig = WebsocketConfig(),
|
||||
var http: HttpConfig = HttpConfig(),
|
||||
var tools: ToolConfig = ToolConfig(),
|
||||
|
||||
)
|
||||
}
|
||||
|
|
@ -1,28 +1,60 @@
|
|||
package top.r3944realms.ltdmanager
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import top.r3944realms.ltdmanager.napcat.events.NapCatEvent
|
||||
import top.r3944realms.ltdmanager.napcat.events.group.SetGroupPortraitEvent
|
||||
import top.r3944realms.ltdmanager.napcat.events.group.SetGroupSearchEvent
|
||||
import top.r3944realms.ltdmanager.napcat.events.personal.CanSendImageEvent
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import top.r3944realms.ltdmanager.module.GroupRequestHandlerModule
|
||||
import top.r3944realms.ltdmanager.module.RconPlayerListModule
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
fun main() {
|
||||
val logger = LoggerFactory.getLogger("log")
|
||||
logger.info("Start")
|
||||
val toJSON = SetOnlineStatusRequest.ONLINE.toJSON()
|
||||
logger.info("S:{}",toJSON)
|
||||
val str: String = """
|
||||
{
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": null,
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
|
||||
fun main() = runBlocking {
|
||||
// 标记程序是否运行
|
||||
val isRunning = AtomicBoolean(true)
|
||||
|
||||
// 创建模块实例
|
||||
val groupModule = GroupRequestHandlerModule(
|
||||
client = GlobalManager.napCatClient,
|
||||
targetGroupId = 538751386
|
||||
)
|
||||
val rconModule = RconPlayerListModule(
|
||||
pollIntervalMillis = 3_000L,
|
||||
timeout = 2_000L,
|
||||
cooldownMillis = 10_000L,
|
||||
targetGroupId = 538751386,
|
||||
selfId = 3327379836,
|
||||
selfNickName = "闲趣老土豆",
|
||||
keywords = setOf(
|
||||
//形容
|
||||
"土豆", "马铃薯", "Potato", "potato", "POTATO",
|
||||
"Potatoes", "potatoes", "POTATOES", "🥔",
|
||||
//正经
|
||||
"列表","服务器状态", "TPS", "tps", "list", "List"
|
||||
)
|
||||
);
|
||||
|
||||
// 注册模块到全局模块管理器
|
||||
GlobalManager.moduleManager.registerModule(groupModule)
|
||||
GlobalManager.moduleManager.registerModule(rconModule)
|
||||
|
||||
// 加载模块
|
||||
GlobalManager.moduleManager.loadModule(groupModule.name)
|
||||
GlobalManager.moduleManager.loadModule(rconModule.name)
|
||||
|
||||
|
||||
// 捕获 JVM 关闭信号,优雅退出
|
||||
Runtime.getRuntime().addShutdownHook(Thread {
|
||||
runBlocking {
|
||||
LoggerUtil.logger.info("\n收到退出信号,正在停止所有模块...")
|
||||
GlobalManager.moduleManager.stopAllModules() // 批量 stop
|
||||
LoggerUtil.logger.info("模块卸载完成,程序退出。")
|
||||
GlobalManager.shutdown()
|
||||
}
|
||||
""".trimIndent()
|
||||
val decodeEvent = NapCatEvent.decodeEvent(str, "group/set_group_search")
|
||||
if (decodeEvent is SetGroupSearchEvent) {
|
||||
logger.info("data:{}",decodeEvent.data)
|
||||
isRunning.set(false)
|
||||
})
|
||||
|
||||
// 持续挂起,保持主线程运行
|
||||
while (isRunning.get()) {
|
||||
delay(1000L)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package top.r3944realms.ltdmanager.module
|
||||
|
||||
import top.r3944realms.ltdmanager.GlobalManager
|
||||
|
||||
/**
|
||||
* 模块抽象基类
|
||||
* 所有功能模块都继承该类
|
||||
*/
|
||||
abstract class BaseModule {
|
||||
|
||||
/**
|
||||
* 模块名称
|
||||
*/
|
||||
abstract val name: String
|
||||
|
||||
/**
|
||||
* 模块是否加载
|
||||
*/
|
||||
@Volatile
|
||||
var loaded: Boolean = false
|
||||
private set
|
||||
|
||||
/**
|
||||
* 模块加载
|
||||
* 可以在这里初始化协程、监听器、定时任务等
|
||||
*/
|
||||
open fun load() {
|
||||
if (!loaded) {
|
||||
loaded = true
|
||||
onLoad()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模块卸载
|
||||
* 清理资源,取消协程、关闭监听器等
|
||||
*/
|
||||
open fun unload() {
|
||||
if (loaded) {
|
||||
loaded = false
|
||||
onUnload()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模块加载时的实际逻辑,由子类实现
|
||||
*/
|
||||
protected abstract fun onLoad()
|
||||
|
||||
/**
|
||||
* 模块卸载时的实际逻辑,由子类实现
|
||||
*/
|
||||
protected abstract fun onUnload()
|
||||
/**
|
||||
* 可选的停止方法,模块内部协程等后台任务在这里被取消
|
||||
*/
|
||||
open suspend fun stop() {
|
||||
unload() // 默认实现直接卸载
|
||||
}
|
||||
/**
|
||||
* 提供访问全局 NapCatClient 的快捷方式
|
||||
*/
|
||||
protected val napCatClient get() = GlobalManager.napCatClient
|
||||
|
||||
/**
|
||||
* 获取数据库连接
|
||||
* 使用 try-with-resources 时会自动关闭
|
||||
*/
|
||||
protected fun getConnection() = GlobalManager.getConnection()
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
package top.r3944realms.ltdmanager.module
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import top.r3944realms.ltdmanager.napcat.NapCatClient
|
||||
import top.r3944realms.ltdmanager.napcat.event.NapCatEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.group.GetGroupIgnoredNotifiesEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.group.GetGroupSystemMsgEvent
|
||||
import top.r3944realms.ltdmanager.napcat.request.group.GetGroupIgnoredNotifiesRequest
|
||||
import top.r3944realms.ltdmanager.napcat.request.group.GetGroupSystemMsgRequest
|
||||
import top.r3944realms.ltdmanager.napcat.request.group.SetGroupAddRequestRequest
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
|
||||
class GroupRequestHandlerModule(
|
||||
private val client: NapCatClient,
|
||||
private val targetGroupId: Long,
|
||||
private val pollIntervalMillis: Long = 30_000L,
|
||||
) : BaseModule() {
|
||||
|
||||
override val name: String = "GroupRequestHandlerModule"
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
private val stopSignal = CompletableDeferred<Unit>()
|
||||
|
||||
override fun onLoad() {
|
||||
LoggerUtil.logger.info("模块[$name]已装载,目标群组: $targetGroupId,轮询间隔: ${pollIntervalMillis}ms")
|
||||
|
||||
// 启动轮询协程
|
||||
scope.launch {
|
||||
LoggerUtil.logger.info("[$name] 轮询协程启动")
|
||||
try {
|
||||
while (isActive) {
|
||||
try {
|
||||
LoggerUtil.logger.debug("[$name] 开始轮询群组请求...")
|
||||
|
||||
// 获取正常请求
|
||||
LoggerUtil.logger.debug("[$name] 获取正常群系统消息...")
|
||||
val systemEvent: GetGroupSystemMsgEvent =
|
||||
client.send(GetGroupSystemMsgRequest())
|
||||
LoggerUtil.logger.debug("[$name] 获取到 ${systemEvent.data.invitedRequest.size} 个邀请请求和 ${systemEvent.data.joinRequests.size} 个加群请求")
|
||||
|
||||
handleEvent(systemEvent)
|
||||
|
||||
// 获取被过滤的请求
|
||||
LoggerUtil.logger.debug("[$name] 获取被过滤的群系统消息...")
|
||||
val ignoredEvent: GetGroupIgnoredNotifiesEvent =
|
||||
client.send(GetGroupIgnoredNotifiesRequest())
|
||||
LoggerUtil.logger.debug("[$name] 获取到 ${ignoredEvent.data.invitedRequest.size} 个被过滤的邀请请求和 ${ignoredEvent.data.joinRequests.size} 个被过滤的加群请求")
|
||||
|
||||
handleEvent(ignoredEvent)
|
||||
|
||||
LoggerUtil.logger.debug("[$name] 本轮轮询完成,等待 ${pollIntervalMillis}ms 后继续")
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.error("[$name] 轮询执行异常", e)
|
||||
}
|
||||
delay(pollIntervalMillis)
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
LoggerUtil.logger.info("[$name] 轮询协程收到取消信号")
|
||||
} finally {
|
||||
LoggerUtil.logger.info("[$name] 轮询协程退出,完成 stopSignal")
|
||||
stopSignal.complete(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun stop() {
|
||||
LoggerUtil.logger.info("[$name] 收到停止命令,开始关闭协程...")
|
||||
scope.cancel()
|
||||
LoggerUtil.logger.info("[$name] 等待协程退出...")
|
||||
stopSignal.await()
|
||||
LoggerUtil.logger.info("[$name] 协程已退出,卸载模块资源")
|
||||
onUnload()
|
||||
}
|
||||
|
||||
public override fun onUnload() {
|
||||
LoggerUtil.logger.info("[$name] 已卸载")
|
||||
}
|
||||
|
||||
private suspend fun handleEvent(event: Any) {
|
||||
LoggerUtil.logger.debug("[$name] 处理群请求事件: ${event.javaClass.simpleName}")
|
||||
|
||||
val provider: GroupRequestProvider? = when (event) {
|
||||
is GetGroupSystemMsgEvent -> {
|
||||
LoggerUtil.logger.debug("[$name] 识别为正常群系统消息事件")
|
||||
event.asProvider()
|
||||
}
|
||||
is GetGroupIgnoredNotifiesEvent -> {
|
||||
LoggerUtil.logger.debug("[$name] 识别为被过滤群系统消息事件")
|
||||
event.asProvider()
|
||||
}
|
||||
else -> {
|
||||
LoggerUtil.logger.warn("[$name] 未知的事件类型: ${event.javaClass}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
provider?.getAllRequests()?.forEach { request ->
|
||||
if (!request.checked) {
|
||||
LoggerUtil.logger.info("[$name] 处理群请求: requestId=${request.requestId}, groupId=${request.groupId}, actor=${request.actor}, type=${request.javaClass}")
|
||||
if (request.groupId == targetGroupId) {
|
||||
LoggerUtil.logger.info("[$name] 请求匹配目标群组 $targetGroupId,查询玩家状态...")
|
||||
val status = queryPlayerStatus(request.invitorUin)
|
||||
LoggerUtil.logger.info("[$name] 玩家 ${request.invitorUin} 状态查询结果: $status")
|
||||
|
||||
when (status) {
|
||||
1 -> {
|
||||
LoggerUtil.logger.info("[$name] 允许加群: groupId=${request.groupId}, invitorUin=${request.invitorUin}, requestId=${request.requestId}")
|
||||
val setGroupAddRequestRequest = SetGroupAddRequestRequest(
|
||||
true,
|
||||
request.requestId.toString()
|
||||
)
|
||||
client.send<NapCatEvent>(setGroupAddRequestRequest)
|
||||
LoggerUtil.logger.info("[$name] 已发送同意加群请求")
|
||||
}
|
||||
|
||||
2, 3 -> {
|
||||
val reason = if (status == 2) "审核未通过" else "待审核"
|
||||
LoggerUtil.logger.info("[$name] 拒绝加群: groupId=${request.groupId}, invitorUin=${request.invitorUin}, status=$status, reason=$reason, requestId=${request.requestId}")
|
||||
val request1 = SetGroupAddRequestRequest(
|
||||
false,
|
||||
request.requestId.toString(),
|
||||
reason
|
||||
)
|
||||
client.send<NapCatEvent>(request1)
|
||||
LoggerUtil.logger.info("[$name] 已发送拒绝加群请求")
|
||||
}
|
||||
|
||||
else -> {
|
||||
LoggerUtil.logger.warn("[$name] 未知玩家状态($status),拒绝请求: invitorUin=${request.invitorUin}, requestId=${request.requestId}")
|
||||
val request1 = SetGroupAddRequestRequest(
|
||||
false,
|
||||
request.requestId.toString(),
|
||||
"未知状态"
|
||||
)
|
||||
client.send<NapCatEvent>(request1)
|
||||
LoggerUtil.logger.info("[$name] 已发送拒绝加群请求(未知状态)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LoggerUtil.logger.debug("[$name] 请求群组 ${request.groupId} 不匹配目标群组 $targetGroupId,跳过处理")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoggerUtil.logger.debug("[$name] 事件处理完成")
|
||||
}
|
||||
|
||||
private fun queryPlayerStatus(actor: Long): Int {
|
||||
LoggerUtil.logger.debug("[$name] 查询玩家状态: qq=$actor")
|
||||
try {
|
||||
getConnection().use { conn ->
|
||||
val stmt = conn.prepareStatement(
|
||||
"SELECT status FROM minecraft_manager_ltd.players WHERE qq=?"
|
||||
)
|
||||
stmt.setLong(1, actor)
|
||||
val rs = stmt.executeQuery()
|
||||
val status = if (rs.next()) rs.getInt("status") else 0
|
||||
LoggerUtil.logger.debug("[$name] 数据库查询结果: qq=$actor, status=$status")
|
||||
return status
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.error("[$name] 查询玩家状态失败: qq=$actor", e)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有群系统请求的统一访问接口
|
||||
*/
|
||||
interface GroupRequestProvider {
|
||||
fun getAllRequests(): List<GetGroupSystemMsgEvent.SystemInfo>
|
||||
}
|
||||
|
||||
/**
|
||||
* 正常请求事件实现
|
||||
*/
|
||||
private fun GetGroupSystemMsgEvent.asProvider(): GroupRequestProvider = object : GroupRequestProvider {
|
||||
override fun getAllRequests(): List<GetGroupSystemMsgEvent.SystemInfo> {
|
||||
return data.invitedRequest + data.joinRequests
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 被过滤请求事件实现
|
||||
*/
|
||||
private fun GetGroupIgnoredNotifiesEvent.asProvider(): GroupRequestProvider = object : GroupRequestProvider {
|
||||
override fun getAllRequests(): List<GetGroupSystemMsgEvent.SystemInfo> {
|
||||
return data.invitedRequest + data.joinRequests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
package top.r3944realms.ltdmanager.module
|
||||
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
|
||||
class ModuleManager {
|
||||
|
||||
private val modules = mutableMapOf<String, BaseModule>()
|
||||
|
||||
/**
|
||||
* 注册模块到管理器
|
||||
*/
|
||||
fun registerModule(module: BaseModule) {
|
||||
if (modules.containsKey(module.name)) {
|
||||
LoggerUtil.logger.warn("模块已注册: ${module.name}")
|
||||
return
|
||||
}
|
||||
modules[module.name] = module
|
||||
LoggerUtil.logger.info("模块注册: ${module.name}")
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定模块
|
||||
*/
|
||||
fun loadModule(name: String) {
|
||||
val module = modules[name]
|
||||
if (module == null) {
|
||||
LoggerUtil.logger.warn("尝试加载不存在的模块: $name")
|
||||
return
|
||||
}
|
||||
if (module.loaded) {
|
||||
LoggerUtil.logger.info("模块已加载: $name")
|
||||
return
|
||||
}
|
||||
try {
|
||||
module.load()
|
||||
LoggerUtil.logger.info("模块加载: $name")
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.error("加载模块 $name 失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载指定模块
|
||||
*/
|
||||
fun unloadModule(name: String) {
|
||||
val module = modules[name]
|
||||
if (module == null) {
|
||||
LoggerUtil.logger.warn("尝试卸载不存在的模块: $name")
|
||||
return
|
||||
}
|
||||
if (!module.loaded) {
|
||||
LoggerUtil.logger.info("模块未加载: $name")
|
||||
return
|
||||
}
|
||||
try {
|
||||
module.unload()
|
||||
LoggerUtil.logger.info("模块卸载: $name")
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.warn("卸载模块 $name 失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载所有模块
|
||||
*/
|
||||
fun unloadAll() {
|
||||
modules.values.forEach { module ->
|
||||
try {
|
||||
if (module.loaded) {
|
||||
module.unload()
|
||||
LoggerUtil.logger.info("模块卸载: ${module.name}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.warn("卸载模块 ${module.name} 失败", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有模块名称
|
||||
*/
|
||||
fun getModuleNames(): List<String> = modules.keys.toList()
|
||||
|
||||
/**
|
||||
* 检查模块是否已加载
|
||||
*/
|
||||
fun isModuleLoaded(name: String): Boolean {
|
||||
return modules[name]?.loaded ?: false
|
||||
}
|
||||
/**
|
||||
* 扩展方法:批量加载模块
|
||||
*/
|
||||
fun ModuleManager.loadModules(vararg names: String) {
|
||||
names.forEach { loadModule(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展方法:批量卸载模块
|
||||
*/
|
||||
fun ModuleManager.unloadModules(vararg names: String) {
|
||||
names.forEach { unloadModule(it) }
|
||||
}
|
||||
/**
|
||||
* 关闭所有模块
|
||||
*/
|
||||
suspend fun stopAllModules() {
|
||||
modules.values.forEach { module ->
|
||||
if (module.loaded) {
|
||||
module.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,501 @@
|
|||
package top.r3944realms.ltdmanager.module
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
||||
import top.r3944realms.ltdmanager.napcat.NapCatClient
|
||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||
import top.r3944realms.ltdmanager.napcat.event.message.GetGroupMsgHistoryEvent
|
||||
import top.r3944realms.ltdmanager.napcat.request.message.GetGroupMsgHistoryRequest
|
||||
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
||||
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||
import top.r3944realms.ltdmanager.utils.CmdUtil
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
class RconPlayerListModule(
|
||||
private val pollIntervalMillis: Long = 30_000L,
|
||||
private val timeout: Long = 2_000L,
|
||||
private val cooldownMillis: Long = 30_000L, // 默认 30 秒
|
||||
private var lastSuccessTime: Long = 0L,
|
||||
private var msgHistoryCheck: Int = 5,
|
||||
private val targetGroupId: Long,
|
||||
private val selfId: Long,
|
||||
private val selfNickName: String,
|
||||
private val keywords: Set<String> = setOf("查看玩家列表", "玩家列表", "在线玩家")
|
||||
) : BaseModule() {
|
||||
|
||||
private val stopSignal = CompletableDeferred<Unit>() // 用于等待协程退出
|
||||
override val name: String = "RconPlayerListModule"
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
// 持久化文件路径
|
||||
private val stateFile = File("rcon_playerlist_state.json")
|
||||
|
||||
// 保存最新触发过的消息 realId 和 time
|
||||
private var moduleState: ModuleState = loadState()
|
||||
|
||||
private val rconPath: String
|
||||
get() = YamlConfigLoader.loadToolConfig().rcon.mcRconToolPath
|
||||
?: throw IllegalStateException("RCON 工具路径未配置")
|
||||
|
||||
private val rconConfigPath: String
|
||||
get() = YamlConfigLoader.loadToolConfig().rcon.mcRconToolConfigPath
|
||||
?: throw IllegalStateException("Rcon配置路径未配置")
|
||||
|
||||
override fun onLoad() {
|
||||
LoggerUtil.logger.info("[$name] 模块已装载,目标群组: $targetGroupId,轮询间隔: ${pollIntervalMillis}ms")
|
||||
LoggerUtil.logger.info("[$name] 上次触发状态: realId=${moduleState.lastTriggeredRealId}, time=${moduleState.lastTriggerTime}")
|
||||
LoggerUtil.logger.info("[$name] 关键词列表: $keywords")
|
||||
|
||||
scope.launch {
|
||||
LoggerUtil.logger.info("[$name] 轮询协程启动")
|
||||
try {
|
||||
while (isActive) {
|
||||
LoggerUtil.logger.debug("[$name] 开始轮询群消息历史...")
|
||||
try {
|
||||
val historyEvent = napCatClient.send(
|
||||
GetGroupMsgHistoryRequest(
|
||||
count = msgHistoryCheck,
|
||||
groupId = ID.long(targetGroupId)
|
||||
)
|
||||
) as? GetGroupMsgHistoryEvent
|
||||
|
||||
val messages = historyEvent?.data?.messages ?: emptyList()
|
||||
LoggerUtil.logger.debug("[$name] 获取到 ${messages.size} 条最近消息")
|
||||
|
||||
// 找到比 lastTriggeredRealId 更新的触发消息
|
||||
val triggerMessages = messages.filter { msg ->
|
||||
((msg.time > moduleState.lastTriggerTime ||
|
||||
(msg.time == moduleState.lastTriggerTime && msg.realId > moduleState.lastTriggeredRealId)) && msg.userId != selfId) &&
|
||||
msg.message.any { seg ->
|
||||
seg.type == MessageType.Text &&
|
||||
seg.data.text?.let { text ->
|
||||
keywords.any { keyword ->
|
||||
text == keyword
|
||||
}
|
||||
} == true
|
||||
}
|
||||
}
|
||||
|
||||
LoggerUtil.logger.debug("[$name] 找到 ${triggerMessages.size} 条符合条件的触发消息")
|
||||
|
||||
if (triggerMessages.isNotEmpty()) {
|
||||
val triggerMsg = triggerMessages.maxBy { it.time }
|
||||
LoggerUtil.logger.info("[$name] 找到触发消息 realId=${triggerMsg.realId}, time=${triggerMsg.time}, userId=${triggerMsg.userId}")
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
// ✅ 首次触发允许直接执行
|
||||
val canTrigger = (lastSuccessTime == 0L) || (now - lastSuccessTime >= cooldownMillis)
|
||||
|
||||
if (!canTrigger) {
|
||||
val remaining = ((cooldownMillis - (now - lastSuccessTime)) / 1000).coerceAtLeast(1)
|
||||
LoggerUtil.logger.info("[$name] 冷却中,拒绝执行,剩余 $remaining 秒")
|
||||
sendCooldownMessage(napCatClient, triggerMsg.realId, triggerMsg.time)
|
||||
continue
|
||||
}
|
||||
|
||||
// 执行 RCON
|
||||
val commands = listOf("forge tps","list")
|
||||
LoggerUtil.logger.info("[$name] 执行 RCON 命令: $commands")
|
||||
|
||||
|
||||
runCatching {
|
||||
val tpsOutput = runCatching {
|
||||
CmdUtil.runExeCommand(rconPath, "-c", rconConfigPath, "-T", (timeout / 1000).toString() + "s", "forge tps")
|
||||
}.getOrElse { ex ->
|
||||
LoggerUtil.logger.warn("[$name] 执行 forge tps 失败: ${ex.message}")
|
||||
""
|
||||
}
|
||||
|
||||
val listOutput = runCatching {
|
||||
CmdUtil.runExeCommand(rconPath, "-c", rconConfigPath, "-T", (timeout / 1000).toString() + "s", "list")
|
||||
}.getOrElse { ex ->
|
||||
LoggerUtil.logger.warn("[$name] 执行 list 失败: ${ex.message}")
|
||||
""
|
||||
}
|
||||
|
||||
// 合并输出,再解析
|
||||
buildString {
|
||||
appendLine(tpsOutput.trim())
|
||||
appendLine("--------")
|
||||
appendLine(listOutput.trim())
|
||||
}
|
||||
} .onFailure { ex ->
|
||||
if (ex is TimeoutException) {
|
||||
LoggerUtil.logger.warn("[$name] RCON 连接超时: ${ex.message}")
|
||||
sendFailedMessage(napCatClient, triggerMsg.realId, triggerMsg.time)
|
||||
} else {
|
||||
LoggerUtil.logger.error("[$name] RCON 命令执行失败", ex)
|
||||
sendFailedMessage(
|
||||
napCatClient,
|
||||
triggerMsg.realId,
|
||||
triggerMsg.time,
|
||||
"系统内部错误请联系管理员:${ex.message}"
|
||||
)
|
||||
throw ex
|
||||
}
|
||||
} .onSuccess { output ->
|
||||
lastSuccessTime = now // ✅ 成功后记录冷却开始时间
|
||||
LoggerUtil.logger.info("[$name] RCON 命令执行成功,输出长度: ${output.length}")
|
||||
LoggerUtil.logger.debug("[$name] RCON 输出内容: $output")
|
||||
val tpsInfo = parseTPS(output)
|
||||
val playerListInfo = parsePlayerList(output)
|
||||
LoggerUtil.logger.info("[$name] 解析成功: TPS=${tpsInfo.overall.meanTPS}, 在线 ${playerListInfo.onlineCount} 人")
|
||||
// 发送转发消息
|
||||
sendForwardMessage(napCatClient, tpsInfo, playerListInfo, triggerMsg.realId, triggerMsg.time)
|
||||
}
|
||||
} else {
|
||||
LoggerUtil.logger.debug("[$name] 未找到新的触发消息")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.error("[$name] 轮询玩家列表或发送转发消息失败", e)
|
||||
}
|
||||
LoggerUtil.logger.debug("[$name] 本轮轮询完成,等待 ${pollIntervalMillis}ms")
|
||||
delay(pollIntervalMillis)
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
LoggerUtil.logger.info("[$name] 轮询协程收到取消信号")
|
||||
} finally {
|
||||
LoggerUtil.logger.info("[$name] 轮询协程退出,完成 stopSignal")
|
||||
stopSignal.complete(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onUnload() {
|
||||
LoggerUtil.logger.info("[$name] 模块已卸载")
|
||||
saveState(moduleState.lastTriggeredRealId, moduleState.lastTriggerTime) // 卸载时保存
|
||||
}
|
||||
|
||||
override suspend fun stop() {
|
||||
LoggerUtil.logger.info("[$name] 收到停止命令,开始关闭协程...")
|
||||
scope.cancel() // 取消协程
|
||||
LoggerUtil.logger.info("[$name] 等待协程退出...")
|
||||
stopSignal.await() // 等待协程完成
|
||||
LoggerUtil.logger.info("[$name] 协程已退出,卸载模块资源")
|
||||
onUnload() // 卸载模块资源,保存状态
|
||||
}
|
||||
private suspend fun sendCooldownMessage(client: NapCatClient, realId: Long, time: Long) {
|
||||
val now = System.currentTimeMillis()
|
||||
val remaining = ((cooldownMillis - (now - lastSuccessTime)) / 1000).coerceAtLeast(1) // 至少显示 1 秒
|
||||
val msg = "⏳ 查询过于频繁,请稍后再试(剩余 $remaining 秒)"
|
||||
|
||||
LoggerUtil.logger.info("[$name] 发送冷却提示: $msg")
|
||||
|
||||
val request = SendGroupMsgRequest(
|
||||
MessageElement.reply(ID.long(realId), msg),
|
||||
ID.long(targetGroupId)
|
||||
)
|
||||
client.sendUnit(request)
|
||||
|
||||
// 更新触发状态,但不更新 lastSuccessTime(避免延长冷却)
|
||||
moduleState.lastTriggeredRealId = realId
|
||||
moduleState.lastTriggerTime = time
|
||||
saveState(realId, time)
|
||||
}
|
||||
|
||||
private val failedMessages = listOf(
|
||||
"💥 土豆服务器炸了,请稍后再试",
|
||||
"🥔 土豆过热,正在冷却中……",
|
||||
"🐌 RCON 响应太慢,像蜗牛一样",
|
||||
"🛠️ 系统开小差了,请联系管理员",
|
||||
"⚠️ 服务器没理我,可能在打盹",
|
||||
"🔥 电路冒烟了!查询失败"
|
||||
)
|
||||
private suspend fun sendFailedMessage(
|
||||
client: NapCatClient,
|
||||
realId: Long,
|
||||
time: Long,
|
||||
text: String? = null
|
||||
) {
|
||||
// 如果调用时传了 text,就用 text,否则随机选择一条
|
||||
val finalText = text ?: failedMessages.random()
|
||||
|
||||
LoggerUtil.logger.info("[$name] 发送失败消息: realId=$realId, text=$finalText")
|
||||
|
||||
val request = SendGroupMsgRequest(
|
||||
MessageElement.reply(ID.long(realId), finalText),
|
||||
ID.long(targetGroupId)
|
||||
)
|
||||
client.sendUnit(request)
|
||||
LoggerUtil.logger.info("[$name] 已发送 RCON 失败消息")
|
||||
|
||||
// 更新触发的最大 realId
|
||||
moduleState.lastTriggeredRealId = realId
|
||||
moduleState.lastTriggerTime = time
|
||||
saveState(realId, time) // 保存到文件
|
||||
}
|
||||
private suspend fun sendForwardMessage(client: NapCatClient, tps: TPSInfo, info: PlayerListInfo, realId: Long, time: Long) {
|
||||
LoggerUtil.logger.info("[$name] 发送转发消息: realId=$realId, TPS=${tps.overall.meanTPS}, 在线玩家数=${info.onlineCount}")
|
||||
|
||||
val messages = mutableListOf<SendForwardMsgRequest.Message>()
|
||||
|
||||
// ① 服务器TPS状态
|
||||
val tpsMessage = SendForwardMsgRequest.Message(
|
||||
data = SendForwardMsgRequest.PurpleData(
|
||||
text = buildString {
|
||||
appendLine("⚡ 服务器性能状态 ${getStatusEmoji(tps.status)}")
|
||||
appendLine("═".repeat(25))
|
||||
appendLine("整体TPS: ${"%.3f".format(tps.overall.meanTPS)} (${getStatusDescription(tps.status)})")
|
||||
appendLine("平均Tick耗时: ${"%.3f".format(tps.overall.meanTickTime)} ms")
|
||||
appendLine()
|
||||
appendLine("📌 各维度详情:")
|
||||
tps.dimensions.forEach {
|
||||
appendLine("- ${it.name}: ${"%.3f".format(it.meanTPS)} TPS, ${"%.3f".format(it.meanTickTime)} ms")
|
||||
}
|
||||
}
|
||||
),
|
||||
type = MessageType.Text
|
||||
)
|
||||
messages.add(tpsMessage)
|
||||
|
||||
// ② 玩家列表
|
||||
if (info.players.isNotEmpty()) {
|
||||
val playerListMessage = SendForwardMsgRequest.Message(
|
||||
data = SendForwardMsgRequest.PurpleData(
|
||||
text = buildString {
|
||||
appendLine("👥 玩家列表")
|
||||
appendLine("─".repeat(20))
|
||||
appendLine("在线人数: ${info.onlineCount}")
|
||||
info.players.forEachIndexed { index, player ->
|
||||
appendLine("${index + 1}. 🧑💻 $player")
|
||||
}
|
||||
}
|
||||
),
|
||||
type = MessageType.Text
|
||||
)
|
||||
messages.add(playerListMessage)
|
||||
} else {
|
||||
messages.add(
|
||||
SendForwardMsgRequest.Message(
|
||||
data = SendForwardMsgRequest.PurpleData("😴 当前没有玩家在线"),
|
||||
type = MessageType.Text
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ③ 摘要消息
|
||||
val summaryMessage = SendForwardMsgRequest.Message(
|
||||
data = SendForwardMsgRequest.PurpleData(
|
||||
text = buildString {
|
||||
appendLine("📊 查询摘要")
|
||||
appendLine("─".repeat(15))
|
||||
appendLine("TPS: ${"%.3f".format(tps.overall.meanTPS)} - ${getStatusDescription(tps.status)}")
|
||||
appendLine("在线玩家: ${info.onlineCount} 人")
|
||||
appendLine("🕐 ${getCurrentTime()}")
|
||||
appendLine("🤖 由 $selfNickName 提供")
|
||||
}
|
||||
),
|
||||
type = MessageType.Text
|
||||
)
|
||||
messages.add(summaryMessage)
|
||||
|
||||
val topMessage = SendForwardMsgRequest.TopForwardMsg(
|
||||
data = SendForwardMsgRequest.MessageData(
|
||||
content = messages,
|
||||
nickname = selfNickName,
|
||||
userId = ID.long(selfId),
|
||||
),
|
||||
type = MessageType.Node
|
||||
)
|
||||
|
||||
val request = SendForwardMsgRequest(
|
||||
groupId = ID.long(targetGroupId),
|
||||
messages = listOf(topMessage),
|
||||
news = listOf(
|
||||
SendForwardMsgRequest.ForwardModelNews("点击查看服务器状态与玩家列表"),
|
||||
SendForwardMsgRequest.ForwardModelNews("TPS: ${"%.1f".format(tps.overall.meanTPS)} 在线 ${info.onlineCount} 人"),
|
||||
SendForwardMsgRequest.ForwardModelNews("更新时间: ${getCurrentTime()}")
|
||||
),
|
||||
prompt = "TPS + 玩家列表查询结果",
|
||||
source = "🎮 服务器状态",
|
||||
summary = "TPS ${"%.1f".format(tps.overall.meanTPS)}, 在线玩家: ${info.onlineCount}人",
|
||||
)
|
||||
|
||||
client.sendUnit(request)
|
||||
LoggerUtil.logger.info("[$name] 已发送 TPS+玩家列表 转发消息")
|
||||
moduleState.lastTriggeredRealId = realId
|
||||
moduleState.lastTriggerTime = time
|
||||
saveState(realId, time)
|
||||
}
|
||||
|
||||
// 添加时间格式化函数
|
||||
private fun getCurrentTime(): String {
|
||||
return java.time.LocalDateTime.now().format(
|
||||
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
)
|
||||
}
|
||||
// 在类内部添加以下数据类和函数
|
||||
|
||||
@Serializable
|
||||
data class TPSInfo(
|
||||
val dimensions: List<DimensionTPS>,
|
||||
val overall: OverallTPS,
|
||||
val status: ServerStatus
|
||||
) {
|
||||
@Serializable
|
||||
data class DimensionTPS(
|
||||
val name: String,
|
||||
val meanTickTime: Double,
|
||||
val meanTPS: Double
|
||||
)
|
||||
@Serializable
|
||||
data class OverallTPS(
|
||||
val meanTickTime: Double,
|
||||
val meanTPS: Double
|
||||
)
|
||||
|
||||
enum class ServerStatus {
|
||||
EXCELLENT, // TPS = 20.0
|
||||
GOOD, // TPS >= 18.0
|
||||
FAIR, // TPS >= 15.0
|
||||
POOR, // TPS >= 10.0
|
||||
CRITICAL // TPS < 10.0
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 parsePlayerList 函数来处理组合输出
|
||||
private fun parsePlayerList(output: String): PlayerListInfo {
|
||||
LoggerUtil.logger.debug("[$name] 解析玩家列表输出: ${output.take(100)}...")
|
||||
|
||||
// 检查是否是连接超时错误
|
||||
if (output.contains("dial tcp") && output.contains("i/o timeout")) {
|
||||
LoggerUtil.logger.warn("[$name] 检测到连接超时错误")
|
||||
throw TimeoutException("服务器不可达")
|
||||
}
|
||||
|
||||
// 分割输出,获取玩家列表部分
|
||||
val parts = output.split("--------")
|
||||
val playerListOutput = if (parts.size > 1) parts[1].trim() else output
|
||||
|
||||
val regex = Regex("""There are (\d+) of a max of \d+ players online:\s*(.*)""")
|
||||
val match = regex.find(playerListOutput)
|
||||
|
||||
if (match == null) {
|
||||
LoggerUtil.logger.warn("[$name] 无法解析玩家列表输出,返回空列表")
|
||||
return PlayerListInfo(0, emptyList())
|
||||
}
|
||||
|
||||
val onlineCount = match.groupValues[1].toInt()
|
||||
val playersString = match.groupValues[2]
|
||||
|
||||
val players = playersString.split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
|
||||
LoggerUtil.logger.debug("[{}] 解析完成: 在线 {} 人,玩家列表: {}", name, onlineCount, players)
|
||||
return PlayerListInfo(onlineCount, players)
|
||||
}
|
||||
|
||||
// 修改 parseTPS 函数来处理组合输出
|
||||
private fun parseTPS(output: String): TPSInfo {
|
||||
LoggerUtil.logger.debug("[$name] 解析TPS输出: ${output.take(100)}...")
|
||||
|
||||
// 分割输出,获取TPS部分
|
||||
val parts = output.split("--------")
|
||||
val tpsOutput = parts[0].trim()
|
||||
|
||||
val dimensionRegex = Regex("""Dim (.+?): Mean tick time: (\d+\.\d+) ms\. Mean TPS: (\d+\.\d+)""")
|
||||
val overallRegex = Regex("""Overall: Mean tick time: (\d+\.\d+) ms\. Mean TPS: (\d+\.\d+)""")
|
||||
|
||||
val dimensions = mutableListOf<TPSInfo.DimensionTPS>()
|
||||
var overall: TPSInfo.OverallTPS? = null
|
||||
|
||||
tpsOutput.lineSequence().forEach { line ->
|
||||
// 解析维度TPS
|
||||
dimensionRegex.find(line)?.let { match ->
|
||||
val name = match.groupValues[1]
|
||||
val meanTickTime = match.groupValues[2].toDouble()
|
||||
val meanTPS = match.groupValues[3].toDouble()
|
||||
|
||||
dimensions.add(TPSInfo.DimensionTPS(name, meanTickTime, meanTPS))
|
||||
}
|
||||
|
||||
// 解析总体TPS
|
||||
overallRegex.find(line)?.let { match ->
|
||||
val meanTickTime = match.groupValues[1].toDouble()
|
||||
val meanTPS = match.groupValues[2].toDouble()
|
||||
|
||||
overall = TPSInfo.OverallTPS(meanTickTime, meanTPS)
|
||||
}
|
||||
}
|
||||
|
||||
if (overall == null) {
|
||||
throw IllegalArgumentException("无法解析TPS输出: $output")
|
||||
}
|
||||
|
||||
// 确定服务器状态
|
||||
val status = when (overall!!.meanTPS) {
|
||||
20.0 -> TPSInfo.ServerStatus.EXCELLENT
|
||||
in 18.0..19.99 -> TPSInfo.ServerStatus.GOOD
|
||||
in 15.0..17.99 -> TPSInfo.ServerStatus.FAIR
|
||||
in 10.0..14.99 -> TPSInfo.ServerStatus.POOR
|
||||
else -> TPSInfo.ServerStatus.CRITICAL
|
||||
}
|
||||
|
||||
return TPSInfo(dimensions, overall!!, status)
|
||||
}
|
||||
|
||||
// 获取服务器状态表情符号
|
||||
private fun getStatusEmoji(status: TPSInfo.ServerStatus): String {
|
||||
return when (status) {
|
||||
TPSInfo.ServerStatus.EXCELLENT -> "💚" // 绿色心形,优秀
|
||||
TPSInfo.ServerStatus.GOOD -> "💛" // 黄色心形,良好
|
||||
TPSInfo.ServerStatus.FAIR -> "🟡" // 黄色圆形,一般
|
||||
TPSInfo.ServerStatus.POOR -> "🟠" // 橙色圆形,较差
|
||||
TPSInfo.ServerStatus.CRITICAL -> "🔴" // 红色圆形,严重
|
||||
}
|
||||
}
|
||||
|
||||
// 获取服务器状态描述
|
||||
private fun getStatusDescription(status: TPSInfo.ServerStatus): String {
|
||||
return when (status) {
|
||||
TPSInfo.ServerStatus.EXCELLENT -> "优秀"
|
||||
TPSInfo.ServerStatus.GOOD -> "良好"
|
||||
TPSInfo.ServerStatus.FAIR -> "一般"
|
||||
TPSInfo.ServerStatus.POOR -> "较差"
|
||||
TPSInfo.ServerStatus.CRITICAL -> "严重"
|
||||
}
|
||||
}
|
||||
data class PlayerListInfo(
|
||||
val onlineCount: Int,
|
||||
val players: List<String>
|
||||
)
|
||||
|
||||
|
||||
|
||||
// ---------------- 持久化部分 ----------------
|
||||
|
||||
@Serializable
|
||||
data class ModuleState(var lastTriggeredRealId: Long, var lastTriggerTime: Long)
|
||||
|
||||
private fun saveState(realId: Long, time: Long) {
|
||||
try {
|
||||
val state = ModuleState(realId, time)
|
||||
stateFile.writeText(Json.encodeToString(state))
|
||||
LoggerUtil.logger.info("[$name] 已保存状态: lastTriggeredRealId=$realId, lastTriggerTime=$time")
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.error("[$name] 保存状态失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadState(): ModuleState {
|
||||
return try {
|
||||
if (!stateFile.exists()) {
|
||||
LoggerUtil.logger.info("[$name] 状态文件不存在,使用默认值")
|
||||
return ModuleState(-1L, 0L)
|
||||
}
|
||||
val state = Json.decodeFromString<ModuleState>(stateFile.readText())
|
||||
LoggerUtil.logger.info("[$name] 成功加载状态: lastTriggeredRealId=${state.lastTriggeredRealId}, lastTriggerTime=${state.lastTriggerTime}")
|
||||
state
|
||||
} catch (e: Exception) {
|
||||
LoggerUtil.logger.warn("[$name] 读取状态失败,使用默认值", e)
|
||||
ModuleState(-1L, 0L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class Developing()
|
||||
annotation class Developing
|
||||
|
|
@ -1,134 +1,200 @@
|
|||
package top.r3944realms.ltdmanager.napcat
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.plugins.websocket.*
|
||||
import io.ktor.websocket.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.slf4j.LoggerFactory
|
||||
import top.r3944realms.ltdmanager.napcat.events.NapCatEvent
|
||||
import top.r3944realms.ltdmanager.napcat.requests.NapCatRequest
|
||||
import top.r3944realms.ltdmanager.napcat.requests.PrioritizedRequest
|
||||
import top.r3944realms.ltdmanager.napcat.requests.PriorityMessageQueue
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
||||
import top.r3944realms.ltdmanager.napcat.event.NapCatEvent
|
||||
import top.r3944realms.ltdmanager.napcat.request.NapCatRequest
|
||||
import top.r3944realms.ltdmanager.utils.Environment
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayDeque
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class NapCatClient private constructor() : Closeable {
|
||||
private val client = HttpClient(CIO)
|
||||
private val httpConfig = YamlConfigLoader.loadHttpConfig()
|
||||
private val token = httpConfig.decryptedToken
|
||||
|
||||
// 限流 (同时最多 3 个请求)
|
||||
private val semaphore = Semaphore(3)
|
||||
|
||||
// 普通优先级队列
|
||||
private val requestQueue = PriorityQueue<QueueItem>(compareBy { it.priority })
|
||||
private val queueMutex = Mutex()
|
||||
|
||||
// 紧急队列 (先进先出,最多 10 个)
|
||||
private val urgentQueue = ArrayDeque<QueueItem>(10)
|
||||
|
||||
class NapCatClient(private val wsUrl: String, private val token: String) {
|
||||
private val client = HttpClient(CIO) { install(WebSockets) }
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val logger = LoggerFactory.getLogger(NapCatClient::class.java)
|
||||
|
||||
// 请求-响应匹配队列(FIFO)
|
||||
private val pendingResponses = Channel<CompletableDeferred<NapCatEvent>>(capacity = Channel.UNLIMITED)
|
||||
private val mutex = Mutex()
|
||||
|
||||
// 事件通道(用于非请求响应的消息)
|
||||
// 优先级队列(按优先级发送请求)
|
||||
private val priorityQueue = PriorityMessageQueue()
|
||||
private val eventChannel = Channel<NapCatEvent>(capacity = Channel.UNLIMITED)
|
||||
private val _connectionState = MutableStateFlow(false)
|
||||
val connectionState = _connectionState.asStateFlow()
|
||||
|
||||
// 子协程引用
|
||||
private var receiverJob: Job? = null
|
||||
private var senderJob: Job? = null
|
||||
|
||||
suspend fun start() {
|
||||
receiverJob = scope.launch { launchReceiver() }
|
||||
senderJob = scope.launch { launchSender() }
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private suspend fun launchReceiver() {
|
||||
try {
|
||||
client.wss(
|
||||
host = wsUrl.removePrefix("ws://").substringBefore(':'),
|
||||
port = wsUrl.substringAfterLast(':').toInt(),
|
||||
path = "/"
|
||||
) {
|
||||
send(Frame.Text("""{"token":"$token"}"""))
|
||||
_connectionState.value = true
|
||||
|
||||
while (true) {
|
||||
when (val frame = incoming.receive()) {
|
||||
is Frame.Text -> {
|
||||
val event = Json.decodeFromString<NapCatEvent>(frame.readText())
|
||||
// 尝试匹配最近的请求
|
||||
if (!pendingResponses.isEmpty) {
|
||||
pendingResponses.tryReceive().getOrNull()?.complete(event)
|
||||
} else {
|
||||
eventChannel.send(event) // 非请求响应的消息
|
||||
}
|
||||
}
|
||||
is Frame.Close -> break
|
||||
else -> {}
|
||||
init {
|
||||
scope.launch {
|
||||
while (isActive) {
|
||||
val item = queueMutex.withLock {
|
||||
when {
|
||||
urgentQueue.isNotEmpty() -> urgentQueue.removeFirst()
|
||||
requestQueue.isNotEmpty() -> requestQueue.poll()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
_connectionState.value = false
|
||||
pendingResponses.close()
|
||||
eventChannel.close()
|
||||
priorityQueue.close()
|
||||
}
|
||||
}
|
||||
private suspend fun launchSender() {
|
||||
while (coroutineContext.isActive) {
|
||||
try {
|
||||
// 从优先级队列取出请求(自动按优先级排序)
|
||||
val prioritized = priorityQueue.dequeue()
|
||||
val request = prioritized.request
|
||||
|
||||
// 发送前注册响应监听器
|
||||
val deferred = CompletableDeferred<NapCatEvent>()
|
||||
mutex.withLock {
|
||||
pendingResponses.send(deferred)
|
||||
if (item == null) {
|
||||
// 队列空 -> 挂起一小段时间等待新任务
|
||||
delay(20)
|
||||
continue
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
client.webSocketSession(wsUrl).send(Frame.Text(Json.encodeToString(request)))
|
||||
|
||||
// 等待响应(超时由外层 sendRequest 控制)
|
||||
deferred.await()
|
||||
} catch (e: Exception) {
|
||||
logger.error("发送请求失败", e)
|
||||
delay(1000) // 错误时暂停1秒
|
||||
processRequest(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 普通发送 (带优先级) 无返回事件版本
|
||||
* 适用于只需要发送请求,不关心返回结果的情况,例如 SetGroupAddRequestRequest
|
||||
*/
|
||||
suspend fun sendUnit(
|
||||
request: NapCatRequest,
|
||||
retries: Int = 3,
|
||||
priority: Int = 5
|
||||
) {
|
||||
checkRequest(request)
|
||||
val deferred = CompletableDeferred<Unit>()
|
||||
queueMutex.withLock {
|
||||
requestQueue.add(QueueItem(request, deferred, retries, priority, expectsEvent = false))
|
||||
}
|
||||
deferred.await()
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带优先级的请求
|
||||
* @param priority 优先级(HIGH_PRIORITY/DEFAULT_PRIORITY/LOW_PRIORITY)
|
||||
* @param timeout 超时时间(毫秒)
|
||||
* 紧急发送 (先进先出, 最多 10 个) 无返回事件版本
|
||||
*/
|
||||
suspend fun sendRequest(
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun sendUrgentUnit(
|
||||
request: NapCatRequest,
|
||||
priority: Int = PrioritizedRequest.DEFAULT_PRIORITY,
|
||||
timeout: Long = 5000
|
||||
): NapCatEvent = withTimeout(timeout) {
|
||||
val deferred = CompletableDeferred<NapCatEvent>()
|
||||
// 将请求加入优先级队列
|
||||
priorityQueue.enqueue(PrioritizedRequest(request, priority))
|
||||
deferred.await() // 等待响应(由 launchSender 和 launchReceiver 协作完成)
|
||||
retries: Int = 3
|
||||
) {
|
||||
checkRequest(request)
|
||||
val deferred = CompletableDeferred<Unit>()
|
||||
queueMutex.withLock {
|
||||
if (urgentQueue.size >= 10) {
|
||||
throw IllegalStateException("紧急任务队列已满 (最多 10 个)")
|
||||
}
|
||||
urgentQueue.addLast(QueueItem(request, deferred, retries, priority = Int.MIN_VALUE, expectsEvent = false))
|
||||
}
|
||||
deferred.await()
|
||||
}
|
||||
|
||||
val incomingEvents: ReceiveChannel<NapCatEvent> = eventChannel
|
||||
private fun cleanup() {
|
||||
_connectionState.value = false
|
||||
pendingResponses.close()
|
||||
eventChannel.close()
|
||||
priorityQueue.close()
|
||||
/**
|
||||
* 普通发送 (带优先级)
|
||||
*/
|
||||
suspend fun <T : NapCatEvent> send(
|
||||
request: NapCatRequest,
|
||||
retries: Int = 3,
|
||||
priority: Int = 5
|
||||
): T {
|
||||
checkRequest(request)
|
||||
val deferred = CompletableDeferred<T>()
|
||||
queueMutex.withLock {
|
||||
requestQueue.add(QueueItem(request, deferred, retries, priority, expectsEvent = true))
|
||||
}
|
||||
return deferred.await()
|
||||
}
|
||||
fun close() {
|
||||
scope.cancel("NapCatClient closed")
|
||||
cleanup()
|
||||
|
||||
/**
|
||||
* 紧急发送 (先进先出, 最多 10 个)
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun <T : NapCatEvent> sendUrgent(
|
||||
request: NapCatRequest,
|
||||
retries: Int = 3
|
||||
): T {
|
||||
checkRequest(request)
|
||||
val deferred = CompletableDeferred<T>()
|
||||
queueMutex.withLock {
|
||||
if (urgentQueue.size >= 10) {
|
||||
throw IllegalStateException("紧急任务队列已满 (最多 10 个)")
|
||||
}
|
||||
urgentQueue.addLast(QueueItem(request, deferred, retries, priority = Int.MIN_VALUE, expectsEvent = true))
|
||||
}
|
||||
return deferred.await()
|
||||
}
|
||||
}
|
||||
private fun checkRequest(request: NapCatRequest) {
|
||||
// 如果请求类标记为 @Developing,则抛出异常
|
||||
if (request::class.annotations.any { it.annotationClass == Developing::class }) {
|
||||
throw UnsupportedOperationException(
|
||||
"请求类 ${request::class.simpleName} 标记为 @Developing,不支持发送"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private suspend fun processRequest(item: QueueItem) {
|
||||
semaphore.withPermit {
|
||||
val (request, deferred, retries, _, expectsEvent) = item
|
||||
var attempt = 0
|
||||
var lastError: Throwable? = null
|
||||
|
||||
while (attempt < retries) {
|
||||
try {
|
||||
val apiUrl = URLBuilder(httpConfig.url.toString()).apply {
|
||||
encodedPath += request.path()
|
||||
}.build()
|
||||
|
||||
if(!Environment.isProduction()) LoggerUtil.logger.debug("发送请求: ${request.toJSON()}")
|
||||
|
||||
val response = client.post(apiUrl) {
|
||||
contentType(ContentType.Application.Json)
|
||||
header("Authorization", "Bearer $token")
|
||||
setBody(request.toJSON())
|
||||
}
|
||||
|
||||
if (!response.status.isSuccess()) {
|
||||
throw IllegalStateException("请求失败: HTTP ${response.status}")
|
||||
}
|
||||
if (response.contentType()?.match(ContentType.Application.Json) != true && expectsEvent) {
|
||||
throw IllegalStateException("请求失败: 响应类型不是 JSON (${response.contentType()})")
|
||||
}
|
||||
|
||||
val jsonText: String = response.body()
|
||||
if(!Environment.isProduction()) LoggerUtil.logger.debug("响应: $jsonText")
|
||||
if (expectsEvent) {
|
||||
val event = NapCatEvent.decodeEvent(jsonText, request.type())
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(deferred as CompletableDeferred<NapCatEvent>).complete(event)
|
||||
} else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(deferred as CompletableDeferred<Unit>).complete(Unit)
|
||||
}
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
lastError = e
|
||||
LoggerUtil.logger.warn("请求失败, 第 ${attempt + 1} 次: ${e.message}")
|
||||
delay(((attempt + 1) * 2L).seconds) // 指数退避
|
||||
}
|
||||
attempt++
|
||||
}
|
||||
|
||||
deferred.completeExceptionally(lastError ?: RuntimeException("未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
scope.cancel()
|
||||
runBlocking { client.close() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(): NapCatClient = NapCatClient()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package top.r3944realms.ltdmanager.napcat
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import top.r3944realms.ltdmanager.napcat.request.NapCatRequest
|
||||
|
||||
data class QueueItem(
|
||||
val request: NapCatRequest,
|
||||
val deferred: CompletableDeferred<*>,
|
||||
var retries: Int,
|
||||
val priority: Int,
|
||||
val expectsEvent: Boolean // true 表示返回 NapCatEvent, false 表示 Unit
|
||||
) : Comparable<QueueItem> {
|
||||
override fun compareTo(other: QueueItem): Int = priority.compareTo(other.priority)
|
||||
}
|
||||
|
|
@ -6,15 +6,15 @@ import kotlinx.serialization.Serializable
|
|||
@Serializable
|
||||
data class Author (
|
||||
@SerialName("groupId")
|
||||
val groupID: String,
|
||||
val groupId: String,
|
||||
|
||||
val groupName: String,
|
||||
|
||||
@SerialName("numId")
|
||||
val numID: String,
|
||||
val numId: String,
|
||||
|
||||
@SerialName("strId")
|
||||
val strID: String,
|
||||
val strId: String,
|
||||
|
||||
val type: Double,
|
||||
val uid: String
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ import kotlinx.serialization.Serializable
|
|||
@Serializable
|
||||
data class CollectionItemList (
|
||||
val author: Author,
|
||||
val bid: Double,
|
||||
val category: Double,
|
||||
val bid: Long,
|
||||
val category: Long,
|
||||
val cid: String,
|
||||
val collectTime: String,
|
||||
val createTime: String,
|
||||
|
||||
@SerialName("customGroupId")
|
||||
val customGroupID: Double,
|
||||
val customGroupId: Double,
|
||||
|
||||
val modifyTime: String,
|
||||
val securityBeat: Boolean,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
package top.r3944realms.ltdmanager.napcat.data
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
|
||||
/**
|
||||
* 好友信息
|
||||
*/
|
||||
@Serializable
|
||||
data class FriendInfo(
|
||||
data class FriendInfo @OptIn(ExperimentalSerializationApi::class) constructor(
|
||||
@SerialName("birthday_year")
|
||||
val birthdayYear: Int,
|
||||
@SerialName("birthday_month")
|
||||
|
|
@ -15,15 +17,15 @@ data class FriendInfo(
|
|||
@SerialName("birthday_day")
|
||||
val birthdayDay: Int,
|
||||
@SerialName("user_id")
|
||||
val userId: Long,
|
||||
val userId: Int,
|
||||
val age: Int,
|
||||
@SerialName("phone_number")
|
||||
@JsonNames("phone_number", "phone_num")
|
||||
val phoneNum: String,
|
||||
val email: String,
|
||||
@SerialName("category_id")
|
||||
val categoryId: Int,
|
||||
val nickname: String,
|
||||
val remark: String,
|
||||
val sex: String,
|
||||
val sex: SexV2,
|
||||
val level: Int
|
||||
)
|
||||
|
|
@ -1,12 +1,19 @@
|
|||
package top.r3944realms.ltdmanager.napcat.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import top.r3944realms.ltdmanager.napcat.serializer.IDSerializer
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@Serializable
|
||||
@Serializable(with = IDSerializer::class)
|
||||
sealed class ID {
|
||||
class DoubleValue(val value: Double) : ID()
|
||||
@Serializable
|
||||
class LongValue(val value: Long) : ID()
|
||||
@Serializable
|
||||
class StringValue(val value: String) : ID()
|
||||
companion object {
|
||||
fun long(value: Long) = LongValue(value)
|
||||
fun str(value: String) = StringValue(value)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class MessageElement(
|
||||
class MessageElement private constructor (
|
||||
val type: MessageType,
|
||||
val data: Message? = null
|
||||
) {
|
||||
|
|
@ -13,7 +13,7 @@ class MessageElement(
|
|||
fun at(qq: ID, name: String?): MessageElement = MessageElement(MessageType.At, AtMessage(qq, name))
|
||||
fun image(file: String, summary: String?): MessageElement = MessageElement(MessageType.Image, ImageMessage(file, summary))
|
||||
fun json(json: String): MessageElement = MessageElement(MessageType.JSON, JSONMessage(json))
|
||||
fun face(id: Int): MessageElement = MessageElement(MessageType.Face, FaceMessage(id))
|
||||
fun face(id: Long): MessageElement = MessageElement(MessageType.Face, FaceMessage(id))
|
||||
fun record(file: String): MessageElement = MessageElement(MessageType.Record, RecordMessage(file))
|
||||
fun markdown(text: String): MessageElement = MessageElement(MessageType.Record, RecordMessage(text))
|
||||
fun video(video: String): MessageElement = MessageElement(MessageType.Video, VideoMessage(video))
|
||||
|
|
@ -29,7 +29,7 @@ class MessageElement(
|
|||
|
||||
}
|
||||
@Serializable
|
||||
abstract class Message
|
||||
sealed class Message
|
||||
|
||||
/**
|
||||
* 文本
|
||||
|
|
@ -91,7 +91,7 @@ class MessageElement(
|
|||
*/
|
||||
@Serializable
|
||||
data class FaceMessage(
|
||||
val id: Int
|
||||
val id: Long
|
||||
) : Message()
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
package top.r3944realms.ltdmanager.napcat.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class QQ {
|
||||
class DoubleValue(val value: Double) : QQ()
|
||||
class StringValue(val value: String) : QQ()
|
||||
}
|
||||
|
|
@ -15,5 +15,5 @@ data class Sender (
|
|||
val sex: SexV2? = null,
|
||||
|
||||
@SerialName("user_id")
|
||||
val userID: Double
|
||||
val userId: Long
|
||||
)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package top.r3944realms.ltdmanager.napcat.event
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
@Serializable
|
||||
data class FailedRequestEvent(
|
||||
val status: Status = Status.Failed,
|
||||
val retcode: Int,
|
||||
val data: JsonElement?= null,
|
||||
val message: String,
|
||||
val wording: String,
|
||||
val echo: String? = null
|
||||
): NapCatEvent() {
|
||||
override fun type(): String {
|
||||
return "FailedRequestEvent"
|
||||
}
|
||||
|
||||
override fun subtype(): String {
|
||||
return "FailedRequestEvent"
|
||||
}
|
||||
|
||||
override fun isOk(): Boolean = false
|
||||
|
||||
companion object {
|
||||
internal val json: Json by lazy {
|
||||
Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package top.r3944realms.ltdmanager.napcat.event
|
||||
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import top.r3944realms.ltdmanager.napcat.event.account.AbstractAccountEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.file.AbstractFileEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.group.AbstractGroupEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.message.AbstractMessageEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.other.AbstractOtherEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.passkey.AbstractPassKeyEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.personal.AbstractPersonalEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.system.AbstractSystemEvent
|
||||
|
||||
|
||||
/**
|
||||
* 基础NapCat事件类
|
||||
* @property httpStatusCode HTTP状态码
|
||||
* @property createTime 创建时间戳
|
||||
*/
|
||||
@Serializable
|
||||
abstract class NapCatEvent(
|
||||
@Transient
|
||||
open val httpStatusCode: HttpStatusCode = HttpStatusCode.OK,
|
||||
@Transient
|
||||
open val createTime: Long = System.currentTimeMillis()
|
||||
) {
|
||||
abstract fun type() :String
|
||||
abstract fun subtype(): String
|
||||
companion object {
|
||||
private val eventTypeMap by lazy {
|
||||
mutableMapOf<String, KSerializer<out NapCatEvent>>().apply {
|
||||
putAll(AbstractAccountEvent.eventTypeMap)
|
||||
putAll(AbstractFileEvent.eventTypeMap)
|
||||
putAll(AbstractOtherEvent.eventTypeMap)
|
||||
putAll(AbstractPersonalEvent.eventTypeMap)
|
||||
putAll(AbstractPassKeyEvent.eventTypeMap)
|
||||
putAll(AbstractGroupEvent.eventTypeMap)
|
||||
putAll(AbstractSystemEvent.eventTypeMap)
|
||||
putAll(AbstractMessageEvent.eventTypeMap)
|
||||
}
|
||||
}
|
||||
|
||||
private fun failedDecode(jsonString: String): FailedRequestEvent {
|
||||
return FailedRequestEvent.json.decodeFromString(jsonString)
|
||||
}
|
||||
fun decodeEvent(jsonString: String, type: String): NapCatEvent {
|
||||
return try {
|
||||
eventTypeMap[type]?.let { serializer ->
|
||||
val json = when {
|
||||
type.startsWith("account/") -> AbstractAccountEvent.json
|
||||
type.startsWith("file/") -> AbstractFileEvent.json
|
||||
type.startsWith("group/") -> AbstractGroupEvent.json
|
||||
type.startsWith("message/") -> AbstractMessageEvent.json
|
||||
type.startsWith("passkey/") -> AbstractPassKeyEvent.json
|
||||
type.startsWith("personal/") -> AbstractPersonalEvent.json
|
||||
type.startsWith("system/") -> AbstractSystemEvent.json
|
||||
type.startsWith("other/") -> AbstractOtherEvent.json
|
||||
else -> Json { ignoreUnknownKeys = true }
|
||||
}
|
||||
json.decodeFromString(serializer, jsonString)
|
||||
} ?: failedDecode(jsonString) // 找不到类型,直接 fallback
|
||||
} catch (e: Exception) {
|
||||
// 解码失败,fallback
|
||||
failedDecode(jsonString)
|
||||
}
|
||||
}
|
||||
}
|
||||
open fun isOk():Boolean = true
|
||||
@Serializable
|
||||
enum class Status(val value: String) {
|
||||
@SerialName("ok") Ok("ok"),
|
||||
@SerialName("failed") Failed("failed"),;
|
||||
companion object {
|
||||
fun isOk(value: Status): Boolean = value == Ok
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -6,7 +6,7 @@ import kotlinx.serialization.json.Json
|
|||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import kotlinx.serialization.modules.subclass
|
||||
import top.r3944realms.ltdmanager.napcat.events.NapCatEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.NapCatEvent
|
||||
|
||||
/**
|
||||
* QQ 账户相关响应抽象
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
|
||||
/**
|
||||
* FetchCustomFace事件
|
||||
|
|
@ -22,7 +21,7 @@ data class FetchCustomFaceEvent(
|
|||
@Transient
|
||||
val echo0: String? = null,
|
||||
|
||||
val data: JsonArray
|
||||
val data: List<String>
|
||||
) : AbstractAccountEvent(status0, retcode0, message0, wording0, echo0) {
|
||||
|
||||
override fun subtype(): String {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import top.r3944realms.ltdmanager.napcat.data.FriendInfo
|
||||
|
||||
/**
|
||||
* GetFriendList事件
|
||||
|
|
@ -22,7 +22,7 @@ data class GetFriendListEvent(
|
|||
@Transient
|
||||
val echo0: String? = null,
|
||||
|
||||
val data: JsonArray
|
||||
val data: List<FriendInfo>
|
||||
) : AbstractAccountEvent(status0, retcode0, message0, wording0, echo0) {
|
||||
|
||||
override fun subtype(): String {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||
import top.r3944realms.ltdmanager.napcat.data.QQ
|
||||
import top.r3944realms.ltdmanager.napcat.data.Sender
|
||||
|
||||
/**
|
||||
|
|
@ -72,7 +72,7 @@ data class GetRecentContactEvent(
|
|||
val font: Double,
|
||||
|
||||
@SerialName("group_id")
|
||||
val groupID: Double? = null,
|
||||
val groupId: Double? = null,
|
||||
|
||||
val message: List<TextMsg>,
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ data class GetRecentContactEvent(
|
|||
val realSeq: String,
|
||||
|
||||
@SerialName("self_id")
|
||||
val selfID: Double,
|
||||
val selfId: Double,
|
||||
|
||||
val sender: Sender,
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ data class GetRecentContactEvent(
|
|||
val time: Double,
|
||||
|
||||
@SerialName("user_id")
|
||||
val userID: Double
|
||||
val userId: Double
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -113,7 +113,7 @@ data class GetRecentContactEvent(
|
|||
val font: Double,
|
||||
|
||||
@SerialName("group_id")
|
||||
val groupID: Double? = null,
|
||||
val groupId: Double? = null,
|
||||
|
||||
val message: List<TextMsg>,
|
||||
|
||||
|
|
@ -136,13 +136,13 @@ data class GetRecentContactEvent(
|
|||
val rawMessage: String,
|
||||
|
||||
@SerialName("real_id")
|
||||
val realID: Double,
|
||||
val realId: Double,
|
||||
|
||||
@SerialName("real_seq")
|
||||
val realSeq: String,
|
||||
|
||||
@SerialName("self_id")
|
||||
val selfID: Double,
|
||||
val selfId: Double,
|
||||
|
||||
val sender: Sender,
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ data class GetRecentContactEvent(
|
|||
val time: Double,
|
||||
|
||||
@SerialName("user_id")
|
||||
val userID: Double
|
||||
val userId: Double
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
@ -165,8 +165,8 @@ data class GetRecentContactEvent(
|
|||
data class Data (
|
||||
val text: String? = null,
|
||||
val name: String? = null,
|
||||
val qq: QQ? = null,
|
||||
val id: QQ? = null,
|
||||
val qq: ID? = null,
|
||||
val id: ID? = null,
|
||||
val file: String? = null,
|
||||
|
||||
/**
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -88,7 +88,7 @@ data class GetStrangerInfoEvent(
|
|||
val uin: String,
|
||||
|
||||
@SerialName("user_id")
|
||||
val userID: Double,
|
||||
val userId: Double,
|
||||
|
||||
/**
|
||||
* 会员等级
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.account
|
||||
package top.r3944realms.ltdmanager.napcat.event.account
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
|
|
@ -7,7 +7,7 @@ import kotlinx.serialization.json.Json
|
|||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import kotlinx.serialization.modules.subclass
|
||||
import top.r3944realms.ltdmanager.napcat.events.NapCatEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.NapCatEvent
|
||||
|
||||
/**
|
||||
* QQ 文件相关响应抽象
|
||||
|
|
@ -62,7 +62,7 @@ abstract class AbstractFileEvent(
|
|||
val downloadTimes: Double,
|
||||
|
||||
@SerialName("file_id")
|
||||
val fileID: String,
|
||||
val fileId: String,
|
||||
|
||||
@SerialName("file_name")
|
||||
val fileName: String,
|
||||
|
|
@ -71,7 +71,7 @@ abstract class AbstractFileEvent(
|
|||
val fileSize: Double,
|
||||
|
||||
@SerialName("group_id")
|
||||
val groupID: Double,
|
||||
val groupId: Double,
|
||||
|
||||
@SerialName("modify_time")
|
||||
val modifyTime: Double,
|
||||
|
|
@ -121,7 +121,7 @@ abstract class AbstractFileEvent(
|
|||
val folderName: String,
|
||||
|
||||
@SerialName("group_id")
|
||||
val groupID: Double,
|
||||
val groupId: Double,
|
||||
|
||||
/**
|
||||
* 文件数量
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import top.r3944realms.ltdmanager.napcat.events.file.GetGroupRootFilesEvent.FileData
|
||||
import top.r3944realms.ltdmanager.napcat.event.file.GetGroupRootFilesEvent.FileData
|
||||
|
||||
/**
|
||||
* GetGroupFilesByFolder事件
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.file
|
||||
package top.r3944realms.ltdmanager.napcat.event.file
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -6,7 +6,7 @@ import kotlinx.serialization.json.Json
|
|||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import kotlinx.serialization.modules.subclass
|
||||
import top.r3944realms.ltdmanager.napcat.events.NapCatEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.NapCatEvent
|
||||
|
||||
/**
|
||||
* QQ 群聊相关响应抽象
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -30,7 +30,7 @@ data class GetGroupDetailInfoEvent(
|
|||
val groupAllShut: Double,
|
||||
|
||||
@SerialName("group_id")
|
||||
val groupID: Double,
|
||||
val groupId: Double,
|
||||
|
||||
@SerialName("group_name")
|
||||
val groupName: String,
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -39,7 +39,7 @@ data class GetGroupHonorInfoEvent(
|
|||
val emotionList: List<GroupHonorInfo>,
|
||||
|
||||
@SerialName("group_id")
|
||||
val groupID: String,
|
||||
val groupId: String,
|
||||
|
||||
/**
|
||||
* 龙王
|
||||
|
|
@ -83,7 +83,7 @@ data class GetGroupHonorInfoEvent(
|
|||
val nickname: String? = null,
|
||||
|
||||
@SerialName("user_id")
|
||||
val userID: Double? = null
|
||||
val userId: Double? = null
|
||||
)
|
||||
override fun subtype(): String {
|
||||
return "get_group_honor_info"
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import top.r3944realms.ltdmanager.napcat.request.group.GetGroupIgnoredNotifiesRequest
|
||||
import top.r3944realms.ltdmanager.napcat.request.group.GetGroupSystemMsgRequest
|
||||
|
||||
/**
|
||||
* GetGroupIgnoredNotifies事件
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -38,11 +38,11 @@ data class GetGroupSystemMsgEvent(
|
|||
*/
|
||||
@Serializable
|
||||
data class SystemInfo (
|
||||
val actor: Double,
|
||||
val actor: Long,
|
||||
val checked: Boolean,
|
||||
|
||||
@SerialName("group_id")
|
||||
val groupID: Double,
|
||||
val groupId: Long,
|
||||
|
||||
@SerialName("group_name")
|
||||
val groupName: String,
|
||||
|
|
@ -51,12 +51,12 @@ data class GetGroupSystemMsgEvent(
|
|||
val invitorNick: String,
|
||||
|
||||
@SerialName("invitor_uin")
|
||||
val invitorUin: Double,
|
||||
val invitorUin: Long,
|
||||
|
||||
val message: String,
|
||||
|
||||
@SerialName("request_id")
|
||||
val requestID: Double,
|
||||
val requestId: Long,
|
||||
|
||||
@SerialName("requester_nick")
|
||||
val requesterNick: String
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import top.r3944realms.ltdmanager.napcat.events.NapCatEvent
|
||||
import top.r3944realms.ltdmanager.napcat.event.NapCatEvent
|
||||
|
||||
/**
|
||||
* SendGroupSign事件
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.events.group
|
||||
package top.r3944realms.ltdmanager.napcat.event.group
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user