fix: 小修改
This commit is contained in:
parent
a0f5504404
commit
e45f2f6272
|
|
@ -1,4 +1,3 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ASMIdeaPluginConfiguration">
|
<component name="ASMIdeaPluginConfiguration">
|
||||||
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
|
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,13 @@ repositories {
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://maven.aliyun.com/repository/gradle-plugin")
|
url = uri("https://maven.aliyun.com/repository/gradle-plugin")
|
||||||
}
|
}
|
||||||
|
maven {
|
||||||
|
url = uri("https://libraries.minecraft.net/")
|
||||||
|
}
|
||||||
|
// 第三方 repo,比如 MohistMC 或 GlareMasters Pub
|
||||||
|
maven {
|
||||||
|
url = uri("https://repo.glaremasters.me/repository/public/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//TODO: 0872d1c0-829c-e1d7-6782-89e45c8a6b76
|
//TODO: 0872d1c0-829c-e1d7-6782-89e45c8a6b76
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
@ -37,6 +44,10 @@ repositories {
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.3") // 推荐使用kotlinx.serialization替代Gson
|
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.3") // 推荐使用kotlinx.serialization替代Gson
|
||||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.12")
|
implementation("io.ktor:ktor-client-content-negotiation:2.3.12")
|
||||||
|
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
// 如果需要日志拦截器(推荐用于调试)
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
|
||||||
|
|
||||||
// 数据库相关
|
// 数据库相关
|
||||||
implementation("org.jetbrains.exposed:exposed-core:0.41.1")
|
implementation("org.jetbrains.exposed:exposed-core:0.41.1")
|
||||||
implementation("org.jetbrains.exposed:exposed-jdbc:0.41.1")
|
implementation("org.jetbrains.exposed:exposed-jdbc:0.41.1")
|
||||||
|
|
@ -71,6 +82,9 @@ repositories {
|
||||||
//生成 二维码
|
//生成 二维码
|
||||||
implementation("com.google.zxing:core:[3.5.3,)")
|
implementation("com.google.zxing:core:[3.5.3,)")
|
||||||
|
|
||||||
|
//命令解析
|
||||||
|
implementation("com.mojang:brigadier:1.2.9")
|
||||||
|
|
||||||
// 测试
|
// 测试
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
testImplementation("io.ktor:ktor-client-mock:2.3.3")
|
testImplementation("io.ktor:ktor-client-mock:2.3.3")
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,5 @@ org.gradle.downloadSources=false
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.degree_of_parallelism=16
|
org.gradle.degree_of_parallelism=16
|
||||||
project_group=top.r3944realms.ltdmanager
|
project_group=top.r3944realms.ltdmanager
|
||||||
project_version=1.10-SNAPSHOT
|
project_version=1.14-SNAPSHOT
|
||||||
dg_lab_version=4.2.11.18
|
dg_lab_version=4.3.13.18
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -2,6 +2,7 @@ package top.r3944realms.ltdmanager
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import top.r3944realms.ltdmanager.blessingskin.BlessingSkinClient
|
import top.r3944realms.ltdmanager.blessingskin.BlessingSkinClient
|
||||||
|
import top.r3944realms.ltdmanager.chevereto.CheveretoClient
|
||||||
import top.r3944realms.ltdmanager.core.mysql.MysqlHikariConnectPool
|
import top.r3944realms.ltdmanager.core.mysql.MysqlHikariConnectPool
|
||||||
import top.r3944realms.ltdmanager.mcserver.McSrvStatusClient
|
import top.r3944realms.ltdmanager.mcserver.McSrvStatusClient
|
||||||
import top.r3944realms.ltdmanager.module.ModuleManager
|
import top.r3944realms.ltdmanager.module.ModuleManager
|
||||||
|
|
@ -29,6 +30,9 @@ object GlobalManager {
|
||||||
val blessingSkinClient: BlessingSkinClient by lazy {
|
val blessingSkinClient: BlessingSkinClient by lazy {
|
||||||
BlessingSkinClient.create()
|
BlessingSkinClient.create()
|
||||||
}
|
}
|
||||||
|
val cheveretoClient: CheveretoClient by lazy {
|
||||||
|
CheveretoClient.create()
|
||||||
|
}
|
||||||
|
|
||||||
val moduleManager: ModuleManager by lazy { ModuleManager() }
|
val moduleManager: ModuleManager by lazy { ModuleManager() }
|
||||||
|
|
||||||
|
|
@ -67,7 +71,8 @@ object GlobalManager {
|
||||||
"NapCatClient" to { napCatClient.close() },
|
"NapCatClient" to { napCatClient.close() },
|
||||||
"McSrvStatusClient" to { mcSrvStatusClient.close() },
|
"McSrvStatusClient" to { mcSrvStatusClient.close() },
|
||||||
"BlessingSkinClient" to { blessingSkinClient.close() },
|
"BlessingSkinClient" to { blessingSkinClient.close() },
|
||||||
"Hikari 数据源" to { dataSource.close() }
|
"Hikari 数据源" to { dataSource.close() },
|
||||||
|
"CheveretoClient" to { cheveretoClient.close() }
|
||||||
)
|
)
|
||||||
|
|
||||||
resources.forEach { (name, closer) ->
|
resources.forEach { (name, closer) ->
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ class BlessingSkinClient private constructor() : AutoCloseable {
|
||||||
*/
|
*/
|
||||||
private suspend fun processQueueItem(item: BlessingSkinQueueItem<BlessingSkinResponse, FailedBlessingSkinResponse>) {
|
private suspend fun processQueueItem(item: BlessingSkinQueueItem<BlessingSkinResponse, FailedBlessingSkinResponse>) {
|
||||||
semaphore.withPermit {
|
semaphore.withPermit {
|
||||||
val (request, deferred, _, maxRetries, expectsResponse) = item
|
val (request, deferred, _, maxRetries, _) = item
|
||||||
var attempt = 0
|
var attempt = 0
|
||||||
var lastError: Exception? = null
|
var lastError: Exception? = null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import io.ktor.client.engine.cio.*
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.forms.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
@ -13,95 +14,223 @@ import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import top.r3944realms.ltdmanager.chevereto.data.CheveretoResponse
|
||||||
|
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayDeque
|
||||||
|
|
||||||
|
|
||||||
object CheveretoUploader {
|
class CheveretoClient private constructor() : Closeable {
|
||||||
|
|
||||||
private val client = HttpClient(CIO) {
|
private val client = HttpClient(CIO) {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(Json {
|
json(Json { ignoreUnknownKeys = true })
|
||||||
ignoreUnknownKeys = true
|
}
|
||||||
})
|
}
|
||||||
|
private val imgTuConfig = YamlConfigLoader.loadTuImgConfig()
|
||||||
|
private val apiUrl = imgTuConfig.url!!
|
||||||
|
private val apiKey = imgTuConfig.decryptedPassword!!
|
||||||
|
// 限流,同时最多 3 个上传
|
||||||
|
private val semaphore = Semaphore(3)
|
||||||
|
|
||||||
|
// 普通队列 (按 priority 排序)
|
||||||
|
private val queue = PriorityQueue<CheveretoQueueItem<CheveretoResponse>>(compareBy { it.priority })
|
||||||
|
private val queueMutex = Mutex()
|
||||||
|
|
||||||
|
// 紧急队列 (FIFO,最多 10 个)
|
||||||
|
private val urgentQueue = ArrayDeque<CheveretoQueueItem<CheveretoResponse>>(10)
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
|
init {
|
||||||
|
scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
val item = queueMutex.withLock {
|
||||||
|
when {
|
||||||
|
urgentQueue.isNotEmpty() -> urgentQueue.removeFirst()
|
||||||
|
queue.isNotEmpty() -> queue.poll()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item != null) processItem(item)
|
||||||
|
else delay(20)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传本地文件
|
* 上传 File
|
||||||
*/
|
*/
|
||||||
suspend fun uploadFile(
|
suspend fun uploadFile(
|
||||||
apiUrl: String,
|
|
||||||
apiKey: String,
|
|
||||||
file: File,
|
file: File,
|
||||||
title: String? = null,
|
title: String? = null,
|
||||||
description: String? = null
|
description: String? = null,
|
||||||
|
tags: String? = null,
|
||||||
|
albumId: String? = null,
|
||||||
|
categoryId: String? = null,
|
||||||
|
width: Int? = null,
|
||||||
|
expiration: String? = null,
|
||||||
|
nsfw: Int? = null,
|
||||||
|
format: String = "json",
|
||||||
|
useFileDate: Int? = null,
|
||||||
|
priority: Int = 5
|
||||||
): CheveretoResponse {
|
): CheveretoResponse {
|
||||||
return client.submitFormWithBinaryData(
|
val deferred = CompletableDeferred<CheveretoResponse>()
|
||||||
url = apiUrl,
|
val source = suspend {
|
||||||
formData = formData {
|
safeUpload {
|
||||||
append("source", file.readBytes(), Headers.build {
|
submitFormWithBinaryData(
|
||||||
append(HttpHeaders.ContentDisposition, "form-data; name=\"source\"; filename=\"${file.name}\"")
|
url = apiUrl,
|
||||||
})
|
formData = formData {
|
||||||
append("format", "json")
|
append("source", file.readBytes(), Headers.build {
|
||||||
title?.let { append("title", it) }
|
append(HttpHeaders.ContentDisposition, "form-data; name=\"source\"; filename=\"${file.name}\"")
|
||||||
description?.let { append("description", it) }
|
})
|
||||||
|
append("format", format)
|
||||||
|
title?.let { append("title", it) }
|
||||||
|
description?.let { append("description", it) }
|
||||||
|
tags?.let { append("tags", it) }
|
||||||
|
albumId?.let { append("album_id", it) }
|
||||||
|
categoryId?.let { append("category_id", it) }
|
||||||
|
width?.let { append("width", it.toString()) }
|
||||||
|
expiration?.let { append("expiration", it) }
|
||||||
|
nsfw?.let { append("nsfw", it.toString()) }
|
||||||
|
useFileDate?.let { append("use_file_date", it.toString()) }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
header("X-API-Key", apiKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
) {
|
}
|
||||||
headers {
|
queueMutex.withLock { queue.add(CheveretoQueueItem(source, deferred, priority)) }
|
||||||
append("X-API-Key", apiKey)
|
return deferred.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传 ByteArrayInputStream
|
||||||
|
*/
|
||||||
|
suspend fun uploadStream(
|
||||||
|
inputStream: ByteArrayInputStream,
|
||||||
|
fileName: String,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
tags: String? = null,
|
||||||
|
albumId: String? = null,
|
||||||
|
categoryId: String? = null,
|
||||||
|
width: Int? = null,
|
||||||
|
expiration: String? = null,
|
||||||
|
nsfw: Int? = null,
|
||||||
|
format: String = "json",
|
||||||
|
useFileDate: Int? = null,
|
||||||
|
priority: Int = 5
|
||||||
|
): CheveretoResponse {
|
||||||
|
val deferred = CompletableDeferred<CheveretoResponse>()
|
||||||
|
val source = suspend {
|
||||||
|
val bytes = inputStream.readBytes()
|
||||||
|
safeUpload {
|
||||||
|
submitFormWithBinaryData(
|
||||||
|
url = apiUrl,
|
||||||
|
formData = formData {
|
||||||
|
append("source", bytes, Headers.build {
|
||||||
|
append(HttpHeaders.ContentDisposition, "form-data; name=\"source\"; filename=\"$fileName\"")
|
||||||
|
})
|
||||||
|
append("format", format)
|
||||||
|
title?.let { append("title", it) }
|
||||||
|
description?.let { append("description", it) }
|
||||||
|
tags?.let { append("tags", it) }
|
||||||
|
albumId?.let { append("album_id", it) }
|
||||||
|
categoryId?.let { append("category_id", it) }
|
||||||
|
width?.let { append("width", it.toString()) }
|
||||||
|
expiration?.let { append("expiration", it) }
|
||||||
|
nsfw?.let { append("nsfw", it.toString()) }
|
||||||
|
useFileDate?.let { append("use_file_date", it.toString()) }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
header("X-API-Key", apiKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.body()
|
}
|
||||||
|
queueMutex.withLock { queue.add(CheveretoQueueItem(source, deferred, priority)) }
|
||||||
|
return deferred.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传网络图片 URL
|
* 上传网络图片 URL
|
||||||
*/
|
*/
|
||||||
suspend fun uploadFromUrl(
|
suspend fun uploadUrl(
|
||||||
apiUrl: String,
|
url: String,
|
||||||
apiKey: String,
|
title: String? = null,
|
||||||
imageUrl: String
|
description: String? = null,
|
||||||
|
tags: String? = null,
|
||||||
|
albumId: String? = null,
|
||||||
|
categoryId: String? = null,
|
||||||
|
width: Int? = null,
|
||||||
|
expiration: String? = null,
|
||||||
|
nsfw: Int? = null,
|
||||||
|
format: String = "json",
|
||||||
|
useFileDate: Int? = null,
|
||||||
|
priority: Int = 5
|
||||||
): CheveretoResponse {
|
): CheveretoResponse {
|
||||||
return client.submitForm(
|
val deferred = CompletableDeferred<CheveretoResponse>()
|
||||||
url = apiUrl,
|
val source = suspend {
|
||||||
formParameters = Parameters.build {
|
safeUpload {
|
||||||
append("source", imageUrl)
|
submitForm(
|
||||||
append("format", "json")
|
url = apiUrl,
|
||||||
|
formParameters = Parameters.build {
|
||||||
|
append("source", url)
|
||||||
|
append("format", format)
|
||||||
|
title?.let { append("title", it) }
|
||||||
|
description?.let { append("description", it) }
|
||||||
|
tags?.let { append("tags", it) }
|
||||||
|
albumId?.let { append("album_id", it) }
|
||||||
|
categoryId?.let { append("category_id", it) }
|
||||||
|
width?.let { append("width", it.toString()) }
|
||||||
|
expiration?.let { append("expiration", it) }
|
||||||
|
nsfw?.let { append("nsfw", it.toString()) }
|
||||||
|
useFileDate?.let { append("use_file_date", it.toString()) }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
header("X-API-Key", apiKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
) {
|
}
|
||||||
headers {
|
queueMutex.withLock { queue.add(CheveretoQueueItem(source, deferred, priority)) }
|
||||||
append("X-API-Key", apiKey)
|
return deferred.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun processItem(item: CheveretoQueueItem<CheveretoResponse>) {
|
||||||
|
semaphore.withPermit {
|
||||||
|
try {
|
||||||
|
val result = item.source()
|
||||||
|
item.deferred.complete(result)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
item.deferred.completeExceptionally(e)
|
||||||
}
|
}
|
||||||
}.body()
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 上传 ByteArrayInputStream
|
* 包装上传,失败时打印原始响应
|
||||||
*/
|
*/
|
||||||
suspend fun uploadFromStream(
|
private suspend fun safeUpload(block: suspend HttpClient.() -> HttpResponse): CheveretoResponse {
|
||||||
apiUrl: String,
|
val response = client.block()
|
||||||
apiKey: String,
|
return try {
|
||||||
inputStream: ByteArrayInputStream,
|
response.body()
|
||||||
fileName: String,
|
} catch (e: Exception) {
|
||||||
title: String? = null,
|
val raw = response.bodyAsText()
|
||||||
description: String? = null
|
throw RuntimeException("Upload failed (status=${response.status}): $raw", e)
|
||||||
): CheveretoResponse {
|
}
|
||||||
val bytes = inputStream.readBytes()
|
}
|
||||||
return client.submitFormWithBinaryData(
|
|
||||||
url = apiUrl,
|
|
||||||
formData = formData {
|
override fun close() {
|
||||||
append("source", bytes, Headers.build {
|
scope.cancel()
|
||||||
append(HttpHeaders.ContentDisposition, "form-data; name=\"source\"; filename=\"$fileName\"")
|
runBlocking { client.close() }
|
||||||
})
|
}
|
||||||
append("format", "json")
|
|
||||||
title?.let { append("title", it) }
|
companion object {
|
||||||
description?.let { append("description", it) }
|
fun create(): CheveretoClient = CheveretoClient()
|
||||||
}
|
|
||||||
) {
|
|
||||||
headers { append("X-API-Key", apiKey) }
|
|
||||||
}.body()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
package top.r3944realms.ltdmanager.chevereto
|
package top.r3944realms.ltdmanager.chevereto
|
||||||
|
|
||||||
class CheveretoQueueItem {
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
}
|
|
||||||
|
data class CheveretoQueueItem<T>(
|
||||||
|
val source: suspend () -> T,
|
||||||
|
val deferred: CompletableDeferred<T>,
|
||||||
|
val priority: Int = 5
|
||||||
|
)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package top.r3944realms.ltdmanager.chevereto
|
package top.r3944realms.ltdmanager.chevereto.data
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -10,5 +11,98 @@ data class CheveretoImage(
|
||||||
val width: Int,
|
val width: Int,
|
||||||
val height: Int,
|
val height: Int,
|
||||||
val date: String,
|
val date: String,
|
||||||
val url: String
|
@SerialName("date_gmt")
|
||||||
)
|
val dateGmt: String,
|
||||||
|
val title: String,
|
||||||
|
val tags: List<String>? = emptyList(),
|
||||||
|
val description: String? = null,
|
||||||
|
val nsfw: Int,
|
||||||
|
@SerialName("storage_mode")
|
||||||
|
val storageMode: String,
|
||||||
|
val md5: String,
|
||||||
|
@SerialName("source_md5")
|
||||||
|
val sourceMd5: String? = null,
|
||||||
|
@SerialName("original_filename")
|
||||||
|
val originalFilename: String,
|
||||||
|
@SerialName("original_exifdata")
|
||||||
|
val originalExifdata: String? = null,
|
||||||
|
val views: Int,
|
||||||
|
@SerialName("category_id")
|
||||||
|
val categoryId: String? = null,
|
||||||
|
val chain: Int,
|
||||||
|
@SerialName("thumb_size")
|
||||||
|
val thumbSize: Int,
|
||||||
|
@SerialName("medium_size")
|
||||||
|
val mediumSize: Int,
|
||||||
|
@SerialName("frame_size")
|
||||||
|
val frameSize: Int? = null,
|
||||||
|
@SerialName("expiration_date_gmt")
|
||||||
|
val expirationDateGmt: String? = null,
|
||||||
|
val likes: Int,
|
||||||
|
@SerialName("is_animated")
|
||||||
|
val isAnimated: Int,
|
||||||
|
@SerialName("is_approved")
|
||||||
|
val isApproved: Int,
|
||||||
|
@SerialName("is_360")
|
||||||
|
val is360: Int,
|
||||||
|
val duration: Int? = null,
|
||||||
|
val type: String? = null,
|
||||||
|
@SerialName("tags_string")
|
||||||
|
val tagsString: String? = null,
|
||||||
|
val file: File? = null,
|
||||||
|
@SerialName("id_encoded")
|
||||||
|
val idEncoded: String,
|
||||||
|
val filename: String,
|
||||||
|
val mime: String,
|
||||||
|
val url: String,
|
||||||
|
val ratio: Double? = null,
|
||||||
|
@SerialName("size_formatted")
|
||||||
|
val sizeFormatted: String,
|
||||||
|
val frame: ImageThumb? = null,
|
||||||
|
val image: ImageFile,
|
||||||
|
val thumb: ImageThumb,
|
||||||
|
@SerialName("url_frame")
|
||||||
|
val urlFrame: String? = null,
|
||||||
|
val medium: Medium? = null,
|
||||||
|
@SerialName("duration_time")
|
||||||
|
val durationTime: String? = null,
|
||||||
|
@SerialName("url_viewer")
|
||||||
|
val urlViewer: String,
|
||||||
|
@SerialName("path_viewer")
|
||||||
|
val pathViewer: String? = null,
|
||||||
|
@SerialName("url_short")
|
||||||
|
val urlShort: String,
|
||||||
|
@SerialName("display_url")
|
||||||
|
val displayUrl: String,
|
||||||
|
@SerialName("display_width")
|
||||||
|
val displayWidth: Int,
|
||||||
|
@SerialName("display_height")
|
||||||
|
val displayHeight: Int,
|
||||||
|
@SerialName("views_label")
|
||||||
|
val viewsLabel: String,
|
||||||
|
@SerialName("likes_label")
|
||||||
|
val likesLabel: String,
|
||||||
|
@SerialName("how_long_ago")
|
||||||
|
val howLongAgo: String,
|
||||||
|
@SerialName("date_fixed_peer")
|
||||||
|
val dateFixedPeer: String,
|
||||||
|
@SerialName("title_truncated")
|
||||||
|
val titleTruncated: String,
|
||||||
|
@SerialName("title_truncated_html")
|
||||||
|
val titleTruncatedHtml: String,
|
||||||
|
@SerialName("is_use_loader")
|
||||||
|
val isUseLoader: Boolean,
|
||||||
|
@SerialName("display_title")
|
||||||
|
val displayTitle: String? = null,
|
||||||
|
@SerialName("delete_url")
|
||||||
|
val deleteUrl: String
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package top.r3944realms.ltdmanager.chevereto
|
package top.r3944realms.ltdmanager.chevereto.data
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
@ -7,6 +7,8 @@ import kotlinx.serialization.Serializable
|
||||||
data class CheveretoResponse(
|
data class CheveretoResponse(
|
||||||
@SerialName("status_code")
|
@SerialName("status_code")
|
||||||
val statusCode: Int,
|
val statusCode: Int,
|
||||||
val success: Map<String, String>? = null,
|
val success: Success? = null,
|
||||||
val image: CheveretoImage? = null
|
val image: CheveretoImage? = null,
|
||||||
|
@SerialName("status_txt")
|
||||||
|
val statusTxt:String ?= null
|
||||||
)
|
)
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
package top.r3944realms.ltdmanager.chevereto.data
|
package top.r3944realms.ltdmanager.chevereto.data
|
||||||
|
|
||||||
class File {
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class File(
|
||||||
|
val resource: Resource
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Resource(
|
||||||
|
val type: String
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
package top.r3944realms.ltdmanager.chevereto.data
|
package top.r3944realms.ltdmanager.chevereto.data
|
||||||
|
|
||||||
class ImageFile {
|
import kotlinx.serialization.Serializable
|
||||||
}
|
|
||||||
|
@Serializable
|
||||||
|
data class ImageFile(
|
||||||
|
val filename: String,
|
||||||
|
val name: String,
|
||||||
|
val mime: String,
|
||||||
|
val extension: String,
|
||||||
|
val url: String,
|
||||||
|
val size: Long
|
||||||
|
)
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
package top.r3944realms.ltdmanager.chevereto.data
|
package top.r3944realms.ltdmanager.chevereto.data
|
||||||
|
|
||||||
data class ImageThumb()
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ImageThumb(
|
||||||
|
val filename: String,
|
||||||
|
val name: String,
|
||||||
|
val mime: String,
|
||||||
|
val extension: String,
|
||||||
|
val url: String,
|
||||||
|
val size: Int
|
||||||
|
)
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
package top.r3944realms.ltdmanager.chevereto.data
|
package top.r3944realms.ltdmanager.chevereto.data
|
||||||
|
|
||||||
class Medium {
|
import kotlinx.serialization.Serializable
|
||||||
}
|
|
||||||
|
@Serializable
|
||||||
|
data class Medium(
|
||||||
|
val filename: String? = null,
|
||||||
|
val name: String? = null,
|
||||||
|
val mime: String? = null,
|
||||||
|
val extension: String? = null,
|
||||||
|
val url: String? = null
|
||||||
|
)
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
package top.r3944realms.ltdmanager.chevereto.data
|
package top.r3944realms.ltdmanager.chevereto.data
|
||||||
|
|
||||||
data class Success()
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Success(
|
||||||
|
val message : String? = null,
|
||||||
|
val code: Int? = 200,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,59 @@
|
||||||
package top.r3944realms.ltdmanager.core.config
|
package top.r3944realms.ltdmanager.core.config
|
||||||
|
|
||||||
class ImgTuConfig {
|
import top.r3944realms.ltdmanager.utils.CryptoUtil
|
||||||
|
import top.r3944realms.ltdmanager.utils.YamlUpdater
|
||||||
|
|
||||||
|
data class ImgTuConfig(
|
||||||
|
var url: String? = null,
|
||||||
|
var encryptedPassword: String? = null
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 获取解密后的Password(如果未加密,返回原值)
|
||||||
|
*/
|
||||||
|
val decryptedPassword: String?
|
||||||
|
get() {
|
||||||
|
if (encryptedPassword == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!isEncrypted()) {
|
||||||
|
return encryptedPassword
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val cipherText = encryptedPassword!!.substring(4, encryptedPassword!!.length - 1)
|
||||||
|
return CryptoUtil.decrypt(cipherText)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException("Password解密失败", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密密码(如果未加密),并返回是否成功加密
|
||||||
|
*/
|
||||||
|
fun encryptPassword() {
|
||||||
|
if (encryptedPassword == null || isEncrypted()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
encryptedPassword = "ENC(${CryptoUtil.encrypt(encryptedPassword!!)})"
|
||||||
|
YamlUpdater.updateYaml(
|
||||||
|
YamlConfigLoader.configFilePath.toString(),
|
||||||
|
"img-tu.encrypted-password",
|
||||||
|
this.encryptedPassword!!
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException("密码加密失败", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查Password是否已加密
|
||||||
|
*/
|
||||||
|
private fun isEncrypted(): Boolean {
|
||||||
|
return encryptedPassword != null &&
|
||||||
|
encryptedPassword!!.startsWith("ENC(") &&
|
||||||
|
encryptedPassword!!.endsWith(")")
|
||||||
|
}
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ImgTuConfig(url=$url, Password=***)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ object YamlConfigLoader {
|
||||||
config?.tools?.rcon?.encryptPassword()
|
config?.tools?.rcon?.encryptPassword()
|
||||||
config?.blessingSkinServer?.invitationApi?.encryptToken()
|
config?.blessingSkinServer?.invitationApi?.encryptToken()
|
||||||
config?.dgLab?.wsServer?.encryptPassword()
|
config?.dgLab?.wsServer?.encryptPassword()
|
||||||
|
config?.imgTu?.encryptPassword()
|
||||||
}
|
}
|
||||||
private fun loadConfig(): ConfigWrapper {
|
private fun loadConfig(): ConfigWrapper {
|
||||||
if (!Files.exists(configFilePath)) {
|
if (!Files.exists(configFilePath)) {
|
||||||
|
|
@ -78,6 +79,7 @@ object YamlConfigLoader {
|
||||||
fun loadMailConfig(): MailConfig = config.mail
|
fun loadMailConfig(): MailConfig = config.mail
|
||||||
fun loadBlessingSkinServerConfig(): BlessingSkinServerConfig = config.blessingSkinServer
|
fun loadBlessingSkinServerConfig(): BlessingSkinServerConfig = config.blessingSkinServer
|
||||||
fun loadDgLabConfig(): DgLabConfig = config.dgLab
|
fun loadDgLabConfig(): DgLabConfig = config.dgLab
|
||||||
|
fun loadTuImgConfig(): ImgTuConfig = config.imgTu
|
||||||
data class ConfigWrapper(
|
data class ConfigWrapper(
|
||||||
var database: DatabaseConfig = DatabaseConfig(),
|
var database: DatabaseConfig = DatabaseConfig(),
|
||||||
var crypto: CryptoConfig = CryptoConfig(),
|
var crypto: CryptoConfig = CryptoConfig(),
|
||||||
|
|
@ -88,6 +90,7 @@ object YamlConfigLoader {
|
||||||
var mail: MailConfig = MailConfig(),
|
var mail: MailConfig = MailConfig(),
|
||||||
var blessingSkinServer: BlessingSkinServerConfig = BlessingSkinServerConfig(),
|
var blessingSkinServer: BlessingSkinServerConfig = BlessingSkinServerConfig(),
|
||||||
var dgLab: DgLabConfig = DgLabConfig(),
|
var dgLab: DgLabConfig = DgLabConfig(),
|
||||||
|
var imgTu: ImgTuConfig = ImgTuConfig(),
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package top.r3944realms.ltdmanager.dglab.manager
|
package top.r3944realms.ltdmanager.dglab
|
||||||
|
|
||||||
|
import com.r3944realms.dg_lab.api.manager.Status
|
||||||
import com.r3944realms.dg_lab.api.operation.ClientOperation
|
import com.r3944realms.dg_lab.api.operation.ClientOperation
|
||||||
import com.r3944realms.dg_lab.api.operation.ServerOperation
|
import com.r3944realms.dg_lab.api.operation.ServerOperation
|
||||||
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketClientRole
|
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketClientRole
|
||||||
|
|
@ -11,25 +12,66 @@ import com.r3944realms.dg_lab.websocket.PowerBoxWSServer
|
||||||
import com.r3944realms.dg_lab.websocket.sharedData.ClientPowerBoxSharedData
|
import com.r3944realms.dg_lab.websocket.sharedData.ClientPowerBoxSharedData
|
||||||
import com.r3944realms.dg_lab.websocket.sharedData.ServerPowerBoxSharedData
|
import com.r3944realms.dg_lab.websocket.sharedData.ServerPowerBoxSharedData
|
||||||
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
||||||
|
import top.r3944realms.ltdmanager.dglab.manager.ClientManager
|
||||||
|
import top.r3944realms.ltdmanager.dglab.manager.ServerManager
|
||||||
|
import top.r3944realms.ltdmanager.dglab.model.game.Player
|
||||||
|
import top.r3944realms.ltdmanager.dglab.model.game.PlayerManager
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局DG_Lab单例管理器
|
* DG_Lab管理器
|
||||||
*/
|
*/
|
||||||
object DgLabManager {
|
class DgLab {
|
||||||
// 可空,延迟初始化
|
// 可空,延迟初始化
|
||||||
var serverManager: ServerManager? = null
|
internal var serverManager: ServerManager? = null
|
||||||
private set
|
get() = field
|
||||||
|
|
||||||
var clientManager: ClientManager? = null
|
internal var clientManager: ClientManager? = null
|
||||||
private set
|
get() = field
|
||||||
|
|
||||||
|
private var playerManager: PlayerManager? = null
|
||||||
|
companion object {
|
||||||
|
const val SERVER_ROLE_NAME = "Se-IC"
|
||||||
|
}
|
||||||
|
fun isSeverOnline(): Boolean = serverManager?.let { it.status == Status.RUNNING } ?: false
|
||||||
|
|
||||||
|
fun isClientOnline(id: String): Boolean = clientManager?.getClient(id)?.let { it.status == Status.RUNNING } ?: false
|
||||||
|
|
||||||
|
fun getPlayerManager(): PlayerManager = playerManager!!
|
||||||
|
|
||||||
|
fun close() {
|
||||||
|
serverManager?.stop()
|
||||||
|
clientManager?.stopAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initOrLoadPlayerManager(idNameMap: Map<Long, String>) {
|
||||||
|
playerManager = PlayerManager(1)
|
||||||
|
val idList = idNameMap.map { id -> id.key }
|
||||||
|
val existingIds = playerManager?.allPlayers()?.map { it.id }?.toSet() ?: emptySet()
|
||||||
|
val targetIds = idList.toSet()
|
||||||
|
|
||||||
|
// 要删除的
|
||||||
|
val toRemove = existingIds - targetIds
|
||||||
|
// 要新增的
|
||||||
|
val toAdd = targetIds - existingIds
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
toRemove.forEach { id ->
|
||||||
|
playerManager?.removePlayer(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
toAdd.forEach { id ->
|
||||||
|
playerManager?.addPlayer(Player(id, idNameMap[id] as String,false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun createServerManager(operation: ServerOperation): DGPBServerManager {
|
fun createServerManager(operation: ServerOperation): DGPBServerManager {
|
||||||
val loadDgLabConfig = YamlConfigLoader.loadDgLabConfig()
|
val loadDgLabConfig = YamlConfigLoader.loadDgLabConfig()
|
||||||
|
|
||||||
val boxWSServer = PowerBoxWSServer.Builder.getBuilder()
|
val boxWSServer = PowerBoxWSServer.Builder.getBuilder()
|
||||||
.port(loadDgLabConfig.wsServer.localServerPort)
|
.port(loadDgLabConfig.wsServer.localServerPort)
|
||||||
.role(WebSocketServerRole("Se-IC"))
|
.role(WebSocketServerRole(SERVER_ROLE_NAME))
|
||||||
.operation(operation)
|
.operation(operation)
|
||||||
.sharedData(ServerPowerBoxSharedData())
|
.sharedData(ServerPowerBoxSharedData())
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -66,7 +108,13 @@ object DgLabManager {
|
||||||
fun removeClient(key: String) {
|
fun removeClient(key: String) {
|
||||||
clientManager?.removeClient(key)
|
clientManager?.removeClient(key)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 获取 服务器管理类
|
||||||
|
*/
|
||||||
|
@Throws(IllegalStateException::class)
|
||||||
|
fun getServer(): DGPBServerManager {
|
||||||
|
return serverManager?.getInstance() ?: throw IllegalStateException("Server is not initialized")
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 获取 客户端管理类
|
* 获取 客户端管理类
|
||||||
*/
|
*/
|
||||||
|
|
@ -86,6 +134,7 @@ object DgLabManager {
|
||||||
.role(WebSocketClientRole("QQ-$key"))
|
.role(WebSocketClientRole("QQ-$key"))
|
||||||
.operation(operation)
|
.operation(operation)
|
||||||
.sharedData(ClientPowerBoxSharedData())
|
.sharedData(ClientPowerBoxSharedData())
|
||||||
|
.useRoleMsgMode(true)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
if (loadDgLabConfig.wsServer.localServerSecure) {
|
if (loadDgLabConfig.wsServer.localServerSecure) {
|
||||||
|
|
|
||||||
|
|
@ -2,60 +2,219 @@ package top.r3944realms.ltdmanager.dglab.model.game
|
||||||
|
|
||||||
import com.r3944realms.dg_lab.api.operation.ClientOperation
|
import com.r3944realms.dg_lab.api.operation.ClientOperation
|
||||||
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData
|
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData
|
||||||
|
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxDataType
|
||||||
|
import com.r3944realms.dg_lab.manager.DGPBClientManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import top.r3944realms.ltdmanager.GlobalManager
|
||||||
|
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.request.other.SendGroupMsgRequest
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.other.SendPrivateMsgRequest
|
||||||
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
import top.r3944realms.ltdmanager.utils.QRCodeUtil
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
|
||||||
|
|
||||||
class GameClientOperation(
|
class GameClientOperation(
|
||||||
val player: Player
|
val napCatClient: NapCatClient,
|
||||||
|
val groupId: Long,
|
||||||
|
val playerManager: PlayerManager,
|
||||||
|
private val playerId: Long
|
||||||
) : ClientOperation {
|
) : ClientOperation {
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
private var qrcode:ByteArrayInputStream? = null;
|
||||||
|
var clientSelf: DGPBClientManager? = null
|
||||||
|
private var hasBinding = false
|
||||||
|
private var bindingTimeoutJob: kotlinx.coroutines.Job? = null // 保存倒计时任务
|
||||||
override fun ClientStartingHandler() {
|
override fun ClientStartingHandler() {
|
||||||
println("Player ${player.id} is starting the client...")
|
LoggerUtil.logger.debug("Player $playerId is starting the client...")
|
||||||
|
scope.launch {
|
||||||
|
napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("DG_LAB客户端启动中...")), ID.long(playerId)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ClientStartedHandler() {
|
override fun ClientStartedHandler() {
|
||||||
println("Player ${player.id} client started successfully.")
|
LoggerUtil.logger.debug("Player $playerId client started successfully.")
|
||||||
|
scope.launch {
|
||||||
|
napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("DG_LAB客户端启动完成!")), ID.long(playerId)))
|
||||||
|
}
|
||||||
|
playerManager.getPlayer(playerId)?.active = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ClientStartingErrorHandler() {
|
override fun ClientStartingErrorHandler(errMsg: String) {
|
||||||
println("Player ${player.id} failed to start client!")
|
LoggerUtil.logger.debug("Player $playerId failed to start client! Reason: $errMsg")
|
||||||
|
scope.launch {
|
||||||
|
napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("DG_LAB客户端启动中遇到错误:$errMsg!")), ID.long(playerId)))
|
||||||
|
}
|
||||||
|
playerManager.getPlayer(playerId)?.active = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ClientStoppingHandler() {
|
override fun ClientStoppingHandler() {
|
||||||
println("Player ${player.id} is stopping the client...")
|
LoggerUtil.logger.debug("Player $playerId is stopping the client...")
|
||||||
|
scope.launch {
|
||||||
|
napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("DG_LAB客户端关闭中...")), ID.long(playerId)))
|
||||||
|
}
|
||||||
|
playerManager.getPlayer(playerId)?.active = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ClientStoppingErrorHandler() {
|
override fun ClientStoppingErrorHandler(errMsg: String) {
|
||||||
println("Player ${player.id} encountered an error while stopping.")
|
LoggerUtil.logger.debug("Player $playerId encountered an error while stopping. Reason: $errMsg")
|
||||||
|
scope.launch {
|
||||||
|
napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("DG_LAB客户端关闭中遇到错误:$errMsg!")), ID.long(playerId)))
|
||||||
|
}
|
||||||
|
playerManager.getPlayer(playerId)?.active = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ClientStoppedHandler() {
|
override fun ClientStoppedHandler() {
|
||||||
println("Player ${player.id} client stopped.")
|
LoggerUtil.logger.debug("Player $playerId client stopped.")
|
||||||
|
scope.launch {
|
||||||
|
napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("DG_LAB客户端成功关闭!")), ID.long(playerId)))
|
||||||
|
}
|
||||||
|
bindingTimeoutJob?.cancel()
|
||||||
|
playerManager.getPlayer(playerId)?.active = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun QrCodeUrlHandler(p0: String?) {
|
override fun QrCodeUrlHandler(p0: String?) {
|
||||||
println("Player ${player.id} QR code received: $p0")
|
LoggerUtil.logger.debug("Player $playerId QR code received: $p0")
|
||||||
}
|
|
||||||
|
|
||||||
|
if (p0.isNullOrBlank()) {
|
||||||
|
LoggerUtil.logger.warn("二维码 URL 为空,无法生成")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 处理 URL,将 IP 和端口替换为配置文件中的服务器 URL
|
||||||
|
val processedUrl = processQrCodeUrl(p0)
|
||||||
|
|
||||||
|
// 生成二维码文件
|
||||||
|
qrcode = QRCodeUtil.generateQRCode(processedUrl, 300, 300)
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 处理二维码 URL,将整个连接地址替换为配置文件中的服务器 URL
|
||||||
|
*/
|
||||||
|
private fun processQrCodeUrl(originalUrl: String): String {
|
||||||
|
return try {
|
||||||
|
val configUrl = YamlConfigLoader.loadDgLabConfig().wsServer.localServerPublishUrl
|
||||||
|
|
||||||
|
// 使用正则表达式匹配整个 ws:// 或 wss:// 开头的 URL
|
||||||
|
val pattern = Regex("""wss?://[^:/]+(?::\d+)?(/.*)?""")
|
||||||
|
|
||||||
|
pattern.replace(originalUrl) { matchResult ->
|
||||||
|
// 保留原始 URL 中的路径部分(如果有的话)
|
||||||
|
val path = matchResult.groupValues[1] ?: ""
|
||||||
|
"$configUrl$path"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.error("处理二维码 URL 时出错: ${e.message}", e)
|
||||||
|
originalUrl // 如果处理失败,返回原 URL
|
||||||
|
}
|
||||||
|
}
|
||||||
override fun ShowQrCodeHandler() {
|
override fun ShowQrCodeHandler() {
|
||||||
println("Player ${player.id} should display QR code.")
|
LoggerUtil.logger.debug("Display QRCode to $playerId.")
|
||||||
}
|
|
||||||
|
|
||||||
|
if (qrcode == null) {
|
||||||
|
LoggerUtil.logger.warn("没有可用的二维码路径")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
// 上传二维码图片
|
||||||
|
val response = GlobalManager.cheveretoClient.uploadStream(
|
||||||
|
qrcode!!,
|
||||||
|
"$playerId-Qrcode-${System.currentTimeMillis()}.png",
|
||||||
|
"Qrcode-$playerId-${System.currentTimeMillis()}",
|
||||||
|
"5min后将会自动删除",
|
||||||
|
albumId = "BFx",
|
||||||
|
expiration = "PT5M"
|
||||||
|
)
|
||||||
|
if (response.image?.url != null) {
|
||||||
|
// 发送图床 URL 给玩家
|
||||||
|
napCatClient.sendUnit(
|
||||||
|
SendPrivateMsgRequest(
|
||||||
|
listOf(
|
||||||
|
MessageElement.text("请在60s内绑定APP,否则将自动断开连接"),
|
||||||
|
MessageElement.image(response.image.url, "二维码")
|
||||||
|
),
|
||||||
|
ID.long(playerId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
LoggerUtil.logger.error("上传二维码返回 JSON 未包含 URL")
|
||||||
|
}
|
||||||
|
// 启动 60 秒倒计时任务
|
||||||
|
bindingTimeoutJob = launch {
|
||||||
|
kotlinx.coroutines.delay(60_000)
|
||||||
|
val player = playerManager.getPlayer(playerId)
|
||||||
|
if (player != null && !hasBinding) {
|
||||||
|
LoggerUtil.logger.warn("Player $playerId 在 60 秒内未绑定,正在停止客户端")
|
||||||
|
napCatClient.sendUnit(
|
||||||
|
SendPrivateMsgRequest(
|
||||||
|
listOf(
|
||||||
|
MessageElement.text("请在60s内未绑定APP,准备停止客户端"),
|
||||||
|
),
|
||||||
|
ID.long(playerId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
clientSelf?.stop()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.error("停止客户端失败: ", e)
|
||||||
|
} finally {
|
||||||
|
player.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
override fun ConnectSuccessfulNoticeHandler() {
|
override fun ConnectSuccessfulNoticeHandler() {
|
||||||
println("Player ${player.id} connected successfully.")
|
LoggerUtil.logger.debug("Player $playerId connected successfully.")
|
||||||
|
bindingTimeoutJob?.cancel()
|
||||||
|
bindingTimeoutJob = null
|
||||||
|
val player = playerManager.getPlayer(playerId)
|
||||||
|
player?.active = true
|
||||||
|
scope.launch {
|
||||||
|
napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("恭喜,绑定成功")), ID.long(playerId)))
|
||||||
|
napCatClient.sendUnit(SendGroupMsgRequest(listOf(MessageElement.text("$playerId 加入战局")), ID.long(groupId)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun DisconnectHandler(p0: PowerBoxData?) {
|
override fun DisconnectHandler(p0: PowerBoxData?) {
|
||||||
println("Player ${player.id} disconnected: $p0")
|
LoggerUtil.logger.debug("Player {} disconnected: {}", playerId, p0)
|
||||||
|
scope.launch {
|
||||||
|
napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("连接断开, $p0")), ID.long(playerId)))
|
||||||
|
napCatClient.sendUnit(SendGroupMsgRequest(listOf(MessageElement.text("$playerId 离开战局")), ID.long(groupId)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ErrorHandler(p0: PowerBoxData?) {
|
override fun ErrorHandler(p0: PowerBoxData?) {
|
||||||
println("Player ${player.id} error occurred: $p0")
|
LoggerUtil.logger.debug("Player {} error occurred: {}", playerId, p0)
|
||||||
|
scope.launch {
|
||||||
|
if(p0 != null && p0.message.isNotEmpty())
|
||||||
|
napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("遇到错误, $p0")), ID.long(playerId)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun HeartBeatHandler(p0: PowerBoxData?) {
|
override fun HeartBeatHandler(p0: PowerBoxData?) {
|
||||||
println("Heartbeat from player ${player.id}: $p0")
|
// LoggerUtil.logger.debug("Heartbeat from player {}: {}", playerId, p0)
|
||||||
|
// scope.launch {
|
||||||
|
// napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("连接断开, $p0")), ID.long(playerId)))
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun OtherMessageHandler(p0: PowerBoxData?) {
|
override fun OtherMessageHandler(p0: PowerBoxData?) {
|
||||||
println("Other message for player ${player.id}: $p0")
|
// LoggerUtil.logger.debug("Other message for player {}: {}", playerId, p0)
|
||||||
|
// scope.launch {
|
||||||
|
// napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("连接断开, $p0")), ID.long(playerId)))
|
||||||
|
// }
|
||||||
|
when (p0?.commandType) {
|
||||||
|
PowerBoxDataType.STRENGTH -> TODO()
|
||||||
|
PowerBoxDataType.PULSE -> TODO()
|
||||||
|
PowerBoxDataType.CLEAR -> TODO()
|
||||||
|
PowerBoxDataType.FEEDBACK -> TODO()
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,84 @@
|
||||||
package top.r3944realms.ltdmanager.dglab.model.game
|
package top.r3944realms.ltdmanager.dglab.model.game
|
||||||
|
|
||||||
|
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage
|
||||||
|
import com.r3944realms.dg_lab.api.websocket.message.role.PlaceholderRole
|
||||||
|
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketServerRole
|
||||||
import com.r3944realms.dg_lab.websocket.handler.server.DefaultServerOperation
|
import com.r3944realms.dg_lab.websocket.handler.server.DefaultServerOperation
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import top.r3944realms.ltdmanager.dglab.DgLab
|
||||||
|
import top.r3944realms.ltdmanager.dglab.manager.ServerManager
|
||||||
|
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.request.other.SendGroupMsgRequest
|
||||||
|
|
||||||
class GameServerOperation : DefaultServerOperation()
|
class GameServerOperation(private val msgClient: NapCatClient, val groupId: Long) : DefaultServerOperation() {
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
var serverManager: ServerManager? = null
|
||||||
|
override fun ServerStartingHandler() {
|
||||||
|
scope.launch {
|
||||||
|
msgClient.sendUnit(
|
||||||
|
SendGroupMsgRequest(listOf(MessageElement.text("服务器启动中...")), ID.long(groupId))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ServerStartedHandler() {
|
||||||
|
scope.launch {
|
||||||
|
msgClient.sendUnit(
|
||||||
|
SendGroupMsgRequest(listOf(MessageElement.text("服务器已启动")), ID.long(groupId))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun ServerStoppingHandler() {
|
||||||
|
scope.launch {
|
||||||
|
msgClient.sendUnit(
|
||||||
|
SendGroupMsgRequest(listOf(MessageElement.text("服务器关闭中...")), ID.long(groupId))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ServerStoppedHandler() {
|
||||||
|
scope.launch {
|
||||||
|
msgClient.sendUnit(
|
||||||
|
SendGroupMsgRequest(listOf(MessageElement.text("服务器已关闭")), ID.long(groupId))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ServerStoppingErrorHandler(errMsg: String) {
|
||||||
|
scope.launch {
|
||||||
|
msgClient.sendUnit(
|
||||||
|
SendGroupMsgRequest(listOf(MessageElement.text("服务器关闭过程中遇到错误: $errMsg")), ID.long(groupId))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ServerStartingErrorHandler(errMsg: String?) {
|
||||||
|
scope.launch {
|
||||||
|
msgClient.sendUnit(
|
||||||
|
SendGroupMsgRequest(listOf(MessageElement.text("服务器开启过程中遇到错误: $errMsg")), ID.long(groupId))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ClientSessionBuildInHandler(clientId: String?) {
|
||||||
|
scope.launch{
|
||||||
|
delay(1000)
|
||||||
|
serverManager?.getInstance()?.send(
|
||||||
|
clientId,
|
||||||
|
PowerBoxMessage.createPowerBoxMessage(
|
||||||
|
"bind",
|
||||||
|
clientId,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
WebSocketServerRole(DgLab.SERVER_ROLE_NAME),
|
||||||
|
PlaceholderRole("Temp-$clientId")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
package top.r3944realms.ltdmanager.dglab.model.game
|
package top.r3944realms.ltdmanager.dglab.model.game
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 玩家类,目前仅包含一个 ID
|
* 玩家类
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
data class Player(
|
data class Player(
|
||||||
val id: String
|
val id: Long,
|
||||||
|
var name: String,
|
||||||
|
var active: Boolean,
|
||||||
)
|
)
|
||||||
|
|
@ -1,4 +1,73 @@
|
||||||
package top.r3944realms.ltdmanager.dglab.model.game
|
package top.r3944realms.ltdmanager.dglab.model.game
|
||||||
|
|
||||||
class PlayerManager {
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import top.r3944realms.ltdmanager.module.PersistentState
|
||||||
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
class PlayerManager(id: Long): PersistentState<PlayerManager.PlayerState> {
|
||||||
|
@Contextual
|
||||||
|
private val map = ConcurrentHashMap<Long, Player>()
|
||||||
|
@Transient
|
||||||
|
private val stateFile: File = getStateFileInternal("dglab_player_data.json", "dglab$id")
|
||||||
|
@Transient
|
||||||
|
private val stateBackupFile: File = getStateFileInternal("dglab_player_data.json.bak","dglab$id")
|
||||||
|
override fun getStateFileInternal(): File = stateFile
|
||||||
|
|
||||||
|
private var playerState = loadState()
|
||||||
|
@Serializable
|
||||||
|
data class PlayerState(
|
||||||
|
val map: Map<Long, Player> = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getState(): PlayerState = playerState
|
||||||
|
/** 添加或更新玩家 */
|
||||||
|
fun addPlayer(player: Player) {
|
||||||
|
map[player.id] = player
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据 ID 获取玩家 */
|
||||||
|
fun getPlayer(id: Long): Player? = map[id]
|
||||||
|
|
||||||
|
/** 删除玩家 */
|
||||||
|
fun removePlayer(id: Long): Player? = map.remove(id)
|
||||||
|
|
||||||
|
/** 判断是否存在玩家 */
|
||||||
|
fun contains(id: Long): Boolean = map.containsKey(id)
|
||||||
|
|
||||||
|
/** 获取所有玩家 */
|
||||||
|
fun allPlayers(): List<Player> = map.values.toList()
|
||||||
|
|
||||||
|
/** 获取所有在线玩家的数量 */
|
||||||
|
fun getOnlinePlayerSize(): Int = map.values.filter { it.active }.size
|
||||||
|
|
||||||
|
|
||||||
|
override fun saveState(state: PlayerState) {
|
||||||
|
try {
|
||||||
|
if (stateFile.exists()) stateFile.copyTo(stateBackupFile, overwrite = true)
|
||||||
|
stateFile.writeText(Json.encodeToString(state))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.error("[dglab] 保存玩家数据&状态失败", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadState(): PlayerState {
|
||||||
|
return try {
|
||||||
|
val fileToRead = when {
|
||||||
|
stateFile.exists() -> stateFile
|
||||||
|
stateBackupFile.exists() -> stateBackupFile
|
||||||
|
else -> null
|
||||||
|
} ?: return PlayerState()
|
||||||
|
|
||||||
|
Json.decodeFromString<PlayerState>(fileToRead.readText())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.warn("[dglab] 读取玩家数据&状态失败", e)
|
||||||
|
PlayerState()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,14 +3,46 @@ package top.r3944realms.ltdmanager.dglab.model.pulseware
|
||||||
import com.r3944realms.dg_lab.api.message.data.PulseWave
|
import com.r3944realms.dg_lab.api.message.data.PulseWave
|
||||||
import com.r3944realms.dg_lab.api.message.data.PulseWaveList
|
import com.r3944realms.dg_lab.api.message.data.PulseWaveList
|
||||||
|
|
||||||
|
|
||||||
object CustomPulseDataConverter {
|
object CustomPulseDataConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将频率转换为 Dg-Lab 格式
|
||||||
|
*
|
||||||
|
* @param frequency 频率值
|
||||||
|
* @return Dg-Lab 格式的数字
|
||||||
|
*/
|
||||||
|
private fun convertFrequency(frequency: Int): Int {
|
||||||
|
return when {
|
||||||
|
frequency <= 10 -> 10
|
||||||
|
frequency <= 100 -> frequency
|
||||||
|
frequency <= 600 -> (frequency - 100) / 5 + 100
|
||||||
|
frequency <= 1000 -> (frequency - 600) / 10 + 200
|
||||||
|
else -> 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将频率数组转换为 Dg-Lab 格式
|
||||||
|
*
|
||||||
|
* @param frequencies 频率数组
|
||||||
|
* @return 转换后的频率数组
|
||||||
|
*/
|
||||||
|
private fun convertFrequencies(frequencies: IntArray): IntArray {
|
||||||
|
return IntArray(4) { index ->
|
||||||
|
if (index < frequencies.size) {
|
||||||
|
convertFrequency(frequencies[index])
|
||||||
|
} else {
|
||||||
|
10 // 默认值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将自定义波形数据转换为 PulseWaveList
|
* 将自定义波形数据转换为 PulseWaveList
|
||||||
*
|
*
|
||||||
* @param customPulseData Map<String></String>, List<int></int>[][]>>
|
* @param customPulseData Map<String, List<Array<IntArray>>>
|
||||||
* 每个 int[][] 包含两个长度为 4 的 int 数组,第一个是 frequencies,第二个是 strengths
|
* 每个 int[][] 包含两个长度为 4 的 int 数组,第一个是 frequencies,第二个是 strengths
|
||||||
* @return Map<String></String>, PulseWaveList>
|
* @return Map<String, PulseWaveList>
|
||||||
*/
|
*/
|
||||||
fun convert(customPulseData: Map<String, List<Array<IntArray>>>): Map<String, PulseWaveList> {
|
fun convert(customPulseData: Map<String, List<Array<IntArray>>>): Map<String, PulseWaveList> {
|
||||||
val pulseWaveLists: MutableMap<String, PulseWaveList> = HashMap()
|
val pulseWaveLists: MutableMap<String, PulseWaveList> = HashMap()
|
||||||
|
|
@ -26,7 +58,10 @@ object CustomPulseDataConverter {
|
||||||
// 确保每个数组长度为4
|
// 确保每个数组长度为4
|
||||||
require(!(freqs.size != 4 || strengths.size != 4)) { "每个波形段必须包含 4 个频率和 4 个强度值" }
|
require(!(freqs.size != 4 || strengths.size != 4)) { "每个波形段必须包含 4 个频率和 4 个强度值" }
|
||||||
|
|
||||||
val wave = PulseWave.fromArrays(freqs, strengths)
|
// 转换频率为 Dg-Lab 格式
|
||||||
|
val convertedFreqs = convertFrequencies(freqs)
|
||||||
|
|
||||||
|
val wave = PulseWave.fromArrays(convertedFreqs, strengths)
|
||||||
waveList.add(wave)
|
waveList.add(wave)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,12 +70,53 @@ object CustomPulseDataConverter {
|
||||||
|
|
||||||
return pulseWaveLists
|
return pulseWaveLists
|
||||||
}
|
}
|
||||||
fun PulseWave.toSerializable(): PulseWaveSerializable =
|
|
||||||
PulseWaveSerializable(f1(), f2(), f3(), f4(), s1(), s2(), s3(), s4())
|
|
||||||
|
|
||||||
fun PulseWaveSerializable.toPulseWave(): PulseWave =
|
/**
|
||||||
|
* 转换单个 PulseWave 的频率
|
||||||
|
*/
|
||||||
|
private fun convertPulseWaveFrequencies(pulseWave: PulseWave): PulseWave {
|
||||||
|
val freqs = intArrayOf(
|
||||||
|
convertFrequency(pulseWave.f1()),
|
||||||
|
convertFrequency(pulseWave.f2()),
|
||||||
|
convertFrequency(pulseWave.f3()),
|
||||||
|
convertFrequency(pulseWave.f4())
|
||||||
|
)
|
||||||
|
val strengths = intArrayOf(
|
||||||
|
pulseWave.s1(),
|
||||||
|
pulseWave.s2(),
|
||||||
|
pulseWave.s3(),
|
||||||
|
pulseWave.s4()
|
||||||
|
)
|
||||||
|
return PulseWave.fromArrays(freqs, strengths)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换整个 PulseWaveList 的频率
|
||||||
|
*/
|
||||||
|
fun convertPulseWaveListFrequencies(pulseWaveList: PulseWaveList): PulseWaveList {
|
||||||
|
val convertedList = PulseWaveList()
|
||||||
|
convertedList.name = pulseWaveList.name
|
||||||
|
|
||||||
|
for (i in 0 until pulseWaveList.list.size) {
|
||||||
|
val convertedWave = convertPulseWaveFrequencies(pulseWaveList.list[i])
|
||||||
|
convertedList.add(convertedWave)
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertedList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PulseWave.toSerializable(): PulseWaveSerializable =
|
||||||
|
PulseWaveSerializable(
|
||||||
|
convertFrequency(f1()),
|
||||||
|
convertFrequency(f2()),
|
||||||
|
convertFrequency(f3()),
|
||||||
|
convertFrequency(f4()),
|
||||||
|
s1(), s2(), s3(), s4()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun PulseWaveSerializable.toPulseWave(): PulseWave =
|
||||||
PulseWave.fromArrays(
|
PulseWave.fromArrays(
|
||||||
intArrayOf(f1, f2, f3, f4),
|
intArrayOf(convertFrequency(f1), convertFrequency(f2), convertFrequency(f3), convertFrequency(f4)),
|
||||||
intArrayOf(s1, s2, s3, s4)
|
intArrayOf(s1, s2, s3, s4)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,39 @@ import com.r3944realms.dg_lab.api.message.data.PulseWave
|
||||||
import com.r3944realms.dg_lab.api.message.data.PulseWaveList
|
import com.r3944realms.dg_lab.api.message.data.PulseWaveList
|
||||||
|
|
||||||
object DefaultPulseData {
|
object DefaultPulseData {
|
||||||
|
/**
|
||||||
|
* 将频率转换为 Dg-Lab 格式
|
||||||
|
*
|
||||||
|
* @param frequency 频率值
|
||||||
|
* @return Dg-Lab 格式的数字
|
||||||
|
*/
|
||||||
|
private fun convertFrequency(frequency: Int): Int {
|
||||||
|
return when {
|
||||||
|
frequency <= 10 -> 10
|
||||||
|
frequency <= 100 -> frequency
|
||||||
|
frequency <= 600 -> (frequency - 100) / 5 + 100
|
||||||
|
frequency <= 1000 -> (frequency - 600) / 10 + 200
|
||||||
|
else -> 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换频率数组为 Dg-Lab 格式
|
||||||
|
*/
|
||||||
|
private fun convertFrequencies(frequencies: IntArray): IntArray {
|
||||||
|
return IntArray(frequencies.size) { index ->
|
||||||
|
convertFrequency(frequencies[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建经过频率转换的波形段
|
||||||
|
*/
|
||||||
|
private fun createWaveSegment(frequencies: IntArray, strengths: IntArray): PulseWave {
|
||||||
|
val convertedFreqs = convertFrequencies(frequencies)
|
||||||
|
return PulseWave.fromArrays(convertedFreqs, strengths)
|
||||||
|
}
|
||||||
|
|
||||||
fun allPulseWaveLists(): Map<String, PulseWaveList> {
|
fun allPulseWaveLists(): Map<String, PulseWaveList> {
|
||||||
return mapOf(
|
return mapOf(
|
||||||
"呼吸" to Breath,
|
"呼吸" to Breath,
|
||||||
|
|
@ -47,7 +79,7 @@ object DefaultPulseData {
|
||||||
|
|
||||||
// 转成 PulseWave 并加入列表
|
// 转成 PulseWave 并加入列表
|
||||||
for (seg in segments) {
|
for (seg in segments) {
|
||||||
list.add(PulseWave.fromArrays(seg[0], seg[1]))
|
list.add(createWaveSegment(seg[0], seg[1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
list
|
list
|
||||||
|
|
@ -68,7 +100,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(84, 82, 80, 76)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(84, 82, 80, 76)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(68, 68, 68, 68))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(68, 68, 68, 68))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +117,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 1)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 1)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(2, 2, 2, 2))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(2, 2, 2, 2))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
val FastPinch: PulseWaveList by lazy {
|
val FastPinch: PulseWaveList by lazy {
|
||||||
|
|
@ -96,7 +128,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
|
||||||
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0))
|
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
val PinchGradual: PulseWaveList by lazy {
|
val PinchGradual: PulseWaveList by lazy {
|
||||||
|
|
@ -115,7 +147,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,7 +172,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
val Compress: PulseWaveList by lazy {
|
val Compress: PulseWaveList by lazy {
|
||||||
|
|
@ -169,7 +201,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
val RhythmStep: PulseWaveList by lazy {
|
val RhythmStep: PulseWaveList by lazy {
|
||||||
|
|
@ -203,7 +235,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,7 +248,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,7 +263,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)),
|
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)),
|
||||||
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0))
|
arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
val WaveRipple: PulseWaveList by lazy {
|
val WaveRipple: PulseWaveList by lazy {
|
||||||
|
|
@ -246,7 +278,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(50, 50, 50, 50)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(50, 50, 50, 50)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,7 +291,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(80, 90, 100, 100)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(80, 90, 100, 100)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,7 +305,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(20, 20, 20, 20), intArrayOf(50, 50, 50, 50)),
|
arrayOf(intArrayOf(20, 20, 20, 20), intArrayOf(50, 50, 50, 50)),
|
||||||
arrayOf(intArrayOf(15, 15, 15, 15), intArrayOf(0, 0, 0, 0))
|
arrayOf(intArrayOf(15, 15, 15, 15), intArrayOf(0, 0, 0, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
val SignalLight: PulseWaveList by lazy {
|
val SignalLight: PulseWaveList by lazy {
|
||||||
|
|
@ -285,7 +317,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,7 +328,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 30, 60, 100)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 30, 60, 100)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 70, 40, 0))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 70, 40, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,7 +339,7 @@ object DefaultPulseData {
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 50, 100, 100)),
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 50, 100, 100)),
|
||||||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 50, 0, 0))
|
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 50, 0, 0))
|
||||||
)
|
)
|
||||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,37 +1,50 @@
|
||||||
package top.r3944realms.ltdmanager
|
package top.r3944realms.ltdmanager
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
||||||
import top.r3944realms.ltdmanager.module.McServerStatusModule
|
|
||||||
import top.r3944realms.ltdmanager.module.*
|
import top.r3944realms.ltdmanager.module.*
|
||||||
|
|
||||||
|
|
||||||
fun main() = GlobalManager.runBlockingMain {
|
fun main() = GlobalManager.runBlockingMain {
|
||||||
val groupId:Long = 538751386
|
val commonGroupId:Long = 538751386
|
||||||
|
val whitelistGroupId:Long = 920719236
|
||||||
val selfQQId = 3327379836
|
val selfQQId = 3327379836
|
||||||
val selfNickName = "闲趣老土豆"
|
val selfNickName = "闲趣老土豆"
|
||||||
// 创建模块实例
|
// 创建模块实例
|
||||||
val groupModule = GroupRequestHandlerModule(
|
val groupModule = GroupRequestHandlerModule(
|
||||||
moduleName = "WhiteListGroup",
|
moduleName = "WhiteListGroup",
|
||||||
client = GlobalManager.napCatClient,
|
client = GlobalManager.napCatClient,
|
||||||
targetGroupId = groupId
|
targetGroupId = whitelistGroupId
|
||||||
)
|
)
|
||||||
val groupMsgPollingModule = GroupMessagePollingModule(
|
val commonGroupMsgPollingModule = GroupMessagePollingModule(
|
||||||
moduleName = "WhiteListGroup",
|
moduleName = "CommonGroupMsgPolling",
|
||||||
targetGroupId = groupId,
|
targetGroupId = commonGroupId,
|
||||||
pollIntervalMillis = 5_000L,
|
pollIntervalMillis = 5_000L,
|
||||||
msgHistoryCheck = 15
|
msgHistoryCheck = 15
|
||||||
)
|
)
|
||||||
val helpModule = HelpModule(
|
val whiteListGroupMsgPollingModule = GroupMessagePollingModule(
|
||||||
|
moduleName = "WhiteListGroup",
|
||||||
|
targetGroupId = whitelistGroupId,
|
||||||
|
pollIntervalMillis = 5_000L,
|
||||||
|
msgHistoryCheck = 15
|
||||||
|
)
|
||||||
|
val commonHelpModule = HelpModule(
|
||||||
|
moduleName = "CommonGroup",
|
||||||
|
keywords = listOf("help", "帮助"),
|
||||||
|
groupMessagePollingModule = commonGroupMsgPollingModule,
|
||||||
|
selfId = selfQQId,
|
||||||
|
selfNickName = selfNickName,
|
||||||
|
)
|
||||||
|
val whitelistHelpModule = HelpModule(
|
||||||
moduleName = "WhiteListGroup",
|
moduleName = "WhiteListGroup",
|
||||||
keywords = listOf("help", "帮助"),
|
keywords = listOf("help", "帮助"),
|
||||||
groupMessagePollingModule = groupMsgPollingModule,
|
groupMessagePollingModule = whiteListGroupMsgPollingModule,
|
||||||
selfId = selfQQId,
|
selfId = selfQQId,
|
||||||
selfNickName = selfNickName,
|
selfNickName = selfNickName,
|
||||||
)
|
)
|
||||||
val toolConfig = YamlConfigLoader.loadToolConfig()
|
val toolConfig = YamlConfigLoader.loadToolConfig()
|
||||||
val rconModule = RconPlayerListModule(
|
val corconModule = RconPlayerListModule(
|
||||||
moduleName = "WhiteListGroup",
|
moduleName = "WhiteListGroup",
|
||||||
groupMessagePollingModule = groupMsgPollingModule,
|
groupMessagePollingModule = commonGroupMsgPollingModule,
|
||||||
rconTimeOut = 2_000L,
|
rconTimeOut = 2_000L,
|
||||||
cooldownMillis = 10_000L,
|
cooldownMillis = 10_000L,
|
||||||
selfId = selfQQId,
|
selfId = selfQQId,
|
||||||
|
|
@ -46,76 +59,106 @@ fun main() = GlobalManager.runBlockingMain {
|
||||||
"列表","服务器状态", "TPS", "tps", "list", "List"
|
"列表","服务器状态", "TPS", "tps", "list", "List"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val mailConfig = YamlConfigLoader.loadMailConfig()
|
val rconModule = RconPlayerListModule(
|
||||||
val mailModule = MailModule(
|
|
||||||
moduleName = "WhiteListGroup",
|
moduleName = "WhiteListGroup",
|
||||||
host = mailConfig.host.toString(),
|
groupMessagePollingModule = whiteListGroupMsgPollingModule,
|
||||||
authToken = mailConfig.decryptedPassword.toString(),
|
rconTimeOut = 2_000L,
|
||||||
port = mailConfig.port!!,
|
cooldownMillis = 10_000L,
|
||||||
senderEmailAddress = mailConfig.mailAddress!!,
|
|
||||||
)
|
|
||||||
val blessingSkinConfig = YamlConfigLoader.loadBlessingSkinServerConfig()
|
|
||||||
val invitationCodesModule = InvitationCodesModule(
|
|
||||||
moduleName = "WhiteListGroup",
|
|
||||||
groupMessagePollingModule = groupMsgPollingModule,
|
|
||||||
mailModule = mailModule,
|
|
||||||
apiToken = blessingSkinConfig.invitationApi?.decryptedToken!!,
|
|
||||||
selfId = selfQQId,
|
selfId = selfQQId,
|
||||||
|
selfNickName = selfNickName,
|
||||||
|
rconPath = toolConfig.rcon.mcRconToolPath.toString(),
|
||||||
|
rconConfigPath = toolConfig.rcon.mcRconToolConfigPath.toString(),
|
||||||
keywords = setOf(
|
keywords = setOf(
|
||||||
"申请皮肤站注册邀请码",
|
//形容
|
||||||
"申请土豆服务器注册邀请码",
|
"土豆", "马铃薯", "Potato", "potato", "POTATO",
|
||||||
"申请LTD邀请码",
|
"Potatoes", "potatoes", "POTATOES", "🥔",
|
||||||
"Apply for an invitation code"
|
//正经
|
||||||
|
"列表","服务器状态", "TPS", "tps", "list", "List"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val mcServerStatusModule = McServerStatusModule(
|
// val mailConfig = YamlConfigLoader.loadMailConfig()
|
||||||
moduleName = "WhiteListGroup",
|
// val mailModule = MailModule(
|
||||||
groupMessagePollingModule = groupMsgPollingModule,
|
// moduleName = "WhiteListGroup",
|
||||||
|
// host = mailConfig.host.toString(),
|
||||||
|
// authToken = mailConfig.decryptedPassword.toString(),
|
||||||
|
// port = mailConfig.port!!,
|
||||||
|
// senderEmailAddress = mailConfig.mailAddress!!,
|
||||||
|
// )
|
||||||
|
// val blessingSkinConfig = YamlConfigLoader.loadBlessingSkinServerConfig()
|
||||||
|
// val invitationCodesModule = InvitationCodesModule(
|
||||||
|
// moduleName = "WhiteListGroup",
|
||||||
|
// groupMessagePollingModule = commonGroupMsgPollingModule,
|
||||||
|
// mailModule = mailModule,
|
||||||
|
// apiToken = blessingSkinConfig.invitationApi?.decryptedToken!!,
|
||||||
|
// selfId = selfQQId,
|
||||||
|
// keywords = setOf(
|
||||||
|
// "申请皮肤站注册邀请码",
|
||||||
|
// "申请土豆服务器注册邀请码",
|
||||||
|
// "申请LTD邀请码",
|
||||||
|
// "Apply for an invitation code"
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
val commonMcServerStatusModule = McServerStatusModule(
|
||||||
|
moduleName = "CommonGroup",
|
||||||
|
groupMessagePollingModule = commonGroupMsgPollingModule,
|
||||||
selfId = selfQQId,
|
selfId = selfQQId,
|
||||||
cooldownMillis = 20_000L,
|
cooldownMillis = 20_000L,
|
||||||
selfNickName = selfNickName,
|
selfNickName = selfNickName,
|
||||||
commands = listOf("/m", "/mcs", "seek", "s"),
|
commands = listOf("/m", "/mcs", "seek", "s", "test"),
|
||||||
presetServer = mapOf(
|
presetServer = mapOf(
|
||||||
setOf("先行土豆", "先行", "pre", "Pre", "BF", "bf", "p", "P") to "n2.akiracloud.net:10599",
|
setOf("老土豆", "七周目", "7" ,"ZZ", "zz", "Zz", "seven") to "main.mmccdd.top:11106",
|
||||||
setOf("土豆", "老土豆", "七周目", "7" ,"ZZ", "zz", "Zz", "seven") to "main.mmccdd.top:11106",
|
setOf("土豆", "八周目", "8" ,"39", "eight") to "ac.r3944realms.top"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val banModule = BanModule(
|
val whitelistMcServerStatusModule = McServerStatusModule(
|
||||||
moduleName = "WhiteListGroup",
|
moduleName = "WhiteListGroup",
|
||||||
groupMessagePollingModule = groupMsgPollingModule,
|
groupMessagePollingModule = whiteListGroupMsgPollingModule,
|
||||||
selfId = selfQQId,
|
selfId = selfQQId,
|
||||||
adminsId = listOf(1283411677),
|
cooldownMillis = 20_000L,
|
||||||
muteCommandPrefixList = listOf("口球", "mute", "Mute", "禁言"),
|
selfNickName = selfNickName,
|
||||||
unmuteCommandPrefixList = listOf("解禁", "unmute", "Unmute", "解除禁言"),
|
commands = listOf("/m", "/mcs", "seek", "s", "test"),
|
||||||
minBanMinutes = 1,
|
presetServer = mapOf(
|
||||||
maxBanMinutes = 15,
|
setOf("老土豆", "七周目", "7" ,"ZZ", "zz", "Zz", "seven") to "main.mmccdd.top:11106",
|
||||||
|
setOf("土豆", "八周目", "8" ,"39", "eight") to "ac.r3944realms.top"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val dgLabModule = DGLabModule(
|
||||||
|
moduleName = "DG",
|
||||||
|
groupMessagePollingModule = commonGroupMsgPollingModule,
|
||||||
|
selfId = selfQQId,
|
||||||
|
adminIds = listOf(2561098830L),
|
||||||
|
commandHead = listOf("dglab")
|
||||||
)
|
)
|
||||||
// val modGroupHandlerModule = ModGroupHandlerModule(
|
|
||||||
// moduleName = "ModGroup",
|
|
||||||
// targetGroupId = 339340846,
|
|
||||||
// answers = listOf("戏鸢", "一只戏鸢", "折戏鸢", "LostInLinearPast", "lostinlinearpast"),
|
|
||||||
// pollIntervalMillis = 15_000L,
|
|
||||||
// )
|
|
||||||
|
|
||||||
// 注册模块到全局模块管理器
|
// 注册模块到全局模块管理器
|
||||||
GlobalManager.moduleManager.registerModule(groupModule)
|
GlobalManager.moduleManager.registerModule(groupModule)
|
||||||
GlobalManager.moduleManager.registerModule(groupMsgPollingModule)
|
GlobalManager.moduleManager.registerModule(commonGroupMsgPollingModule)
|
||||||
GlobalManager.moduleManager.registerModule(mcServerStatusModule)
|
GlobalManager.moduleManager.registerModule(whiteListGroupMsgPollingModule)
|
||||||
|
GlobalManager.moduleManager.registerModule(commonMcServerStatusModule)
|
||||||
GlobalManager.moduleManager.registerModule(rconModule)
|
GlobalManager.moduleManager.registerModule(rconModule)
|
||||||
GlobalManager.moduleManager.registerModule(mailModule)
|
GlobalManager.moduleManager.registerModule(corconModule)
|
||||||
GlobalManager.moduleManager.registerModule(invitationCodesModule)
|
GlobalManager.moduleManager.registerModule(whitelistMcServerStatusModule)
|
||||||
GlobalManager.moduleManager.registerModule(helpModule)
|
// GlobalManager.moduleManager.registerModule(mailModule)
|
||||||
GlobalManager.moduleManager.registerModule(banModule)
|
// GlobalManager.moduleManager.registerModule(invitationCodesModule)
|
||||||
|
GlobalManager.moduleManager.registerModule(whitelistHelpModule)
|
||||||
|
GlobalManager.moduleManager.registerModule(commonHelpModule)
|
||||||
|
GlobalManager.moduleManager.registerModule(dgLabModule)
|
||||||
|
// GlobalManager.moduleManager.registerModule(banModule)
|
||||||
// GlobalManager.moduleManager.registerModule(modGroupHandlerModule)
|
// GlobalManager.moduleManager.registerModule(modGroupHandlerModule)
|
||||||
|
|
||||||
// 加载模块
|
// 加载模块
|
||||||
GlobalManager.moduleManager.loadModule(groupModule.name)
|
GlobalManager.moduleManager.loadModule(groupModule.name)
|
||||||
GlobalManager.moduleManager.loadModule(groupMsgPollingModule.name)
|
GlobalManager.moduleManager.loadModule(commonGroupMsgPollingModule.name)
|
||||||
GlobalManager.moduleManager.loadModule(mcServerStatusModule.name)
|
GlobalManager.moduleManager.loadModule(whiteListGroupMsgPollingModule.name)
|
||||||
|
GlobalManager.moduleManager.loadModule(commonMcServerStatusModule.name)
|
||||||
|
GlobalManager.moduleManager.loadModule(corconModule.name)
|
||||||
GlobalManager.moduleManager.loadModule(rconModule.name)
|
GlobalManager.moduleManager.loadModule(rconModule.name)
|
||||||
GlobalManager.moduleManager.loadModule(mailModule.name)
|
// GlobalManager.moduleManager.loadModule(mailModule.name)
|
||||||
GlobalManager.moduleManager.loadModule(invitationCodesModule.name)
|
// GlobalManager.moduleManager.loadModule(invitationCodesModule.name)
|
||||||
GlobalManager.moduleManager.loadModule(helpModule.name)
|
GlobalManager.moduleManager.loadModule(commonHelpModule.name)
|
||||||
GlobalManager.moduleManager.loadModule(banModule.name)
|
GlobalManager.moduleManager.loadModule(whitelistMcServerStatusModule.name)
|
||||||
|
GlobalManager.moduleManager.loadModule(whitelistHelpModule.name)
|
||||||
|
GlobalManager.moduleManager.loadModule(dgLabModule.name)
|
||||||
|
// GlobalManager.moduleManager.loadModule(banModule.name)
|
||||||
// GlobalManager.moduleManager.loadModule(modGroupHandlerModule.name)
|
// GlobalManager.moduleManager.loadModule(modGroupHandlerModule.name)
|
||||||
}
|
}
|
||||||
|
|
@ -12,8 +12,8 @@ import top.r3944realms.ltdmanager.module.common.filter.type.NewMessageFilter
|
||||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
import top.r3944realms.ltdmanager.napcat.event.group.GetGroupShutListEvent
|
import top.r3944realms.ltdmanager.napcat.event.group.GetGroupShutListEvent
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
|
||||||
import top.r3944realms.ltdmanager.napcat.request.group.GetGroupShutListRequest
|
import top.r3944realms.ltdmanager.napcat.request.group.GetGroupShutListRequest
|
||||||
import top.r3944realms.ltdmanager.napcat.request.group.SetGroupBanRequest
|
import top.r3944realms.ltdmanager.napcat.request.group.SetGroupBanRequest
|
||||||
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||||
|
|
@ -78,7 +78,7 @@ class BanModule(
|
||||||
scope?.cancel()
|
scope?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleMessages(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>) {
|
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||||
// 先过一遍过滤器,只有符合条件的才进入后续处理
|
// 先过一遍过滤器,只有符合条件的才进入后续处理
|
||||||
val filtered = triggerFilter.filter(messages)
|
val filtered = triggerFilter.filter(messages)
|
||||||
for (msg in filtered) {
|
for (msg in filtered) {
|
||||||
|
|
@ -91,7 +91,7 @@ class BanModule(
|
||||||
* - text 段直接拼接
|
* - text 段直接拼接
|
||||||
* - 如果消息段里包含 @(在 MessageData 中为 qq 字段),则拼成 "@{qq}",方便 parseMentionToUserId 解析
|
* - 如果消息段里包含 @(在 MessageData 中为 qq 字段),则拼成 "@{qq}",方便 parseMentionToUserId 解析
|
||||||
*/
|
*/
|
||||||
private fun GetFriendMsgHistoryEvent.SpecificMsg.plainText(): String {
|
private fun MsgHistorySpecificMsg.plainText(): String {
|
||||||
return this.message.joinToString(" ") { seg ->
|
return this.message.joinToString(" ") { seg ->
|
||||||
// 如果 message element 包含 qq 字段(即@用户),优先使用它
|
// 如果 message element 包含 qq 字段(即@用户),优先使用它
|
||||||
seg.data.qq?.let { "@${it}" } ?: (seg.data.text ?: "")
|
seg.data.qq?.let { "@${it}" } ?: (seg.data.text ?: "")
|
||||||
|
|
@ -100,7 +100,7 @@ class BanModule(
|
||||||
/**
|
/**
|
||||||
* 从消息段中提取所有被 @ 的用户 ID
|
* 从消息段中提取所有被 @ 的用户 ID
|
||||||
*/
|
*/
|
||||||
private fun GetFriendMsgHistoryEvent.SpecificMsg.getMentionedUserIds(): List<ID> {
|
private fun MsgHistorySpecificMsg.getMentionedUserIds(): List<ID> {
|
||||||
return this.message
|
return this.message
|
||||||
.filter { it.type == MessageType.At && it.data.qq != null }
|
.filter { it.type == MessageType.At && it.data.qq != null }
|
||||||
.mapNotNull { it.data.qq }
|
.mapNotNull { it.data.qq }
|
||||||
|
|
@ -111,7 +111,7 @@ class BanModule(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private suspend fun processUnBanCommand(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
|
private suspend fun processUnBanCommand(msg: MsgHistorySpecificMsg) {
|
||||||
try {
|
try {
|
||||||
pardonCommandParse.parseCommand(msg.plainText()) ?: return
|
pardonCommandParse.parseCommand(msg.plainText()) ?: return
|
||||||
// 获取所有被 @ 的用户
|
// 获取所有被 @ 的用户
|
||||||
|
|
@ -149,7 +149,7 @@ class BanModule(
|
||||||
saveState(banState)
|
saveState(banState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private suspend fun processBanCommand(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
|
private suspend fun processBanCommand(msg: MsgHistorySpecificMsg) {
|
||||||
try {
|
try {
|
||||||
val parsed = banCommandParse.parseCommand(msg.plainText()) ?: return
|
val parsed = banCommandParse.parseCommand(msg.plainText()) ?: return
|
||||||
val (_, argument) = parsed
|
val (_, argument) = parsed
|
||||||
|
|
@ -171,9 +171,9 @@ class BanModule(
|
||||||
is ID.LongValue -> target.value
|
is ID.LongValue -> target.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 权限检查:非管理员不能禁言他人
|
// 权限检查:非管理员不能禁言多个他人
|
||||||
if (mentionedUserIds.isNotEmpty() && mentionedUserIds.size != 1 && msg.sender.userId !in adminsId) {
|
if (mentionedUserIds.isNotEmpty() && mentionedUserIds.size != 1 && msg.sender.userId !in adminsId) {
|
||||||
sendGroupMessage("❌ 你没有权限禁言使用禁言多用户功能", msg.realId)
|
sendGroupMessage("❌ 你没有权限使用禁言多用户功能", msg.realId)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,7 +201,7 @@ class BanModule(
|
||||||
}
|
}
|
||||||
|
|
||||||
val selfDuration = durationSeconds * factorX
|
val selfDuration = durationSeconds * factorX
|
||||||
if (Random.nextInt(100) < chance) {
|
if (Random.nextInt(0,100) > chance) {
|
||||||
// 触发反禁自己
|
// 触发反禁自己
|
||||||
banUser(ID.long(msg.sender.userId), groupMessagePollingModule.targetGroupId, selfDuration)
|
banUser(ID.long(msg.sender.userId), groupMessagePollingModule.targetGroupId, selfDuration)
|
||||||
sendGroupMessage(
|
sendGroupMessage(
|
||||||
|
|
@ -262,6 +262,7 @@ class BanModule(
|
||||||
override fun info(): String {
|
override fun info(): String {
|
||||||
return buildString {
|
return buildString {
|
||||||
append("[$name] 指令禁言模块:\n")
|
append("[$name] 指令禁言模块:\n")
|
||||||
|
append(" 管理员用户ID: ${adminsId}\n")
|
||||||
append(" - 用户发送 ${banCommandParse.getCommands().joinToString("、")} 来禁言自己或指定其他用户(需管理员权限)。\n")
|
append(" - 用户发送 ${banCommandParse.getCommands().joinToString("、")} 来禁言自己或指定其他用户(需管理员权限)。\n")
|
||||||
append(" - 支持指定禁言分钟数或随机分钟数,范围 $minBanMinutes-$maxBanMinutes 分钟。\n")
|
append(" - 支持指定禁言分钟数或随机分钟数,范围 $minBanMinutes-$maxBanMinutes 分钟。\n")
|
||||||
append(" - 支持对单个 @ 用户禁言,有概率反禁自己(骰子点数决定概率)。\n")
|
append(" - 支持对单个 @ 用户禁言,有概率反禁自己(骰子点数决定概率)。\n")
|
||||||
|
|
|
||||||
|
|
@ -84,22 +84,46 @@ abstract class BaseModule(baseName : String = "BaseModule", idName : String = ""
|
||||||
* 提供访问全局 NapCatClient 的快捷方式
|
* 提供访问全局 NapCatClient 的快捷方式
|
||||||
*/
|
*/
|
||||||
protected val napCatClient get() = GlobalManager.napCatClient
|
protected val napCatClient get() = GlobalManager.napCatClient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提供访问全局 blessingSkinClient 的快捷方式
|
* 提供访问全局 blessingSkinClient 的快捷方式
|
||||||
*/
|
*/
|
||||||
protected val blessingSkinClient get() = GlobalManager.blessingSkinClient
|
protected val blessingSkinClient get() = GlobalManager.blessingSkinClient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提供访问全局 mcSrvStatusClient 的快捷方式
|
* 提供访问全局 mcSrvStatusClient 的快捷方式
|
||||||
*/
|
*/
|
||||||
protected val mcSrvStatusClient get() = GlobalManager.mcSrvStatusClient
|
protected val mcSrvStatusClient get() = GlobalManager.mcSrvStatusClient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提供访问全局 加载模块 的快捷方式
|
* 提供访问全局 加载模块 的快捷方式
|
||||||
*/
|
*/
|
||||||
protected val moduleMap get() = GlobalManager.moduleManager.getModules()
|
protected val moduleMap get() = GlobalManager.moduleManager.getModules()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取数据库连接
|
* 获取数据库连接
|
||||||
* 使用 try-with-resources 时会自动关闭
|
* 使用 try-with-resources 时会自动关闭
|
||||||
*/
|
*/
|
||||||
protected fun getConnection() = GlobalManager.getConnection()
|
protected fun getConnection() = GlobalManager.getConnection()
|
||||||
|
/**
|
||||||
|
* 安全获取 NapCatClient,避免空指针异常
|
||||||
|
*/
|
||||||
|
protected fun getNapCatClientOrNull() = try {
|
||||||
|
GlobalManager.napCatClient
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.warn("获取NapCatClient失败", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全获取 NapCatClient,如果获取失败则抛出详细异常
|
||||||
|
*/
|
||||||
|
protected fun getNapCatClientOrThrow(): Any {
|
||||||
|
val client = try {
|
||||||
|
GlobalManager.napCatClient
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException("无法获取NapCatClient,请检查GlobalManager初始化状态", e)
|
||||||
|
}
|
||||||
|
return client ?: throw IllegalStateException("NapCatClient为null,请检查GlobalManager初始化")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,489 @@
|
||||||
package top.r3944realms.ltdmanager.module
|
package top.r3944realms.ltdmanager.module
|
||||||
|
|
||||||
|
import com.mojang.brigadier.CommandDispatcher
|
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType
|
||||||
|
import com.mojang.brigadier.arguments.LongArgumentType
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder.literal
|
||||||
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder.argument
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
||||||
|
import com.r3944realms.dg_lab.api.message.IPowerBoxMsg
|
||||||
|
import com.r3944realms.dg_lab.api.message.argType.ChangePolicy
|
||||||
|
import com.r3944realms.dg_lab.api.message.argType.Channel
|
||||||
|
import com.r3944realms.dg_lab.api.websocket.message.MessageDirection
|
||||||
|
import com.r3944realms.dg_lab.manager.DGPBClientManager
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import top.r3944realms.ltdmanager.GlobalManager
|
||||||
|
import top.r3944realms.ltdmanager.dglab.DgLab
|
||||||
|
import top.r3944realms.ltdmanager.dglab.model.game.GameClientOperation
|
||||||
|
import top.r3944realms.ltdmanager.dglab.model.game.GameServerOperation
|
||||||
|
import top.r3944realms.ltdmanager.dglab.model.game.Player
|
||||||
|
import top.r3944realms.ltdmanager.dglab.model.pulseware.DefaultPulseData
|
||||||
|
import top.r3944realms.ltdmanager.module.common.filter.TriggerMessageFilter
|
||||||
|
import top.r3944realms.ltdmanager.module.common.filter.type.IgnoreSelfFilter
|
||||||
|
import top.r3944realms.ltdmanager.module.common.filter.type.KeywordFilter
|
||||||
|
import top.r3944realms.ltdmanager.module.common.filter.type.NewMessageFilter
|
||||||
|
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.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
import top.r3944realms.ltdmanager.napcat.event.group.GetGroupMemberListEvent
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.group.GetGroupMemberListRequest
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.message.SetMsgEmojiLikeRequest
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||||
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据 {QQ}
|
||||||
|
*/
|
||||||
class DGLabModule(
|
class DGLabModule(
|
||||||
moduleName: String,
|
moduleName: String,
|
||||||
):
|
private val groupMessagePollingModule : GroupMessagePollingModule,
|
||||||
BaseModule("DGLabModule", moduleName) {
|
private val selfId: Long,
|
||||||
override fun onLoad() {
|
val adminIds: List<Long> = listOf(),
|
||||||
|
val maxClientNumber: Int = 10,
|
||||||
|
val commandHead: List<String> = listOf("dglab"),
|
||||||
|
) : BaseModule("DGLabModule", moduleName), PersistentState<DGLabModule.DgLabState> {
|
||||||
|
|
||||||
|
var dgLabManager: DgLab? = null
|
||||||
|
private var scope: CoroutineScope? = null
|
||||||
|
private var dglabCommandDispatcher: CommandDispatcher<Player> = CommandDispatcher<Player>().apply {
|
||||||
|
for (command in commandHead) register(
|
||||||
|
literal<Player>(command)
|
||||||
|
.then(literal<Player?>("server").requires { adminIds.contains(it.id) }
|
||||||
|
.then(literal<Player?>("start").executes { startDgLab() })
|
||||||
|
.then(literal<Player?>("stop").executes { stopDgLab() })
|
||||||
|
.then(literal<Player?>("stopAllClient").executes { stopAllDgLabClient() })
|
||||||
|
)
|
||||||
|
.then(literal<Player?>("client")
|
||||||
|
.then(literal<Player?>("start").executes { startClient(it.source.id) })
|
||||||
|
.then(literal<Player?>("stop").executes { stopClient(it.source.id) })
|
||||||
|
)
|
||||||
|
.then(literal<Player?>("strength")
|
||||||
|
.then(argument<Player?, String>("channel", StringArgumentType.string())
|
||||||
|
.then(literal<Player?>("add")
|
||||||
|
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
|
||||||
|
.executes { strengthAdd(it.source.id, StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(literal<Player?>("set")
|
||||||
|
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
|
||||||
|
.executes { strengthSet(it.source.id, StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(argument<Player?, Long>("player", LongArgumentType.longArg())
|
||||||
|
.then(argument<Player?, String>("channel", StringArgumentType.string())
|
||||||
|
.then(literal<Player?>("add")
|
||||||
|
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(-200, 200))
|
||||||
|
.executes { strengthAdd(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(literal<Player?>("set")
|
||||||
|
.then(argument<Player?, Int>("value", IntegerArgumentType.integer(0, 200))
|
||||||
|
.executes { strengthSet(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(literal<Player?>("pulse")
|
||||||
|
.then(argument<Player?, String>("channel", StringArgumentType.string())
|
||||||
|
.then(literal<Player?>("clear").executes { pulseClear(it.source.id, StringArgumentType.getString(it, "channel")) })
|
||||||
|
.then(literal<Player?>("set")
|
||||||
|
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
|
||||||
|
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
|
||||||
|
.executes { pulseSet(it.source.id, StringArgumentType.getString(it, "channel"), StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(argument<Player?, Long>("player", LongArgumentType.longArg())
|
||||||
|
.then(argument<Player?, String>("channel", StringArgumentType.string())
|
||||||
|
.then(literal<Player?>("clear").executes { pulseClear(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel")) })
|
||||||
|
.then(literal<Player?>("set")
|
||||||
|
.then(argument<Player?, String>("pulseName", StringArgumentType.string())
|
||||||
|
.then(argument<Player?, Int>("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE))
|
||||||
|
.executes { pulseSet(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel"), StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// .then(literal<Player?>("info").executes {}
|
||||||
|
// .then(argument<Player?, String>("player", StringArgumentType.string()).executes {})
|
||||||
|
// )
|
||||||
|
|
||||||
}
|
}
|
||||||
|
private val stateFile: File = getStateFileInternal("dg_lab_state.json", name)
|
||||||
|
private val stateBackupFile: File = getStateFileInternal("dg_lab_state.json.bak", name)
|
||||||
|
private var dgLabState = loadState()
|
||||||
|
override fun getState(): DgLabState = dgLabState
|
||||||
|
override fun getStateFileInternal(): File = stateFile
|
||||||
|
|
||||||
|
private val triggerFilter by lazy {
|
||||||
|
TriggerMessageFilter(
|
||||||
|
listOf(
|
||||||
|
IgnoreSelfFilter(selfId),
|
||||||
|
NewMessageFilter { userId ->
|
||||||
|
dgLabState.getLastTriggerTime(userId) to dgLabState.getLastTriggerRealId(userId)
|
||||||
|
},
|
||||||
|
KeywordFilter(commandHead.toSet())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoad() {
|
||||||
|
LoggerUtil.logger.info("[$name] 模块已装载,监听群组: ${groupMessagePollingModule.targetGroupId}")
|
||||||
|
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
scope!!.launch {
|
||||||
|
LoggerUtil.logger.info("[$name] 轮询协程启动")
|
||||||
|
dgLabManager = DgLab()
|
||||||
|
val gameServerOperation = GameServerOperation(napCatClient, groupMessagePollingModule.targetGroupId)
|
||||||
|
dgLabManager?.createServerManager(gameServerOperation)?.let { dgLabManager?.initServerManager(it) }
|
||||||
|
gameServerOperation.serverManager = dgLabManager?.serverManager
|
||||||
|
init()
|
||||||
|
groupMessagePollingModule.messagesFlow.collect { messages ->
|
||||||
|
if (loaded) handleMessages(messages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
override suspend fun onUnload() {
|
override suspend fun onUnload() {
|
||||||
|
saveState(dgLabState)
|
||||||
|
dgLabManager?.close()
|
||||||
|
scope?.cancel()
|
||||||
|
LoggerUtil.logger.info("[$name] 模块已卸载完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||||
|
if (messages.isEmpty()) return
|
||||||
|
|
||||||
|
// 先对所有消息进行 @ 提及处理
|
||||||
|
val processedMessages = messages.map { msg ->
|
||||||
|
val processedText = processMessageMentionsToLong(msg)
|
||||||
|
msg to processedText
|
||||||
|
}
|
||||||
|
|
||||||
|
val triggerMsgs = processedMessages
|
||||||
|
.filter { (msg, _) -> filterTriggerMessages(listOf(msg)).isNotEmpty() }
|
||||||
|
.map { (msg, processedText) -> Triple(msg, msg.userId, processedText) }
|
||||||
|
|
||||||
|
if (triggerMsgs.isEmpty()) return
|
||||||
|
|
||||||
|
var refPlayer: Player? = null
|
||||||
|
var refMsg: MsgHistorySpecificMsg? = null
|
||||||
|
try {
|
||||||
|
triggerMsgs.forEach { (msg, userId, processedText) ->
|
||||||
|
refMsg = msg
|
||||||
|
LoggerUtil.logger.info("[$name] 原始消息用户: $userId")
|
||||||
|
LoggerUtil.logger.info("[$name] 处理后的命令: $processedText")
|
||||||
|
|
||||||
|
refPlayer = dgLabManager?.getPlayerManager()?.getPlayer(userId)
|
||||||
|
dgLabState = dgLabState.updateOrCreate(userId, msg.realId, msg.time)
|
||||||
|
val execute = dglabCommandDispatcher.execute(processedText, refPlayer)
|
||||||
|
scope?.launch {
|
||||||
|
GlobalManager.napCatClient.sendUnit(
|
||||||
|
SetMsgEmojiLikeRequest(
|
||||||
|
if (execute == 0) 1.0 else 2.0, ID.long(msg.realId), true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: CommandSyntaxException) {
|
||||||
|
val reader = e.input // 用户输入
|
||||||
|
val cursor = e.cursor
|
||||||
|
val partialInput = reader.substring(0, cursor)
|
||||||
|
if (refPlayer != null) {
|
||||||
|
val node = dglabCommandDispatcher.parse(
|
||||||
|
partialInput,
|
||||||
|
dgLabManager?.getPlayerManager()?.getPlayer(refPlayer!!.id)
|
||||||
|
).context.nodes.lastOrNull()?.node
|
||||||
|
val usage = if (node != null) {
|
||||||
|
val values = dglabCommandDispatcher.getSmartUsage(node, refPlayer).values
|
||||||
|
if(!values.isEmpty()) "目前节点可使用的子命令: $values"
|
||||||
|
else "目前节点无用法"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
"未找到用法"
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFailedMessage(
|
||||||
|
napCatClient,
|
||||||
|
text = "指令解析错误:\n ${e.message}\n\n$usage",
|
||||||
|
qq = refMsg?.userId,
|
||||||
|
realId = refMsg?.realId,
|
||||||
|
time = refMsg?.time
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
sendFailedMessage(napCatClient, text = "系统错误,请联系管理员: ${e.message}")
|
||||||
|
} finally {
|
||||||
|
saveState(dgLabState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 处理整个消息中的 @ 提及,转换为 Long 类型,并清理多余空格
|
||||||
|
*/
|
||||||
|
private fun processMessageMentionsToLong(msg: MsgHistorySpecificMsg): String {
|
||||||
|
val processedText = msg.message.joinToString(" ") { seg ->
|
||||||
|
when (seg.type) {
|
||||||
|
MessageType.At -> {
|
||||||
|
// 处理 @ 提及,转换为 Long
|
||||||
|
seg.data.qq?.let { qq ->
|
||||||
|
when (qq) {
|
||||||
|
is ID.StringValue -> qq.value.toLong().toString()
|
||||||
|
is ID.LongValue -> qq.value.toString()
|
||||||
|
}
|
||||||
|
} ?: seg.data.text ?: ""
|
||||||
|
}
|
||||||
|
MessageType.Text -> {
|
||||||
|
seg.data.text ?: ""
|
||||||
|
}
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}.trim()
|
||||||
|
|
||||||
|
// 清理多余空格:将多个连续空格替换为单个空格
|
||||||
|
return processedText.replace(Regex("\\s+"), " ")
|
||||||
|
}
|
||||||
|
private suspend fun filterTriggerMessages(
|
||||||
|
messages: List<MsgHistorySpecificMsg>
|
||||||
|
): List<MsgHistorySpecificMsg> = triggerFilter.filter(messages)
|
||||||
|
private suspend fun init() {
|
||||||
|
val getGroupMemberListEvent = napCatClient.send<GetGroupMemberListEvent>(
|
||||||
|
GetGroupMemberListRequest(
|
||||||
|
ID.long(groupMessagePollingModule.targetGroupId),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dgLabManager?.initOrLoadPlayerManager(getGroupMemberListEvent.data.filter { !it.isRobot }
|
||||||
|
.associate { it.userId to it.nickname })
|
||||||
|
dgLabManager?.initClientManager()
|
||||||
|
}
|
||||||
|
// private fun getHelp(): Int {
|
||||||
|
// scope?.launch {
|
||||||
|
// sendMessage()
|
||||||
|
// }
|
||||||
|
// return 1
|
||||||
|
// }
|
||||||
|
private fun startDgLab(): Int {
|
||||||
|
dgLabManager?.getServer()?.start()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
private fun stopDgLab(): Int {
|
||||||
|
dgLabManager?.getServer()?.stop()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
private fun stopAllDgLabClient(): Int {
|
||||||
|
dgLabManager?.clientManager?.stopAll()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
private fun startClient(qq: Long): Int {
|
||||||
|
if (dgLabManager?.getPlayerManager()?.getOnlinePlayerSize()!! > maxClientNumber) {
|
||||||
|
scope!!.launch {
|
||||||
|
sendFailedMessage(napCatClient, text = "无法启动新的客户端, 因为已到达最大连接数${maxClientNumber}")
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
val operation = GameClientOperation(
|
||||||
|
napCatClient,
|
||||||
|
groupMessagePollingModule.targetGroupId,
|
||||||
|
dgLabManager!!.getPlayerManager(),
|
||||||
|
qq
|
||||||
|
)
|
||||||
|
val dgpbClientManager = dgLabManager?.getClientOrCreate(
|
||||||
|
qq.toString(),
|
||||||
|
operation
|
||||||
|
)
|
||||||
|
operation.clientSelf = dgpbClientManager
|
||||||
|
dgpbClientManager?.start()
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
private fun stopClient(qq: Long): Int {
|
||||||
|
dgLabManager?.getClient(qq.toString())?.stop()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
private fun strengthAdd(qq: Long, channel: String, value: Int): Int {
|
||||||
|
val client = dgLabManager?.getClient(qq.toString()) ?: return -1
|
||||||
|
val changePolicy = if(value >= 0) ChangePolicy.INCREASE else ChangePolicy.DECREASE
|
||||||
|
val strengthValue = abs(value)
|
||||||
|
|
||||||
|
when(channel) {
|
||||||
|
"a" -> sendStrengthChange(client, Channel.A, changePolicy, strengthValue)
|
||||||
|
"b" -> sendStrengthChange(client, Channel.B, changePolicy, strengthValue)
|
||||||
|
"ab" -> {
|
||||||
|
sendStrengthChange(client, Channel.A, changePolicy, strengthValue)
|
||||||
|
sendStrengthChange(client, Channel.B, changePolicy, strengthValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
private fun strengthSet(qq: Long, channel: String, value: Int): Int {
|
||||||
|
val client = dgLabManager?.getClient(qq.toString()) ?: return -1
|
||||||
|
when(channel) {
|
||||||
|
"a" -> sendStrengthChange(client, Channel.A, ChangePolicy.GOTO, value)
|
||||||
|
"b" -> sendStrengthChange(client, Channel.B, ChangePolicy.GOTO, value)
|
||||||
|
"ab" -> {
|
||||||
|
sendStrengthChange(client, Channel.A, ChangePolicy.GOTO, value)
|
||||||
|
sendStrengthChange(client, Channel.B, ChangePolicy.GOTO, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendStrengthChange(client: DGPBClientManager, channel: Channel, policy: ChangePolicy, value: Int) {
|
||||||
|
client.send(IPowerBoxMsg.StrengthChange(channel, policy, value)
|
||||||
|
.toPowerBoxMessage(client.sharedData.connectionId, client.sharedData.targetWSId, MessageDirection.DirectType.CLIENT_TO_APPLICATION))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pulseClear(qq: Long, channel: String): Int {
|
||||||
|
val client = dgLabManager?.getClient(qq.toString()) ?: return -1
|
||||||
|
when(channel) {
|
||||||
|
"a" -> client.send(IPowerBoxMsg.Clear(Channel.A)
|
||||||
|
.toPowerBoxMessage(client.sharedData.connectionId, client.sharedData.targetWSId, MessageDirection.DirectType.CLIENT_TO_APPLICATION))
|
||||||
|
"b" -> client.send(IPowerBoxMsg.Clear(Channel.B)
|
||||||
|
.toPowerBoxMessage(client.sharedData.connectionId, client.sharedData.targetWSId, MessageDirection.DirectType.CLIENT_TO_APPLICATION))
|
||||||
|
"ab" -> {
|
||||||
|
client.send(IPowerBoxMsg.Clear(Channel.A)
|
||||||
|
.toPowerBoxMessage(client.sharedData.connectionId, client.sharedData.targetWSId, MessageDirection.DirectType.CLIENT_TO_APPLICATION))
|
||||||
|
client.send(IPowerBoxMsg.Clear(Channel.B)
|
||||||
|
.toPowerBoxMessage(client.sharedData.connectionId, client.sharedData.targetWSId, MessageDirection.DirectType.CLIENT_TO_APPLICATION))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
private fun pulseSet(qq: Long, channel: String, pulseName: String, timer: Int): Int {
|
||||||
|
val client = dgLabManager?.getClient(qq.toString()) ?: return -1
|
||||||
|
val pulse = DefaultPulseData.allPulseWaveLists()[pulseName] ?: return -2
|
||||||
|
when(channel) {
|
||||||
|
"a" -> client.send(IPowerBoxMsg.Pulse(Channel.A, pulse, timer)
|
||||||
|
.toPowerBoxMessage(client.sharedData.connectionId, client.sharedData.targetWSId, MessageDirection.DirectType.CLIENT_TO_APPLICATION))
|
||||||
|
"b" -> client.send(IPowerBoxMsg.Pulse(Channel.B, pulse, timer)
|
||||||
|
.toPowerBoxMessage(client.sharedData.connectionId, client.sharedData.targetWSId, MessageDirection.DirectType.CLIENT_TO_APPLICATION))
|
||||||
|
"ab" -> {
|
||||||
|
client.send(IPowerBoxMsg.Pulse(Channel.A, pulse, timer)
|
||||||
|
.toPowerBoxMessage(client.sharedData.connectionId, client.sharedData.targetWSId, MessageDirection.DirectType.CLIENT_TO_APPLICATION))
|
||||||
|
client.send(IPowerBoxMsg.Pulse(Channel.B, pulse, timer)
|
||||||
|
.toPowerBoxMessage(client.sharedData.connectionId, client.sharedData.targetWSId, MessageDirection.DirectType.CLIENT_TO_APPLICATION))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendMessage(
|
||||||
|
client: NapCatClient,
|
||||||
|
qq: Long,
|
||||||
|
realId: Long,
|
||||||
|
time: Long,
|
||||||
|
text: String = "正常消息"
|
||||||
|
) {
|
||||||
|
LoggerUtil.logger.info("[$name] 发送消息: realId=$realId, text=$text")
|
||||||
|
|
||||||
|
val request = SendGroupMsgRequest(
|
||||||
|
MessageElement.reply(ID.long(realId), text),
|
||||||
|
ID.long(groupMessagePollingModule.targetGroupId)
|
||||||
|
)
|
||||||
|
client.sendUnit(request)
|
||||||
|
LoggerUtil.logger.info("[$name] 已发送 消息")
|
||||||
|
|
||||||
|
// 更新触发的最大 realId
|
||||||
|
dgLabState = dgLabState.updateOrCreate(qq, realId, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendFailedMessage(
|
||||||
|
client: NapCatClient,
|
||||||
|
qq: Long? = null,
|
||||||
|
realId: Long? = null,
|
||||||
|
time: Long? = null,
|
||||||
|
text: String = "失败消息"
|
||||||
|
) {
|
||||||
|
LoggerUtil.logger.info("[$name] 发送失败消息: realId=$realId, text=$text")
|
||||||
|
if (realId != null && qq != null && time != null) {
|
||||||
|
val request = SendGroupMsgRequest(
|
||||||
|
MessageElement.reply(ID.long(realId), text),
|
||||||
|
ID.long(groupMessagePollingModule.targetGroupId)
|
||||||
|
)
|
||||||
|
client.sendUnit(request)
|
||||||
|
LoggerUtil.logger.info("[$name] 已发送 失败消息")
|
||||||
|
|
||||||
|
// 更新触发的最大 realId
|
||||||
|
dgLabState = dgLabState.updateOrCreate(qq, realId, time)
|
||||||
|
} else {
|
||||||
|
val request = SendGroupMsgRequest(
|
||||||
|
listOf(MessageElement.text(text)),
|
||||||
|
ID.long(groupMessagePollingModule.targetGroupId)
|
||||||
|
)
|
||||||
|
client.sendUnit(request)
|
||||||
|
LoggerUtil.logger.info("[$name] 已发送 失败消息[无指定对象]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -------- 持久化 -----------
|
||||||
|
@Serializable
|
||||||
|
data class DgLabDetail(
|
||||||
|
val realId : Long,
|
||||||
|
val time: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DgLabState(
|
||||||
|
val map: Map<Long, DgLabDetail> = emptyMap()
|
||||||
|
) {
|
||||||
|
fun getLastTriggerTime(userId: Long): Long = map[userId]?.time ?: -1
|
||||||
|
fun getLastTriggerRealId(userId: Long): Long = map[userId]?.realId ?: -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新或创建某个用户的触发信息
|
||||||
|
* - 如果传了 realId,则更新 realId
|
||||||
|
* - 如果传了 time,则更新 time
|
||||||
|
* - 其他字段保持原值
|
||||||
|
*/
|
||||||
|
fun updateOrCreate(
|
||||||
|
userId: Long,
|
||||||
|
realId: Long? = null,
|
||||||
|
time: Long? = null
|
||||||
|
): DgLabState {
|
||||||
|
val old = map[userId]
|
||||||
|
val newDetail = DgLabDetail(
|
||||||
|
realId = realId ?: old?.realId ?: -1,
|
||||||
|
time = time ?: old?.time ?: -1
|
||||||
|
)
|
||||||
|
val newMap = map.toMutableMap().apply { put(userId, newDetail) }
|
||||||
|
return copy(map = newMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveState(state: DgLabState) {
|
||||||
|
try {
|
||||||
|
if (stateFile.exists()) stateFile.copyTo(stateBackupFile, overwrite = true)
|
||||||
|
stateFile.writeText(Json.encodeToString(state))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.error("[$name] 保存状态失败", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadState(): DgLabState {
|
||||||
|
return try {
|
||||||
|
val fileToRead = when {
|
||||||
|
stateFile.exists() -> stateFile
|
||||||
|
stateBackupFile.exists() -> stateBackupFile
|
||||||
|
else -> null
|
||||||
|
} ?: return DgLabState()
|
||||||
|
|
||||||
|
Json.decodeFromString<DgLabState>(fileToRead.readText())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.warn("[$name] 读取状态失败", e)
|
||||||
|
DgLabState()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetGroupMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.event.message.GetGroupMsgHistoryEvent
|
||||||
import top.r3944realms.ltdmanager.napcat.request.message.GetGroupMsgHistoryRequest
|
import top.r3944realms.ltdmanager.napcat.request.message.GetGroupMsgHistoryRequest
|
||||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
|
@ -19,11 +19,11 @@ class GroupMessagePollingModule(
|
||||||
private var scope: CoroutineScope? = null
|
private var scope: CoroutineScope? = null
|
||||||
|
|
||||||
// 用 Flow 存消息,其他模块可以订阅
|
// 用 Flow 存消息,其他模块可以订阅
|
||||||
private val _messagesFlow = MutableSharedFlow<List<GetFriendMsgHistoryEvent.SpecificMsg>>(
|
private val _messagesFlow = MutableSharedFlow<List<MsgHistorySpecificMsg>>(
|
||||||
replay = 1, // 保留最近一份消息
|
replay = 1, // 保留最近一份消息
|
||||||
extraBufferCapacity = 1
|
extraBufferCapacity = 1
|
||||||
)
|
)
|
||||||
val messagesFlow: SharedFlow<List<GetFriendMsgHistoryEvent.SpecificMsg>> = _messagesFlow.asSharedFlow()
|
val messagesFlow: SharedFlow<List<MsgHistorySpecificMsg>> = _messagesFlow.asSharedFlow()
|
||||||
|
|
||||||
override fun onLoad() {
|
override fun onLoad() {
|
||||||
LoggerUtil.logger.info("[$name] 启动消息轮询 (群: $targetGroupId)")
|
LoggerUtil.logger.info("[$name] 启动消息轮询 (群: $targetGroupId)")
|
||||||
|
|
@ -31,12 +31,12 @@ class GroupMessagePollingModule(
|
||||||
scope!!.launch {
|
scope!!.launch {
|
||||||
while (isActive && loaded) {
|
while (isActive && loaded) {
|
||||||
try {
|
try {
|
||||||
val event = napCatClient.send(
|
val event = getNapCatClientOrNull()?.send<GetGroupMsgHistoryEvent>(
|
||||||
GetGroupMsgHistoryRequest(
|
GetGroupMsgHistoryRequest(
|
||||||
count = msgHistoryCheck,
|
count = msgHistoryCheck,
|
||||||
groupId = ID.long(targetGroupId)
|
groupId = ID.long(targetGroupId)
|
||||||
)
|
)
|
||||||
) as? GetGroupMsgHistoryEvent
|
)
|
||||||
|
|
||||||
val messages = event?.data?.messages ?: emptyList()
|
val messages = event?.data?.messages ?: emptyList()
|
||||||
LoggerUtil.logger.debug("[$name] 拉取到 ${messages.size} 条消息")
|
LoggerUtil.logger.debug("[$name] 拉取到 ${messages.size} 条消息")
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ class GroupRequestHandlerModule(
|
||||||
try {
|
try {
|
||||||
getConnection().use { conn ->
|
getConnection().use { conn ->
|
||||||
val stmt = conn.prepareStatement(
|
val stmt = conn.prepareStatement(
|
||||||
"SELECT status FROM minecraft_manager_ltd.players WHERE qq=?"
|
"SELECT status FROM minecraft_manager_ltd_8.players WHERE qq=?"
|
||||||
)
|
)
|
||||||
stmt.setLong(1, actor)
|
stmt.setLong(1, actor)
|
||||||
val rs = stmt.executeQuery()
|
val rs = stmt.executeQuery()
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import top.r3944realms.ltdmanager.napcat.NapCatClient
|
||||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
||||||
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
|
@ -37,7 +37,7 @@ class HelpModule(
|
||||||
|
|
||||||
// 命令解析器
|
// 命令解析器
|
||||||
private val commandParser = CommandParser(keywords)
|
private val commandParser = CommandParser(keywords)
|
||||||
private val GetFriendMsgHistoryEvent.SpecificMsg.textContent: String
|
private val MsgHistorySpecificMsg.textContent: String
|
||||||
get() = message.joinToString("") { it.data.text ?: "" }
|
get() = message.joinToString("") { it.data.text ?: "" }
|
||||||
|
|
||||||
// 持久化文件
|
// 持久化文件
|
||||||
|
|
@ -100,7 +100,7 @@ class HelpModule(
|
||||||
LoggerUtil.logger.info("[$name] 模块已卸载完成")
|
LoggerUtil.logger.info("[$name] 模块已卸载完成")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleMessages(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>) {
|
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||||
val filtered = triggerFilter.filter(messages)
|
val filtered = triggerFilter.filter(messages)
|
||||||
val triggerMsg = filtered.maxByOrNull { it.time } ?: return
|
val triggerMsg = filtered.maxByOrNull { it.time } ?: return
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ class HelpModule(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendAllModulesHelp(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
|
private suspend fun sendAllModulesHelp(msg: MsgHistorySpecificMsg) {
|
||||||
val messages = moduleMap.map { (name, module) ->
|
val messages = moduleMap.map { (name, module) ->
|
||||||
val textBuilder = StringBuilder()
|
val textBuilder = StringBuilder()
|
||||||
textBuilder.appendLine("===== $name =====")
|
textBuilder.appendLine("===== $name =====")
|
||||||
|
|
@ -153,7 +153,7 @@ class HelpModule(
|
||||||
updateTriggerState(msg)
|
updateTriggerState(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendModuleHelp(msg: GetFriendMsgHistoryEvent.SpecificMsg, moduleName: String, module: BaseModule) {
|
private suspend fun sendModuleHelp(msg: MsgHistorySpecificMsg, moduleName: String, module: BaseModule) {
|
||||||
val textBuilder = StringBuilder()
|
val textBuilder = StringBuilder()
|
||||||
textBuilder.appendLine("===== $moduleName =====")
|
textBuilder.appendLine("===== $moduleName =====")
|
||||||
textBuilder.appendLine(module.info())
|
textBuilder.appendLine(module.info())
|
||||||
|
|
@ -187,7 +187,7 @@ class HelpModule(
|
||||||
updateTriggerState(msg)
|
updateTriggerState(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendText(msg: GetFriendMsgHistoryEvent.SpecificMsg, text: String) {
|
private suspend fun sendText(msg: MsgHistorySpecificMsg, text: String) {
|
||||||
val request = SendGroupMsgRequest(
|
val request = SendGroupMsgRequest(
|
||||||
MessageElement.reply(ID.long(msg.realId), text),
|
MessageElement.reply(ID.long(msg.realId), text),
|
||||||
ID.long(groupMessagePollingModule.targetGroupId)
|
ID.long(groupMessagePollingModule.targetGroupId)
|
||||||
|
|
@ -196,7 +196,7 @@ class HelpModule(
|
||||||
updateTriggerState(msg)
|
updateTriggerState(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateTriggerState(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
|
private fun updateTriggerState(msg: MsgHistorySpecificMsg) {
|
||||||
lastTriggerState.lastTriggeredRealId = msg.realId
|
lastTriggerState.lastTriggeredRealId = msg.realId
|
||||||
lastTriggerState.lastTriggerTime = msg.time
|
lastTriggerState.lastTriggerTime = msg.time
|
||||||
saveState(lastTriggerState)
|
saveState(lastTriggerState)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import top.r3944realms.ltdmanager.module.exception.InvitationCodeException
|
||||||
import top.r3944realms.ltdmanager.napcat.NapCatClient
|
import top.r3944realms.ltdmanager.napcat.NapCatClient
|
||||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||||
import top.r3944realms.ltdmanager.utils.HtmlTemplateUtil
|
import top.r3944realms.ltdmanager.utils.HtmlTemplateUtil
|
||||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
|
@ -165,14 +165,14 @@ class InvitationCodesModule(
|
||||||
// =========================
|
// =========================
|
||||||
// 消息处理主流程
|
// 消息处理主流程
|
||||||
// =========================
|
// =========================
|
||||||
private suspend fun handleMessages(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>) {
|
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||||
if (messages.isEmpty()) return
|
if (messages.isEmpty()) return
|
||||||
val triggerMsgs = filterTriggerMessages(messages)
|
val triggerMsgs = filterTriggerMessages(messages)
|
||||||
if (triggerMsgs.isEmpty()) return
|
if (triggerMsgs.isEmpty()) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val hadValidCodeButNotUsed = mutableListOf<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>()
|
val hadValidCodeButNotUsed = mutableListOf<Pair<Long, MsgHistorySpecificMsg>>()
|
||||||
val needNewCode = mutableListOf<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>()
|
val needNewCode = mutableListOf<Pair<Long, MsgHistorySpecificMsg>>()
|
||||||
|
|
||||||
getIdAndSelectSituation(triggerMsgs, hadValidCodeButNotUsed, needNewCode)
|
getIdAndSelectSituation(triggerMsgs, hadValidCodeButNotUsed, needNewCode)
|
||||||
createAndSearchInvitationCodeIdsThenUpdateDate(needNewCode)
|
createAndSearchInvitationCodeIdsThenUpdateDate(needNewCode)
|
||||||
|
|
@ -186,8 +186,8 @@ class InvitationCodesModule(
|
||||||
|
|
||||||
/** 过滤出符合条件的触发消息 */
|
/** 过滤出符合条件的触发消息 */
|
||||||
private suspend fun filterTriggerMessages(
|
private suspend fun filterTriggerMessages(
|
||||||
messages: List<GetFriendMsgHistoryEvent.SpecificMsg>
|
messages: List<MsgHistorySpecificMsg>
|
||||||
): List<GetFriendMsgHistoryEvent.SpecificMsg> {
|
): List<MsgHistorySpecificMsg> {
|
||||||
|
|
||||||
// 先应用通用过滤器
|
// 先应用通用过滤器
|
||||||
val filtered = triggerFilter.filter(messages)
|
val filtered = triggerFilter.filter(messages)
|
||||||
|
|
@ -198,9 +198,9 @@ class InvitationCodesModule(
|
||||||
.mapNotNull { (_, msgs) -> msgs.maxByOrNull { it.time } }
|
.mapNotNull { (_, msgs) -> msgs.maxByOrNull { it.time } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getIdAndSelectSituation(msgs: List<GetFriendMsgHistoryEvent.SpecificMsg>,
|
private suspend fun getIdAndSelectSituation(msgs: List<MsgHistorySpecificMsg>,
|
||||||
hadVaildCodeButNotUseList : MutableList<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>,
|
hadVaildCodeButNotUseList : MutableList<Pair<Long, MsgHistorySpecificMsg>>,
|
||||||
needNewCodeList: MutableList<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>) {
|
needNewCodeList: MutableList<Pair<Long, MsgHistorySpecificMsg>>) {
|
||||||
if (msgs.isEmpty()) return
|
if (msgs.isEmpty()) return
|
||||||
|
|
||||||
val qqIds = msgs.map { it.userId }
|
val qqIds = msgs.map { it.userId }
|
||||||
|
|
@ -273,7 +273,7 @@ class InvitationCodesModule(
|
||||||
sendFailedMessage(napCatClient, text = "批量查询用户资格信息失败,请联系管理员: ${e.message}")
|
sendFailedMessage(napCatClient, text = "批量查询用户资格信息失败,请联系管理员: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private suspend fun hadVaildCodeButNotUseListHandler(list: List<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>) {
|
private suspend fun hadVaildCodeButNotUseListHandler(list: List<Pair<Long, MsgHistorySpecificMsg>>) {
|
||||||
if (list.isEmpty()) return
|
if (list.isEmpty()) return
|
||||||
|
|
||||||
val whiteListIds = list.map { it.first }
|
val whiteListIds = list.map { it.first }
|
||||||
|
|
@ -405,7 +405,7 @@ class InvitationCodesModule(
|
||||||
lastTriggerMapState = lastTriggerMapState.updateLastTrigger(qq, realId, -1)
|
lastTriggerMapState = lastTriggerMapState.updateLastTrigger(qq, realId, -1)
|
||||||
}
|
}
|
||||||
private suspend fun createAndSearchInvitationCodeIdsThenUpdateDate(
|
private suspend fun createAndSearchInvitationCodeIdsThenUpdateDate(
|
||||||
needNewTokenIdAndMsgPairs: List<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>,
|
needNewTokenIdAndMsgPairs: List<Pair<Long, MsgHistorySpecificMsg>>,
|
||||||
) {
|
) {
|
||||||
if (needNewTokenIdAndMsgPairs.isEmpty()) return
|
if (needNewTokenIdAndMsgPairs.isEmpty()) return
|
||||||
|
|
||||||
|
|
@ -461,7 +461,7 @@ class InvitationCodesModule(
|
||||||
*/
|
*/
|
||||||
private fun validateCodeCountMatch(
|
private fun validateCodeCountMatch(
|
||||||
invitationCodes: List<InvitationCodeGenerationResponse.InvitationCode>?,
|
invitationCodes: List<InvitationCodeGenerationResponse.InvitationCode>?,
|
||||||
needNewTokenIdAndMsgPairs: List<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>
|
needNewTokenIdAndMsgPairs: List<Pair<Long, MsgHistorySpecificMsg>>
|
||||||
) {
|
) {
|
||||||
if (invitationCodes == null) {
|
if (invitationCodes == null) {
|
||||||
throw InvitationCodeException.ApiFailureException("获取邀请码请求失败")
|
throw InvitationCodeException.ApiFailureException("获取邀请码请求失败")
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import top.r3944realms.ltdmanager.napcat.NapCatClient
|
||||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
||||||
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
|
@ -112,7 +112,7 @@ class McServerStatusModule(
|
||||||
LoggerUtil.logger.info("[$name] 模块已卸载完成")
|
LoggerUtil.logger.info("[$name] 模块已卸载完成")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleMessages(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>) {
|
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||||
if (messages.isEmpty()) return
|
if (messages.isEmpty()) return
|
||||||
val triggerMsgs = filterTriggerMessages(messages)
|
val triggerMsgs = filterTriggerMessages(messages)
|
||||||
if (triggerMsgs.isEmpty()) return
|
if (triggerMsgs.isEmpty()) return
|
||||||
|
|
@ -129,8 +129,8 @@ class McServerStatusModule(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun filterTriggerMessages(
|
private suspend fun filterTriggerMessages(
|
||||||
messages: List<GetFriendMsgHistoryEvent.SpecificMsg>
|
messages: List<MsgHistorySpecificMsg>
|
||||||
): List<GetFriendMsgHistoryEvent.SpecificMsg> = triggerFilter.filter(messages)
|
): List<MsgHistorySpecificMsg> = triggerFilter.filter(messages)
|
||||||
|
|
||||||
private suspend fun sendFailedMessage(
|
private suspend fun sendFailedMessage(
|
||||||
client: NapCatClient,
|
client: NapCatClient,
|
||||||
|
|
@ -169,7 +169,7 @@ class McServerStatusModule(
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private suspend fun processCommand(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
|
private suspend fun processCommand(msg: MsgHistorySpecificMsg) {
|
||||||
// 找出文本内容
|
// 找出文本内容
|
||||||
val text = msg.message
|
val text = msg.message
|
||||||
.firstOrNull { it.type == MessageType.Text }
|
.firstOrNull { it.type == MessageType.Text }
|
||||||
|
|
@ -226,7 +226,7 @@ class McServerStatusModule(
|
||||||
// ---------------- 转发消息封装 ----------------
|
// ---------------- 转发消息封装 ----------------
|
||||||
private suspend fun sendStatusForwardMessage(
|
private suspend fun sendStatusForwardMessage(
|
||||||
client: NapCatClient,
|
client: NapCatClient,
|
||||||
msg: GetFriendMsgHistoryEvent.SpecificMsg,
|
msg: MsgHistorySpecificMsg,
|
||||||
address: String,
|
address: String,
|
||||||
status: McServerStatus,
|
status: McServerStatus,
|
||||||
realId: Long,
|
realId: Long,
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ class ModGroupHandlerModule(
|
||||||
}
|
}
|
||||||
saveState(state)
|
saveState(state)
|
||||||
}
|
}
|
||||||
fun getRejectRecord(userId: Long): RejectRecord? {
|
private fun getRejectRecord(userId: Long): RejectRecord? {
|
||||||
return getState().records[userId]
|
return getState().records[userId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ interface PersistentState<T> {
|
||||||
fun saveState(state: T)
|
fun saveState(state: T)
|
||||||
fun loadState(): T
|
fun loadState(): T
|
||||||
// 默认实现:统一管理 data 目录下的文件
|
// 默认实现:统一管理 data 目录下的文件
|
||||||
fun getStateFileInternal(name: String, moduleName: String): File {
|
fun getStateFileInternal(name: String, subName: String): File {
|
||||||
val dataDir = File("data", FileNameFilter.filterFileName(moduleName))
|
val dataDir = File("data", FileNameFilter.filterFileName(subName))
|
||||||
if (!dataDir.exists()) dataDir.mkdirs()
|
if (!dataDir.exists()) dataDir.mkdirs()
|
||||||
return File(dataDir, name)
|
return File(dataDir, name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import top.r3944realms.ltdmanager.napcat.NapCatClient
|
||||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
import top.r3944realms.ltdmanager.napcat.data.MessageElement
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
||||||
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||||
import top.r3944realms.ltdmanager.utils.CmdUtil
|
import top.r3944realms.ltdmanager.utils.CmdUtil
|
||||||
|
|
@ -110,7 +110,7 @@ class RconPlayerListModule(
|
||||||
LoggerUtil.logger.info("[$name] 模块已卸载完成")
|
LoggerUtil.logger.info("[$name] 模块已卸载完成")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleMessages(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>) {
|
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||||
val filtered = triggerFilter.filter(messages)
|
val filtered = triggerFilter.filter(messages)
|
||||||
|
|
||||||
// RCON 模块只取最新的一条消息
|
// RCON 模块只取最新的一条消息
|
||||||
|
|
@ -124,7 +124,7 @@ class RconPlayerListModule(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private suspend fun processTrigger(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
|
private suspend fun processTrigger(msg: MsgHistorySpecificMsg) {
|
||||||
LoggerUtil.logger.info("[$name] 执行 RCON 查询")
|
LoggerUtil.logger.info("[$name] 执行 RCON 查询")
|
||||||
|
|
||||||
val commands = listOf("forge tps", "list")
|
val commands = listOf("forge tps", "list")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,27 @@
|
||||||
package top.r3944realms.ltdmanager.module
|
package top.r3944realms.ltdmanager.module
|
||||||
|
|
||||||
class StateModule {
|
import kotlinx.coroutines.*
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.account.SetQQProfileRequest
|
||||||
|
|
||||||
|
//TODO: 有问题不要使用 #unload得考虑下怎么写
|
||||||
|
class StateModule(
|
||||||
|
moduleName: String,
|
||||||
|
private val onlineName: String,
|
||||||
|
private val offlineName: String,
|
||||||
|
): BaseModule("StateModule", moduleName) {
|
||||||
|
private var scope: CoroutineScope? = null
|
||||||
|
override fun onLoad() {
|
||||||
|
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
scope!!.launch {
|
||||||
|
if (loaded) updateProfile(onlineName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private suspend fun updateProfile(name: String) {
|
||||||
|
napCatClient.sendUrgentUnit(SetQQProfileRequest(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onUnload() {
|
||||||
|
updateProfile(offlineName)
|
||||||
|
scope!!.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,342 @@
|
||||||
package top.r3944realms.ltdmanager.module.common
|
package top.r3944realms.ltdmanager.module.common
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高级命令解析器
|
||||||
|
* 支持自定义参数语法和参数验证
|
||||||
|
*/
|
||||||
class AdvancedCommandParser {
|
class AdvancedCommandParser {
|
||||||
}
|
private val commands = mutableListOf<CommandDefinition>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命令定义类
|
||||||
|
*/
|
||||||
|
data class CommandDefinition(
|
||||||
|
val name: String,
|
||||||
|
val aliases: List<String> = emptyList(),
|
||||||
|
val syntax: String = "",
|
||||||
|
val description: String = "",
|
||||||
|
val parameterPattern: Regex = DEFAULT_PARAMETER_PATTERN
|
||||||
|
) {
|
||||||
|
val allCommandForms: List<String> get() = listOf(name) + aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析结果
|
||||||
|
*/
|
||||||
|
data class ParseResult(
|
||||||
|
val command: String,
|
||||||
|
val arguments: Map<String, String> = emptyMap(),
|
||||||
|
val rawArguments: List<String> = emptyList(),
|
||||||
|
val isValid: Boolean = true,
|
||||||
|
val errorMessage: String? = null,
|
||||||
|
val commandDefinition: CommandDefinition? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// 默认参数模式:<参数名> 或 [可选参数名]
|
||||||
|
val DEFAULT_PARAMETER_PATTERN = Regex("""<(\w+)>|\[(\w+)]""")
|
||||||
|
|
||||||
|
// 常用参数模式
|
||||||
|
/**
|
||||||
|
* 必需参数
|
||||||
|
*/
|
||||||
|
val ANGLE_BRACKETS = Regex("""<(\w+)>""") // <param>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可选参数
|
||||||
|
*/
|
||||||
|
val SQUARE_BRACKETS = Regex("""\[(\w+)]""") // [param]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义参数类型
|
||||||
|
*/
|
||||||
|
val CURLY_BRACES = Regex("""\{(\w+)}""") // {param}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册命令
|
||||||
|
*/
|
||||||
|
fun registerCommand(
|
||||||
|
name: String,
|
||||||
|
aliases: List<String> = emptyList(),
|
||||||
|
syntax: String = "",
|
||||||
|
description: String = "",
|
||||||
|
parameterPattern: Regex = DEFAULT_PARAMETER_PATTERN
|
||||||
|
): AdvancedCommandParser {
|
||||||
|
commands.add(CommandDefinition(name, aliases, syntax, description, parameterPattern))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量注册命令
|
||||||
|
*/
|
||||||
|
fun registerCommands(vararg commandDefs: CommandDefinition): AdvancedCommandParser {
|
||||||
|
commands.addAll(commandDefs)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 智能分割参数,正确处理引号内的空格
|
||||||
|
*/
|
||||||
|
private fun smartSplit(input: String): List<String> {
|
||||||
|
val result = mutableListOf<String>()
|
||||||
|
val current = StringBuilder()
|
||||||
|
var inQuotes = false
|
||||||
|
var quoteChar: Char? = null
|
||||||
|
var escapeNext = false
|
||||||
|
|
||||||
|
for (char in input) {
|
||||||
|
when {
|
||||||
|
escapeNext -> {
|
||||||
|
current.append(char)
|
||||||
|
escapeNext = false
|
||||||
|
}
|
||||||
|
char == '\\' -> {
|
||||||
|
escapeNext = true
|
||||||
|
}
|
||||||
|
char == '"' || char == '\'' -> {
|
||||||
|
if (inQuotes && char == quoteChar) {
|
||||||
|
// 结束引号
|
||||||
|
inQuotes = false
|
||||||
|
quoteChar = null
|
||||||
|
} else if (!inQuotes) {
|
||||||
|
// 开始引号
|
||||||
|
inQuotes = true
|
||||||
|
quoteChar = char
|
||||||
|
} else {
|
||||||
|
current.append(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char == ' ' && !inQuotes -> {
|
||||||
|
// 空格分隔,但不是引号内
|
||||||
|
if (current.isNotEmpty()) {
|
||||||
|
result.add(current.toString())
|
||||||
|
current.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
current.append(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.isNotEmpty()) {
|
||||||
|
result.add(current.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 解析命令
|
||||||
|
*/
|
||||||
|
private fun parse(input: String): ParseResult {
|
||||||
|
val trimmedInput = input.trim()
|
||||||
|
if (trimmedInput.isEmpty()) {
|
||||||
|
return ParseResult("", isValid = false, errorMessage = "输入为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分割命令和参数
|
||||||
|
val parts = smartSplit(trimmedInput)
|
||||||
|
val commandPart = parts[0]
|
||||||
|
|
||||||
|
// 查找匹配的命令定义
|
||||||
|
val commandDef = commands.find { def ->
|
||||||
|
def.allCommandForms.any { it.equals(commandPart, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commandDef == null) {
|
||||||
|
return ParseResult(
|
||||||
|
commandPart,
|
||||||
|
isValid = false,
|
||||||
|
errorMessage = "未知命令: $commandPart"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析参数
|
||||||
|
val arguments = parseArguments(commandDef, parts.drop(1))
|
||||||
|
val rawArgs = parts.drop(1)
|
||||||
|
|
||||||
|
return ParseResult(
|
||||||
|
command = commandDef.name,
|
||||||
|
arguments = arguments,
|
||||||
|
rawArguments = rawArgs,
|
||||||
|
commandDefinition = commandDef
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析参数
|
||||||
|
*/
|
||||||
|
private fun parseArguments(commandDef: CommandDefinition, args: List<String>): Map<String, String> {
|
||||||
|
val parameters = extractParameterNames(commandDef.syntax, commandDef.parameterPattern)
|
||||||
|
val result = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
if (parameters.isEmpty()) {
|
||||||
|
args.forEachIndexed { index, value -> result["arg${index + 1}"] = value }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
val positionals = mutableListOf<String>()
|
||||||
|
val namedParams = mutableMapOf<String, String>()
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
// 第一遍:处理命名参数
|
||||||
|
while (i < args.size) {
|
||||||
|
when {
|
||||||
|
args[i].startsWith("--") -> {
|
||||||
|
val paramName = args[i].substring(2)
|
||||||
|
if (paramName in parameters) {
|
||||||
|
if (i + 1 < args.size && !args[i + 1].startsWith("-")) {
|
||||||
|
namedParams[paramName] = args[i + 1]
|
||||||
|
i += 2
|
||||||
|
} else {
|
||||||
|
namedParams[paramName] = "true"
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
positionals.add(args[i])
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args[i].startsWith("-") && args[i].length > 1 && !args[i].startsWith("--") -> {
|
||||||
|
val paramName = args[i].substring(1)
|
||||||
|
if (paramName in parameters) {
|
||||||
|
if (i + 1 < args.size && !args[i + 1].startsWith("-")) {
|
||||||
|
namedParams[paramName] = args[i + 1]
|
||||||
|
i += 2
|
||||||
|
} else {
|
||||||
|
namedParams[paramName] = "true"
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
positionals.add(args[i])
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
positionals.add(args[i])
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二遍:映射位置参数
|
||||||
|
var posIndex = 0
|
||||||
|
for (paramName in parameters) {
|
||||||
|
if (paramName !in namedParams && posIndex < positionals.size) {
|
||||||
|
result[paramName] = positionals[posIndex]
|
||||||
|
posIndex++
|
||||||
|
} else if (paramName in namedParams) {
|
||||||
|
result[paramName] = namedParams[paramName]!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理额外参数
|
||||||
|
for (j in posIndex until positionals.size) {
|
||||||
|
result["extraArg${j - posIndex + 1}"] = positionals[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从语法字符串中提取参数名
|
||||||
|
*/
|
||||||
|
private fun extractParameterNames(syntax: String, pattern: Regex): List<String> {
|
||||||
|
if (syntax.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return pattern.findAll(syntax).map { matchResult ->
|
||||||
|
matchResult.groupValues[1].ifEmpty { matchResult.groupValues[2] }
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证参数是否符合要求
|
||||||
|
*/
|
||||||
|
fun validateArguments(result: ParseResult): ParseResult {
|
||||||
|
if (!result.isValid) return result
|
||||||
|
|
||||||
|
val commandDef = result.commandDefinition ?: return result.copy(
|
||||||
|
isValid = false,
|
||||||
|
errorMessage = "命令定义不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
val requiredParams = extractParameterNames(commandDef.syntax, ANGLE_BRACKETS)
|
||||||
|
val missingParams = requiredParams.filter { it !in result.arguments }
|
||||||
|
|
||||||
|
return if (missingParams.isNotEmpty()) {
|
||||||
|
result.copy(
|
||||||
|
isValid = false,
|
||||||
|
errorMessage = "缺少必需参数: ${missingParams.joinToString()}"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取命令的帮助信息(增强版)
|
||||||
|
*/
|
||||||
|
fun getCommandHelp(commandName: String): String? {
|
||||||
|
val commandDef = commands.find { it.name == commandName || commandName in it.aliases }
|
||||||
|
return commandDef?.let { def ->
|
||||||
|
buildString {
|
||||||
|
appendLine("命令: ${def.name}")
|
||||||
|
if (def.aliases.isNotEmpty()) {
|
||||||
|
appendLine("别名: ${def.aliases.joinToString()}")
|
||||||
|
}
|
||||||
|
appendLine("用法: ${def.name} ${def.syntax}")
|
||||||
|
appendLine("描述: ${def.description}")
|
||||||
|
|
||||||
|
// 显示参数说明
|
||||||
|
val params = extractParameterNames(def.syntax, def.parameterPattern)
|
||||||
|
if (params.isNotEmpty()) {
|
||||||
|
appendLine("参数:")
|
||||||
|
params.forEach { param ->
|
||||||
|
val isRequired = def.syntax.contains("<$param>")
|
||||||
|
appendLine(" ${if (isRequired) "<$param>" else "[$param]"} - ${if (isRequired) "必需" else "可选"}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取所有注册的命令
|
||||||
|
*/
|
||||||
|
fun getRegisteredCommands(): List<CommandDefinition> = commands.toList()
|
||||||
|
/**
|
||||||
|
* 获取所有命令的帮助信息
|
||||||
|
*/
|
||||||
|
fun getAllCommandsHelp(): String {
|
||||||
|
return buildString {
|
||||||
|
appendLine("可用命令:")
|
||||||
|
appendLine("=".repeat(10))
|
||||||
|
commands.forEach { def ->
|
||||||
|
appendLine("${def.name} - ${def.description}")
|
||||||
|
if (def.aliases.isNotEmpty()) {
|
||||||
|
appendLine(" 别名: ${def.aliases.joinToString()}")
|
||||||
|
}
|
||||||
|
appendLine(" 用法: ${def.name} ${def.syntax}")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查输入是否包含有效命令
|
||||||
|
*/
|
||||||
|
fun containsCommand(input: String): Boolean {
|
||||||
|
val trimmedInput = input.trim()
|
||||||
|
if (trimmedInput.isEmpty()) return false
|
||||||
|
|
||||||
|
val commandPart = trimmedInput.split("\\s+".toRegex())[0]
|
||||||
|
return commands.any { def ->
|
||||||
|
def.allCommandForms.any { it.equals(commandPart, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快速解析(包含验证)
|
||||||
|
*/
|
||||||
|
fun parseAndValidate(input: String): ParseResult {
|
||||||
|
return validateArguments(parse(input))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package top.r3944realms.ltdmanager.module.common.filter
|
package top.r3944realms.ltdmanager.module.common.filter
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
|
||||||
interface MessageFilter {
|
interface MessageFilter {
|
||||||
suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean
|
suspend fun test(msg: MsgHistorySpecificMsg): Boolean
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package top.r3944realms.ltdmanager.module.common.filter
|
package top.r3944realms.ltdmanager.module.common.filter
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
|
||||||
class TriggerMessageFilter(private val filters: List<MessageFilter>) {
|
class TriggerMessageFilter(private val filters: List<MessageFilter>) {
|
||||||
suspend fun filter(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>)
|
suspend fun filter(messages: List<MsgHistorySpecificMsg>)
|
||||||
: List<GetFriendMsgHistoryEvent.SpecificMsg> {
|
: List<MsgHistorySpecificMsg> {
|
||||||
|
|
||||||
val result = mutableListOf<GetFriendMsgHistoryEvent.SpecificMsg>()
|
val result = mutableListOf<MsgHistorySpecificMsg>()
|
||||||
for (msg in messages) {
|
for (msg in messages) {
|
||||||
if (filters.all { it.test(msg) }) {
|
if (filters.all { it.test(msg) }) {
|
||||||
result.add(msg)
|
result.add(msg)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,17 @@
|
||||||
package top.r3944realms.ltdmanager.module.common.filter.type
|
package top.r3944realms.ltdmanager.module.common.filter.type
|
||||||
|
|
||||||
class AdvancedCommonFilter {
|
import top.r3944realms.ltdmanager.module.common.AdvancedCommandParser
|
||||||
|
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
|
||||||
|
class AdvancedCommonFilter(private val advancedCommandParser: AdvancedCommandParser): MessageFilter {
|
||||||
|
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||||
|
return msg.message.any { seg ->
|
||||||
|
seg.type == MessageType.Text && seg.data.text?.let { text ->
|
||||||
|
advancedCommandParser.getRegisteredCommands().map { it.name }.any { name -> text.startsWith(name) }
|
||||||
|
} == true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,11 +3,11 @@ package top.r3944realms.ltdmanager.module.common.filter.type
|
||||||
import top.r3944realms.ltdmanager.module.common.CommandParser
|
import top.r3944realms.ltdmanager.module.common.CommandParser
|
||||||
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
|
||||||
/** 命令解析器匹配 */
|
/** 命令解析器匹配 */
|
||||||
class CommandFilter(private val parser: CommandParser) : MessageFilter {
|
class CommandFilter(private val parser: CommandParser) : MessageFilter {
|
||||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||||
return msg.message.any { seg ->
|
return msg.message.any { seg ->
|
||||||
seg.type == MessageType.Text && seg.data.text?.let { parser.containsCommand(it) } == true
|
seg.type == MessageType.Text && seg.data.text?.let { parser.containsCommand(it) } == true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ package top.r3944realms.ltdmanager.module.common.filter.type
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.module.common.cooldown.CooldownManager
|
import top.r3944realms.ltdmanager.module.common.cooldown.CooldownManager
|
||||||
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
|
||||||
class CooldownFilter(
|
class CooldownFilter(
|
||||||
private val cooldownManager: CooldownManager<*>,
|
private val cooldownManager: CooldownManager<*>,
|
||||||
private val sendCooldown: suspend (GetFriendMsgHistoryEvent.SpecificMsg, Long) -> Unit
|
private val sendCooldown: suspend (MsgHistorySpecificMsg, Long) -> Unit
|
||||||
) : MessageFilter {
|
) : MessageFilter {
|
||||||
|
|
||||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||||
val result = cooldownManager.checkAndHandle(msg.userId, msg.realId)
|
val result = cooldownManager.checkAndHandle(msg.userId, msg.realId)
|
||||||
if (!result.allowed && result.notify) {
|
if (!result.allowed && result.notify) {
|
||||||
sendCooldown(msg, result.remaining)
|
sendCooldown(msg, result.remaining)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package top.r3944realms.ltdmanager.module.common.filter.type
|
package top.r3944realms.ltdmanager.module.common.filter.type
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
|
||||||
/** 忽略机器人自己的消息 */
|
/** 忽略机器人自己的消息 */
|
||||||
class IgnoreSelfFilter(private val selfId: Long) : MessageFilter {
|
class IgnoreSelfFilter(private val selfId: Long) : MessageFilter {
|
||||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||||
return msg.userId != selfId
|
return msg.userId != selfId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,11 +2,11 @@ package top.r3944realms.ltdmanager.module.common.filter.type
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
|
||||||
/** 文本关键词匹配 */
|
/** 文本关键词匹配 */
|
||||||
class KeywordFilter(private val keywords: Set<String>) : MessageFilter {
|
class KeywordFilter(private val keywords: Set<String>) : MessageFilter {
|
||||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||||
return msg.message.any { seg ->
|
return msg.message.any { seg ->
|
||||||
seg.type == MessageType.Text && seg.data.text?.let { text ->
|
seg.type == MessageType.Text && seg.data.text?.let { text ->
|
||||||
keywords.any { keyword -> text.startsWith(keyword) }
|
keywords.any { keyword -> text.startsWith(keyword) }
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ package top.r3944realms.ltdmanager.module.common.filter.type
|
||||||
import top.r3944realms.ltdmanager.module.common.CommandParser
|
import top.r3944realms.ltdmanager.module.common.CommandParser
|
||||||
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
|
||||||
/** 多命令解析器匹配 */
|
/** 多命令解析器匹配 */
|
||||||
class MultiCommandFilter(private val parsers: List<CommandParser>) : MessageFilter {
|
class MultiCommandFilter(private val parsers: List<CommandParser>) : MessageFilter {
|
||||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||||
return msg.message.any { seg ->
|
return msg.message.any { seg ->
|
||||||
seg.type == MessageType.Text && seg.data.text?.let { text ->
|
seg.type == MessageType.Text && seg.data.text?.let { text ->
|
||||||
parsers.any { parser -> parser.containsCommand(text) }
|
parsers.any { parser -> parser.containsCommand(text) }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package top.r3944realms.ltdmanager.module.common.filter.type
|
package top.r3944realms.ltdmanager.module.common.filter.type
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
import top.r3944realms.ltdmanager.module.common.filter.MessageFilter
|
||||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
import top.r3944realms.ltdmanager.utils.Environment
|
import top.r3944realms.ltdmanager.utils.Environment
|
||||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@ import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
class NewMessageFilter(
|
class NewMessageFilter(
|
||||||
private val getLastTrigger: (Long) -> Pair<Long, Long> // (time, realId)
|
private val getLastTrigger: (Long) -> Pair<Long, Long> // (time, realId)
|
||||||
) : MessageFilter {
|
) : MessageFilter {
|
||||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||||
val (lastTime, lastRealId) = getLastTrigger(msg.userId)
|
val (lastTime, lastRealId) = getLastTrigger(msg.userId)
|
||||||
val result = msg.time > lastTime || (msg.time == lastTime && msg.realId > lastRealId)
|
val result = msg.time > lastTime || (msg.time == lastTime && msg.realId > lastRealId)
|
||||||
if (Environment.isDevelopment()) LoggerUtil.logger.debug("NewMessageFilter: msg.time=${msg.time}, msg.realId=${msg.realId}, lastTime=$lastTime, lastRealId=$lastRealId, result=$result")
|
if (Environment.isDevelopment()) LoggerUtil.logger.debug("NewMessageFilter: msg.time=${msg.time}, msg.realId=${msg.realId}, lastTime=$lastTime, lastRealId=$lastRealId, result=$result")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,35 @@
|
||||||
package top.r3944realms.ltdmanager.napcat.data
|
package top.r3944realms.ltdmanager.napcat.data
|
||||||
|
|
||||||
class GroupMember {
|
import kotlinx.serialization.SerialName
|
||||||
}
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GroupMember(
|
||||||
|
@SerialName("group_id")
|
||||||
|
val groupId: Long,
|
||||||
|
@SerialName("user_id")
|
||||||
|
val userId: Long,
|
||||||
|
val nickname: String,
|
||||||
|
val card: String,
|
||||||
|
val sex: String,
|
||||||
|
val age: Int,
|
||||||
|
val area: String,
|
||||||
|
val level: String,
|
||||||
|
@SerialName("qq_level")
|
||||||
|
val qqLevel: Int,
|
||||||
|
@SerialName("join_time")
|
||||||
|
val joinTime: Long,
|
||||||
|
@SerialName("last_sent_time")
|
||||||
|
val lastSentTime: Long,
|
||||||
|
@SerialName("title_expire_time")
|
||||||
|
val titleExpireTime: Long,
|
||||||
|
val unfriendly: Boolean,
|
||||||
|
@SerialName("card_changeable")
|
||||||
|
val cardChangeable: Boolean,
|
||||||
|
@SerialName("is_robot")
|
||||||
|
val isRobot: Boolean,
|
||||||
|
@SerialName("shut_up_timestamp")
|
||||||
|
val shutUpTimestamp: Long,
|
||||||
|
val role: String,
|
||||||
|
val title: String
|
||||||
|
)
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
|
@file:Suppress("EXTERNAL_SERIALIZER_USELESS")
|
||||||
|
|
||||||
package top.r3944realms.ltdmanager.napcat.data.msghistory
|
package top.r3944realms.ltdmanager.napcat.data.msghistory
|
||||||
|
|
||||||
class MsgHistoryContent {
|
import kotlinx.serialization.Serializable
|
||||||
|
import top.r3944realms.ltdmanager.napcat.serializer.MsgHistoryContentSerializer
|
||||||
|
|
||||||
|
@Serializable(with = MsgHistoryContentSerializer::class)
|
||||||
|
sealed class MsgHistoryContent {
|
||||||
|
@Serializable
|
||||||
|
class StringValue(val value: String) : MsgHistoryContent()
|
||||||
|
@Serializable
|
||||||
|
class SpecificMsgList(val value: List<MsgHistorySpecificMsg>) : MsgHistoryContent()
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,33 @@
|
||||||
package top.r3944realms.ltdmanager.napcat.data.msghistory
|
package top.r3944realms.ltdmanager.napcat.data.msghistory
|
||||||
|
|
||||||
class MsgHistoryMessage {
|
import kotlinx.serialization.Serializable
|
||||||
}
|
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本消息
|
||||||
|
*
|
||||||
|
* 艾特消息
|
||||||
|
*
|
||||||
|
* 表情消息
|
||||||
|
*
|
||||||
|
* 图片消息
|
||||||
|
*
|
||||||
|
* 文件消息
|
||||||
|
*
|
||||||
|
* 回复消息
|
||||||
|
*
|
||||||
|
* JSON消息
|
||||||
|
*
|
||||||
|
* 语音消息
|
||||||
|
*
|
||||||
|
* 视频消息
|
||||||
|
*
|
||||||
|
* markdown消息
|
||||||
|
*
|
||||||
|
* 消息forward
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class MsgHistoryMessage (
|
||||||
|
val data: MsgHistoryMessageData,
|
||||||
|
val type: MessageType
|
||||||
|
)
|
||||||
|
|
@ -1,4 +1,55 @@
|
||||||
package top.r3944realms.ltdmanager.napcat.data.msghistory
|
package top.r3944realms.ltdmanager.napcat.data.msghistory
|
||||||
|
|
||||||
class MsgHistorySpecificMsg {
|
import kotlinx.serialization.SerialName
|
||||||
}
|
import kotlinx.serialization.Serializable
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.Sender
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息详情
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class MsgHistorySpecificMsg (
|
||||||
|
val font: Long,
|
||||||
|
|
||||||
|
@SerialName("group_id")
|
||||||
|
val groupId: Long? = null,
|
||||||
|
|
||||||
|
val message: List<MsgHistoryMessage>,
|
||||||
|
|
||||||
|
@SerialName("message_format")
|
||||||
|
val messageFormat: String,
|
||||||
|
|
||||||
|
@SerialName("message_id")
|
||||||
|
val messageId: Long,
|
||||||
|
|
||||||
|
@SerialName("message_seq")
|
||||||
|
val messageSeq: Long,
|
||||||
|
|
||||||
|
@SerialName("message_type")
|
||||||
|
val messageType: String,
|
||||||
|
|
||||||
|
@SerialName("post_type")
|
||||||
|
val postType: String,
|
||||||
|
|
||||||
|
@SerialName("raw_message")
|
||||||
|
val rawMessage: String,
|
||||||
|
|
||||||
|
@SerialName("real_id")
|
||||||
|
val realId: Long,
|
||||||
|
|
||||||
|
@SerialName("real_seq")
|
||||||
|
val realSeq: String,
|
||||||
|
|
||||||
|
@SerialName("self_id")
|
||||||
|
val selfId: Long,
|
||||||
|
|
||||||
|
val sender: Sender,
|
||||||
|
|
||||||
|
@SerialName("sub_type")
|
||||||
|
val subType: String,
|
||||||
|
|
||||||
|
val time: Long,
|
||||||
|
|
||||||
|
@SerialName("user_id")
|
||||||
|
val userId: Long
|
||||||
|
)
|
||||||
|
|
@ -3,7 +3,7 @@ package top.r3944realms.ltdmanager.napcat.event.group
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import kotlinx.serialization.json.JsonArray
|
import top.r3944realms.ltdmanager.napcat.data.GroupMember
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetGroupMemberList事件
|
* GetGroupMemberList事件
|
||||||
|
|
@ -22,7 +22,7 @@ data class GetGroupMemberListEvent(
|
||||||
@Transient
|
@Transient
|
||||||
val echo0: String? = null,
|
val echo0: String? = null,
|
||||||
|
|
||||||
val data: JsonArray
|
val data: List<GroupMember>
|
||||||
) : AbstractGroupEvent(status0, retcode0, message0, wording0, echo0) {
|
) : AbstractGroupEvent(status0, retcode0, message0, wording0, echo0) {
|
||||||
|
|
||||||
override fun subtype(): String {
|
override fun subtype(): String {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
|
|
||||||
package top.r3944realms.ltdmanager.napcat.event.message
|
package top.r3944realms.ltdmanager.napcat.event.message
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
|
||||||
import top.r3944realms.ltdmanager.napcat.data.Sender
|
|
||||||
import top.r3944realms.ltdmanager.napcat.event.group.AbstractGroupEvent
|
import top.r3944realms.ltdmanager.napcat.event.group.AbstractGroupEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -30,106 +27,9 @@ data class GetFriendMsgHistoryEvent(
|
||||||
) : AbstractGroupEvent(status0, retcode0, message0, wording0, echo0) {
|
) : AbstractGroupEvent(status0, retcode0, message0, wording0, echo0) {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Data (
|
data class Data (
|
||||||
val messages: List<SpecificMsg>
|
val messages: List<MsgHistorySpecificMsg>
|
||||||
)
|
)
|
||||||
@Serializable
|
|
||||||
sealed class Content {
|
|
||||||
class StringValue(val value: String) : Content()
|
|
||||||
class SpecificMsgList(val value: List<SpecificMsg>) : Content()
|
|
||||||
}
|
|
||||||
@Serializable
|
|
||||||
data class MessageData (
|
|
||||||
val text: String? = null,
|
|
||||||
val name: String? = null,
|
|
||||||
val qq: ID? = null,
|
|
||||||
val id: ID? = null,
|
|
||||||
val file: String? = null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 外显
|
|
||||||
*/
|
|
||||||
val summary: String? = null,
|
|
||||||
|
|
||||||
val data: String? = null,
|
|
||||||
val content: Content? = null
|
|
||||||
)
|
|
||||||
/**
|
|
||||||
* 文本消息
|
|
||||||
*
|
|
||||||
* 艾特消息
|
|
||||||
*
|
|
||||||
* 表情消息
|
|
||||||
*
|
|
||||||
* 图片消息
|
|
||||||
*
|
|
||||||
* 文件消息
|
|
||||||
*
|
|
||||||
* 回复消息
|
|
||||||
*
|
|
||||||
* JSON消息
|
|
||||||
*
|
|
||||||
* 语音消息
|
|
||||||
*
|
|
||||||
* 视频消息
|
|
||||||
*
|
|
||||||
* markdown消息
|
|
||||||
*
|
|
||||||
* 消息forward
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class Message (
|
|
||||||
val data: MessageData,
|
|
||||||
val type: MessageType
|
|
||||||
)
|
|
||||||
/**
|
|
||||||
* 消息详情
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class SpecificMsg (
|
|
||||||
val font: Long,
|
|
||||||
|
|
||||||
@SerialName("group_id")
|
|
||||||
val groupId: Long? = null,
|
|
||||||
|
|
||||||
val message: List<Message>,
|
|
||||||
|
|
||||||
@SerialName("message_format")
|
|
||||||
val messageFormat: String,
|
|
||||||
|
|
||||||
@SerialName("message_id")
|
|
||||||
val messageId: Long,
|
|
||||||
|
|
||||||
@SerialName("message_seq")
|
|
||||||
val messageSeq: Long,
|
|
||||||
|
|
||||||
@SerialName("message_type")
|
|
||||||
val messageType: String,
|
|
||||||
|
|
||||||
@SerialName("post_type")
|
|
||||||
val postType: String,
|
|
||||||
|
|
||||||
@SerialName("raw_message")
|
|
||||||
val rawMessage: String,
|
|
||||||
|
|
||||||
@SerialName("real_id")
|
|
||||||
val realId: Long,
|
|
||||||
|
|
||||||
@SerialName("real_seq")
|
|
||||||
val realSeq: String,
|
|
||||||
|
|
||||||
@SerialName("self_id")
|
|
||||||
val selfId: Long,
|
|
||||||
|
|
||||||
val sender: Sender,
|
|
||||||
|
|
||||||
@SerialName("sub_type")
|
|
||||||
val subType: String,
|
|
||||||
|
|
||||||
val time: Long,
|
|
||||||
|
|
||||||
@SerialName("user_id")
|
|
||||||
val userId: Long
|
|
||||||
)
|
|
||||||
override fun subtype(): String {
|
override fun subtype(): String {
|
||||||
return "get_friend_msg_history"
|
return "get_friend_msg_history"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package top.r3944realms.ltdmanager.napcat.event.message
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
import top.r3944realms.ltdmanager.napcat.event.group.AbstractGroupEvent
|
import top.r3944realms.ltdmanager.napcat.event.group.AbstractGroupEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -22,9 +23,13 @@ data class GetGroupMsgHistoryEvent(
|
||||||
@Transient
|
@Transient
|
||||||
val echo0: String? = null,
|
val echo0: String? = null,
|
||||||
|
|
||||||
val data: GetFriendMsgHistoryEvent.Data
|
val data: Data
|
||||||
) : AbstractGroupEvent(status0, retcode0, message0, wording0, echo0) {
|
) : AbstractGroupEvent(status0, retcode0, message0, wording0, echo0) {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Data (
|
||||||
|
val messages: List<MsgHistorySpecificMsg>
|
||||||
|
)
|
||||||
override fun subtype(): String {
|
override fun subtype(): String {
|
||||||
return "get_group_msg_history"
|
return "get_group_msg_history"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ data class GetGroupMemberListRequest(
|
||||||
@SerialName("no_cache")
|
@SerialName("no_cache")
|
||||||
val noCache: Boolean,
|
val noCache: Boolean,
|
||||||
|
|
||||||
@SerialName("user_id")
|
|
||||||
val userId: ID
|
|
||||||
) : AbstractGroupRequest() {
|
) : AbstractGroupRequest() {
|
||||||
override fun toJSON(): String = Json.encodeToString(this)
|
override fun toJSON(): String = Json.encodeToString(this)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,68 @@
|
||||||
package top.r3944realms.ltdmanager.napcat.serializer
|
package top.r3944realms.ltdmanager.napcat.serializer
|
||||||
|
|
||||||
object MsgHistoryContentSerializer {
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistoryContent
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||||
|
|
||||||
|
object MsgHistoryContentSerializer : KSerializer<MsgHistoryContent> {
|
||||||
|
|
||||||
|
// 创建宽松的 JSON 配置
|
||||||
|
private val json = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
coerceInputValues = true
|
||||||
|
isLenient = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("MsgHistoryContent")
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: MsgHistoryContent) {
|
||||||
|
val jsonEncoder = encoder as? JsonEncoder
|
||||||
|
?: throw SerializationException("只能使用JSON编码器")
|
||||||
|
|
||||||
|
when (value) {
|
||||||
|
is MsgHistoryContent.SpecificMsgList -> {
|
||||||
|
val jsonArray = JsonArray(value.value.map { specificMsg ->
|
||||||
|
json.encodeToJsonElement(specificMsg)
|
||||||
|
})
|
||||||
|
jsonEncoder.encodeJsonElement(jsonArray)
|
||||||
|
}
|
||||||
|
is MsgHistoryContent.StringValue -> {
|
||||||
|
jsonEncoder.encodeJsonElement(JsonPrimitive(value.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): MsgHistoryContent {
|
||||||
|
val jsonDecoder = decoder as? JsonDecoder
|
||||||
|
?: throw SerializationException("只能使用JSON解码器")
|
||||||
|
|
||||||
|
return when (val jsonElement = jsonDecoder.decodeJsonElement()) {
|
||||||
|
is JsonArray -> {
|
||||||
|
try {
|
||||||
|
val specificMsgList = jsonElement.map { element ->
|
||||||
|
json.decodeFromJsonElement<MsgHistorySpecificMsg>(element)
|
||||||
|
}
|
||||||
|
MsgHistoryContent.SpecificMsgList(specificMsgList)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw SerializationException("无法将JsonArray解析为List<MsgHistorySpecificMsg>: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is JsonPrimitive -> {
|
||||||
|
if (jsonElement.isString) {
|
||||||
|
MsgHistoryContent.StringValue(jsonElement.content)
|
||||||
|
} else {
|
||||||
|
throw SerializationException("不支持的非字符串原始类型")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw SerializationException("不支持的JSON元素类型: ${jsonElement::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,4 +57,24 @@ object QRCodeUtil {
|
||||||
}
|
}
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
@Throws(IOException::class, WriterException::class)
|
||||||
|
fun generateQRCodeToFile(
|
||||||
|
text: String,
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
|
filePath: String
|
||||||
|
) {
|
||||||
|
val hints: MutableMap<EncodeHintType, Any> = EnumMap(EncodeHintType::class.java)
|
||||||
|
hints[EncodeHintType.CHARACTER_SET] = CHARSET
|
||||||
|
|
||||||
|
// 生成二维码矩阵
|
||||||
|
val bitMatrix = MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints)
|
||||||
|
|
||||||
|
// 转成 BufferedImage
|
||||||
|
val image = toBufferedImage(bitMatrix)
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
val outputFile = java.io.File(filePath)
|
||||||
|
ImageIO.write(image, FORMAT, outputFile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,3 +101,6 @@ dg-lab:
|
||||||
enable-debug: false
|
enable-debug: false
|
||||||
ide-host: "127.0.0.1"
|
ide-host: "127.0.0.1"
|
||||||
ide-port: 5678
|
ide-port: 5678
|
||||||
|
img-tu:
|
||||||
|
url: https://mysite.com/api/1/upload
|
||||||
|
encrypted-password: 11223344bbcc
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,34 @@
|
||||||
package top.r394realms.ltdmanagertest.command
|
package top.r394realms.ltdmanagertest.command
|
||||||
|
|
||||||
|
import com.mojang.brigadier.CommandDispatcher
|
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder.literal
|
||||||
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder.argument
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val dispatcher = CommandDispatcher<String>() // String 表示消息来源
|
||||||
|
|
||||||
|
dispatcher.register(
|
||||||
|
literal<String>("say")
|
||||||
|
.then(argument<String, String>("message", StringArgumentType.greedyString())
|
||||||
|
.executes { ctx ->
|
||||||
|
val msg = StringArgumentType.getString(ctx, "message")
|
||||||
|
println("[BOT] $msg")
|
||||||
|
1
|
||||||
|
})
|
||||||
|
)
|
||||||
|
dispatcher.register(
|
||||||
|
literal<String>("add")
|
||||||
|
.then(argument<String, Int>("a", IntegerArgumentType.integer())
|
||||||
|
.then(argument<String, Int>("b", IntegerArgumentType.integer())
|
||||||
|
.executes { ctx ->
|
||||||
|
val a = IntegerArgumentType.getInteger(ctx, "a")
|
||||||
|
val b = IntegerArgumentType.getInteger(ctx, "b")
|
||||||
|
println("[BOT] $a + $b = ${a + b}")
|
||||||
|
1
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
dispatcher.execute("say Hello World", "user123")
|
||||||
|
dispatcher.execute("add 3 7", "user123")
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,197 @@
|
||||||
package top.r394realms.ltdmanagertest.command
|
package top.r394realms.ltdmanagertest.command
|
||||||
|
|
||||||
|
import top.r3944realms.ltdmanager.module.common.AdvancedCommandParser
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数提取演示类
|
||||||
|
*/
|
||||||
class ParameterExtractionDemo {
|
class ParameterExtractionDemo {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// 默认参数模式:<参数名> 或 [可选参数名]
|
||||||
|
val DEFAULT_PARAMETER_PATTERN = Regex("""<(\w+)>|\[(\w+)]""")
|
||||||
|
|
||||||
|
// 常用参数模式
|
||||||
|
val ANGLE_BRACKETS = Regex("""<(\w+)>""") // <param> - 必需参数
|
||||||
|
val SQUARE_BRACKETS = Regex("""\[(\w+)]""") // [param] - 可选参数
|
||||||
|
val CURLY_BRACES = Regex("""\{(\w+)}""") // {param} - 自定义参数
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从语法字符串中提取参数名
|
||||||
|
*/
|
||||||
|
fun extractParameterNames(syntax: String, pattern: Regex): List<String> {
|
||||||
|
if (syntax.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return pattern.findAll(syntax).map { matchResult ->
|
||||||
|
// 从捕获组中提取参数名(处理不同的括号类型)
|
||||||
|
matchResult.groupValues[1].ifEmpty { matchResult.groupValues[2] }
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示场景1:只需要必需参数(参数验证)
|
||||||
|
*/
|
||||||
|
fun demoRequiredParameters() {
|
||||||
|
println("=== 场景1:必需参数验证 ===")
|
||||||
|
|
||||||
|
val syntax = "send <message> [target] [priority]"
|
||||||
|
|
||||||
|
val parser = AdvancedCommandParser().apply {
|
||||||
|
registerCommand(
|
||||||
|
"ls",
|
||||||
|
syntax = syntax,
|
||||||
|
parameterPattern = ANGLE_BRACKETS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
println(parser.getCommandHelp("ls"))
|
||||||
|
|
||||||
|
// 模拟用户输入验证
|
||||||
|
val testCases = listOf(
|
||||||
|
"ls send Hello", // 有效:提供了必需参数
|
||||||
|
"ls send Hello @all", // 有效:提供了必需参数和可选参数
|
||||||
|
"ls send" // 无效:缺少必需参数
|
||||||
|
)
|
||||||
|
|
||||||
|
testCases.forEach { input ->
|
||||||
|
println("输入: $input")
|
||||||
|
val result = parser.parseAndValidate(input)
|
||||||
|
if (result.isValid) {
|
||||||
|
println("✓ 命令: ${result.command}")
|
||||||
|
println("✓ 参数:")
|
||||||
|
result.arguments.forEach { (key, value) ->
|
||||||
|
println(" $key: $value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("✗ 错误: ${result.errorMessage}")
|
||||||
|
}
|
||||||
|
println("-".repeat(50))
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示场景2:需要所有参数(完整解析)
|
||||||
|
*/
|
||||||
|
fun demoAllParameters() {
|
||||||
|
println("=== 场景2:完整参数解析 ===")
|
||||||
|
|
||||||
|
val syntax = "user <action> <id> [name] [age] [email]"
|
||||||
|
val parser = AdvancedCommandParser().apply {
|
||||||
|
registerCommand(
|
||||||
|
"ls",
|
||||||
|
syntax = syntax,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
println(parser.getCommandHelp("ls"))
|
||||||
|
|
||||||
|
// 模拟参数映射
|
||||||
|
val testInput = "ls user add 123 John 30 john@example.com"
|
||||||
|
|
||||||
|
println("输入: $testInput")
|
||||||
|
val result = parser.parseAndValidate(testInput)
|
||||||
|
if (result.isValid) {
|
||||||
|
println("✓ 命令: ${result.command}")
|
||||||
|
println("✓ 参数:")
|
||||||
|
result.arguments.forEach { (key, value) ->
|
||||||
|
println(" $key: $value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("✗ 错误: ${result.errorMessage}")
|
||||||
|
}
|
||||||
|
println("-".repeat(50))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示场景3:自定义参数格式
|
||||||
|
*/
|
||||||
|
fun demoCustomParameters() {
|
||||||
|
println("=== 场景3:自定义参数格式 ===")
|
||||||
|
|
||||||
|
val customSyntax = "execute {command} {args} --timeout {timeout} --retry {retries}"
|
||||||
|
extractParameterNames(customSyntax, CURLY_BRACES)
|
||||||
|
val parser = AdvancedCommandParser().apply {
|
||||||
|
registerCommand(
|
||||||
|
"ls",
|
||||||
|
syntax = customSyntax,
|
||||||
|
parameterPattern = CURLY_BRACES,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
println(parser.getCommandHelp("ls"))
|
||||||
|
|
||||||
|
// 模拟命名参数解析
|
||||||
|
val testInput = "ls execute {ls -la} {--help} --timeout 30 --retry 3"
|
||||||
|
val result = parser.parseAndValidate(testInput)
|
||||||
|
if (result.isValid) {
|
||||||
|
println("✓ 命令: ${result.command}")
|
||||||
|
println("✓ 参数:")
|
||||||
|
result.arguments.forEach { (key, value) ->
|
||||||
|
println(" $key: $value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("✗ 错误: ${result.errorMessage}")
|
||||||
|
}
|
||||||
|
println("-".repeat(5))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 综合演示:完整的命令处理流程
|
||||||
|
*/
|
||||||
|
fun demoCompleteWorkflow() {
|
||||||
|
println("=== 综合演示:完整工作流程 ===")
|
||||||
|
|
||||||
|
// 定义复杂的命令语法
|
||||||
|
val syntax = "ls database <operation> <table> [where] [limit] [offset] --format {format}"
|
||||||
|
val parser1 = AdvancedCommandParser().apply {
|
||||||
|
registerCommand(
|
||||||
|
"ls",
|
||||||
|
syntax = syntax,
|
||||||
|
parameterPattern = DEFAULT_PARAMETER_PATTERN,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟真实命令处理
|
||||||
|
val testCommand = "ls database select users --where \"age > 18\" --limit 10 --format json"
|
||||||
|
val result = parser1.parseAndValidate(testCommand)
|
||||||
|
if (result.isValid) {
|
||||||
|
println("✓ 命令: ${result.command}")
|
||||||
|
println("✓ 参数:")
|
||||||
|
result.arguments.forEach { (key, value) ->
|
||||||
|
println(" $key: $value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("✗ 错误: ${result.errorMessage}")
|
||||||
|
}
|
||||||
|
println("-".repeat(5))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主函数运行演示
|
||||||
|
*/
|
||||||
|
fun main() {
|
||||||
|
val demo = ParameterExtractionDemo()
|
||||||
|
|
||||||
|
// 运行各个演示场景
|
||||||
|
demo.demoRequiredParameters()
|
||||||
|
demo.demoAllParameters()
|
||||||
|
demo.demoCustomParameters()
|
||||||
|
demo.demoCompleteWorkflow()
|
||||||
|
|
||||||
|
// 额外演示:不同语法模式对比
|
||||||
|
println("\n=== 语法模式对比 ===")
|
||||||
|
val syntaxes = listOf(
|
||||||
|
"cmd <req1> <req2> [opt1] [opt2]",
|
||||||
|
"run {command} {args}",
|
||||||
|
"test <input> [output] --mode {mode} --verbose {flag}"
|
||||||
|
)
|
||||||
|
|
||||||
|
syntaxes.forEach { syntax ->
|
||||||
|
println("\n语法: $syntax")
|
||||||
|
println("尖括号参数: ${demo.extractParameterNames(syntax, ParameterExtractionDemo.ANGLE_BRACKETS)}")
|
||||||
|
println("方括号参数: ${demo.extractParameterNames(syntax, ParameterExtractionDemo.SQUARE_BRACKETS)}")
|
||||||
|
println("花括号参数: ${demo.extractParameterNames(syntax, ParameterExtractionDemo.CURLY_BRACES)}")
|
||||||
|
println("所有参数: ${demo.extractParameterNames(syntax, ParameterExtractionDemo.DEFAULT_PARAMETER_PATTERN)}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,54 @@
|
||||||
package top.r394realms.ltdmanagertest.command
|
package top.r394realms.ltdmanagertest.command
|
||||||
|
|
||||||
class testACP {
|
import top.r3944realms.ltdmanager.module.common.AdvancedCommandParser
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val parser = AdvancedCommandParser().apply {
|
||||||
|
registerCommand(
|
||||||
|
name = "send",
|
||||||
|
aliases = listOf("s"),
|
||||||
|
syntax = "<message> [target] [priority]",
|
||||||
|
description = "发送消息到指定目标"
|
||||||
|
)
|
||||||
|
|
||||||
|
registerCommand(
|
||||||
|
name = "user",
|
||||||
|
aliases = listOf("u"),
|
||||||
|
syntax = "<action> <id> [name] [email] --role {role}",
|
||||||
|
description = "用户管理命令"
|
||||||
|
)
|
||||||
|
|
||||||
|
registerCommand(
|
||||||
|
name = "database",
|
||||||
|
aliases = listOf("db"),
|
||||||
|
syntax = "<operation> <table> [where] [limit] --format {format}",
|
||||||
|
description = "数据库操作命令"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试复杂命令
|
||||||
|
val testCommands = listOf(
|
||||||
|
"database select users --where \"age > 18 and name = 'John Doe'\" --limit 10 --format json",
|
||||||
|
"send \"Hello, World!\" @all --priority high",
|
||||||
|
"user add 123 --email john@example.com --role admin --name \"John Smith\"",
|
||||||
|
"invalid command test"
|
||||||
|
)
|
||||||
|
|
||||||
|
testCommands.forEach { input ->
|
||||||
|
println("输入: $input")
|
||||||
|
val result = parser.parseAndValidate(input)
|
||||||
|
if (result.isValid) {
|
||||||
|
println("✓ 命令: ${result.command}")
|
||||||
|
println("✓ 参数:")
|
||||||
|
result.arguments.forEach { (key, value) ->
|
||||||
|
println(" $key: $value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("✗ 错误: ${result.errorMessage}")
|
||||||
|
}
|
||||||
|
println("-".repeat(50))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示帮助信息
|
||||||
|
println(parser.getCommandHelp("database") ?: "命令未找到")
|
||||||
}
|
}
|
||||||
|
|
@ -1,2 +1,48 @@
|
||||||
package top.r394realms.ltdmanagertest.command
|
package top.r394realms.ltdmanagertest.command
|
||||||
|
|
||||||
|
import top.r3944realms.ltdmanager.module.common.AdvancedCommandParser
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val parser = AdvancedCommandParser().apply {
|
||||||
|
registerCommand(
|
||||||
|
name = "send",
|
||||||
|
aliases = listOf("s"),
|
||||||
|
syntax = "<message> [target]",
|
||||||
|
description = "发送消息到指定目标"
|
||||||
|
)
|
||||||
|
|
||||||
|
registerCommand(
|
||||||
|
name = "user",
|
||||||
|
aliases = listOf("u"),
|
||||||
|
syntax = "<action> <id> [options]",
|
||||||
|
description = "用户管理命令"
|
||||||
|
)
|
||||||
|
|
||||||
|
registerCommand(
|
||||||
|
name = "config",
|
||||||
|
aliases = listOf("cfg"),
|
||||||
|
syntax = "set <key> <value> | get <key>",
|
||||||
|
description = "配置管理",
|
||||||
|
parameterPattern = AdvancedCommandParser.ANGLE_BRACKETS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试解析
|
||||||
|
val testInputs = listOf(
|
||||||
|
"send Hello World",
|
||||||
|
"user add 123 --name John",
|
||||||
|
"config set theme dark",
|
||||||
|
"invalid command"
|
||||||
|
)
|
||||||
|
|
||||||
|
testInputs.forEach { input ->
|
||||||
|
println("输入: $input")
|
||||||
|
val result = parser.parseAndValidate(input)
|
||||||
|
println("结果: $result")
|
||||||
|
println("---")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取帮助信息
|
||||||
|
println("帮助信息:")
|
||||||
|
println(parser.getCommandHelp("send"))
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package top.r394realms.ltdmanagertest.help
|
package top.r394realms.ltdmanagertest.help
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.GlobalManager
|
import top.r3944realms.ltdmanager.GlobalManager
|
||||||
import top.r3944realms.ltdmanager.module.BanModule
|
import top.r3944realms.ltdmanager.module.DGLabModule
|
||||||
import top.r3944realms.ltdmanager.module.GroupMessagePollingModule
|
import top.r3944realms.ltdmanager.module.GroupMessagePollingModule
|
||||||
import top.r3944realms.ltdmanager.module.HelpModule
|
import top.r3944realms.ltdmanager.module.HelpModule
|
||||||
|
|
||||||
|
|
@ -22,18 +22,18 @@ fun main() = GlobalManager.runBlockingMain {
|
||||||
selfId = selfQQId,
|
selfId = selfQQId,
|
||||||
selfNickName = selfNickName,
|
selfNickName = selfNickName,
|
||||||
)
|
)
|
||||||
val banModule = BanModule(
|
val dgLabModule = DGLabModule(
|
||||||
moduleName = "TestGroup",
|
moduleName = "TestGroup",
|
||||||
groupMessagePollingModule = groupMsgPollingModule,
|
groupMessagePollingModule = groupMsgPollingModule,
|
||||||
selfId = selfQQId,
|
selfId = selfQQId,
|
||||||
adminsId = listOf(2561098830),
|
adminIds = listOf(2561098830L),
|
||||||
muteCommandPrefixList = listOf("禁言", "口球", "mute", "Mute", "闭嘴")
|
commandHead = listOf("dglab")
|
||||||
)
|
)
|
||||||
GlobalManager.moduleManager.registerModule(groupMsgPollingModule)
|
GlobalManager.moduleManager.registerModule(groupMsgPollingModule)
|
||||||
GlobalManager.moduleManager.registerModule(helpModule)
|
GlobalManager.moduleManager.registerModule(helpModule)
|
||||||
GlobalManager.moduleManager.registerModule(banModule)
|
GlobalManager.moduleManager.registerModule(dgLabModule)
|
||||||
|
|
||||||
GlobalManager.moduleManager.loadModule(groupMsgPollingModule.name)
|
GlobalManager.moduleManager.loadModule(groupMsgPollingModule.name)
|
||||||
GlobalManager.moduleManager.loadModule(helpModule.name)
|
GlobalManager.moduleManager.loadModule(helpModule.name)
|
||||||
GlobalManager.moduleManager.loadModule(banModule.name)
|
GlobalManager.moduleManager.loadModule(dgLabModule.name)
|
||||||
}
|
}
|
||||||
|
|
@ -5,12 +5,19 @@ import top.r3944realms.ltdmanager.GlobalManager
|
||||||
import top.r3944realms.ltdmanager.module.ModGroupHandlerModule
|
import top.r3944realms.ltdmanager.module.ModGroupHandlerModule
|
||||||
import top.r3944realms.ltdmanager.napcat.NapCatClient
|
import top.r3944realms.ltdmanager.napcat.NapCatClient
|
||||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
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.data.MessageType
|
||||||
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.other.SendPrivateMsgRequest
|
||||||
|
|
||||||
fun main() = GlobalManager.runBlockingMain {
|
fun main() = GlobalManager.runBlockingMain {
|
||||||
val napCatClient = NapCatClient.create()
|
val napCatClient = NapCatClient.create()
|
||||||
formatAndSendForwardMessage(napCatClient, 2561098830L, "幸福亮亮")
|
// formatAndSendForwardMessage(napCatClient, 2561098830L, "幸福亮亮")
|
||||||
|
sendTestMsg(napCatClient)
|
||||||
|
}
|
||||||
|
private suspend fun sendTestMsg(napCatClient: NapCatClient) {
|
||||||
|
val request = SendPrivateMsgRequest(listOf(MessageElement.image("https://pic.xiaobuawa.top/images/2025/09/30/icons8-postgresql-96d4af6da8d4bd8df5.png","图片")),ID.long(2561098830L))
|
||||||
|
napCatClient.sendUnit(request)
|
||||||
}
|
}
|
||||||
private suspend fun formatAndSendForwardMessage(napCatClient: NapCatClient ,userId: Long, requesterNick: String) {
|
private suspend fun formatAndSendForwardMessage(napCatClient: NapCatClient ,userId: Long, requesterNick: String) {
|
||||||
// 虚拟数据 - 模拟有审核记录的情况
|
// 虚拟数据 - 模拟有审核记录的情况
|
||||||
|
|
@ -45,7 +52,7 @@ private suspend fun formatAndSendForwardMessage(napCatClient: NapCatClient ,user
|
||||||
|
|
||||||
// 创建合并转发消息
|
// 创建合并转发消息
|
||||||
val forwardRequest = SendForwardMsgRequest(
|
val forwardRequest = SendForwardMsgRequest(
|
||||||
groupId = ID.long(339340846),
|
groupId = ID.long(920719236),
|
||||||
messages = listOf(
|
messages = listOf(
|
||||||
SendForwardMsgRequest.TopForwardMsg(
|
SendForwardMsgRequest.TopForwardMsg(
|
||||||
data = SendForwardMsgRequest.MessageData(
|
data = SendForwardMsgRequest.MessageData(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,26 @@
|
||||||
package top.r394realms.ltdmanagertest.msg
|
package top.r394realms.ltdmanagertest.msg
|
||||||
|
|
||||||
class sendMsgTest {
|
import top.r3944realms.ltdmanager.GlobalManager
|
||||||
|
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||||
|
import top.r3944realms.ltdmanager.napcat.event.group.GetGroupMemberListEvent
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.group.GetGroupMemberListRequest
|
||||||
|
import top.r3944realms.ltdmanager.napcat.request.message.SetMsgEmojiLikeRequest
|
||||||
|
|
||||||
|
fun main() = GlobalManager.runBlockingMain {
|
||||||
|
// val getGroupMemberListEvent = GlobalManager.napCatClient.send<GetGroupMemberListEvent>(
|
||||||
|
// GetGroupMemberListRequest(
|
||||||
|
// ID.long(920719236),
|
||||||
|
// false
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// println(getGroupMemberListEvent.data.filter { !it.isRobot }.map { it.userId to it.nickname }.toMap())
|
||||||
|
for (i in 61 ..81){
|
||||||
|
GlobalManager.napCatClient.sendUnit(
|
||||||
|
SetMsgEmojiLikeRequest(
|
||||||
|
i.toDouble(), ID.long(2080109145), true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,20 +2,21 @@ package top.r394realms.ltdmanagertest
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.GlobalManager
|
import top.r3944realms.ltdmanager.GlobalManager
|
||||||
import top.r3944realms.ltdmanager.module.GroupRequestHandlerModule
|
import top.r3944realms.ltdmanager.module.GroupRequestHandlerModule
|
||||||
|
import top.r3944realms.ltdmanager.module.StateModule
|
||||||
|
|
||||||
|
|
||||||
fun main() = GlobalManager.runBlockingMain {
|
fun main() = GlobalManager.runBlockingMain {
|
||||||
// 创建模块实例
|
// 创建模块实例
|
||||||
val groupModule = GroupRequestHandlerModule(
|
val stateModule = StateModule(
|
||||||
moduleName = "WhiteListGroup",
|
moduleName = "Globe",
|
||||||
client = GlobalManager.napCatClient,
|
onlineName = "[\uD83D\uDFE2] 闲趣老土豆🥔",
|
||||||
targetGroupId = 538751386
|
offlineName = "[\uD83D\uDD34] 闲趣老土豆🥔"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// 注册模块到全局模块管理器
|
// 注册模块到全局模块管理器
|
||||||
GlobalManager.moduleManager.registerModule(groupModule)
|
GlobalManager.moduleManager.registerModule(stateModule)
|
||||||
|
|
||||||
// 加载模块
|
// 加载模块
|
||||||
GlobalManager.moduleManager.loadModule(groupModule.name)
|
GlobalManager.moduleManager.loadModule(stateModule.name)
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
package top.r394realms.ltdmanagertest
|
package top.r394realms.ltdmanagertest
|
||||||
|
|
||||||
class testRandom {
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
for(item in 1..100){
|
||||||
|
println(Random.nextInt(100))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ package top.r394realms.ltdmanagertest.util
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
import top.r3944realms.ltdmanager.GlobalManager
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
@ -42,6 +42,7 @@ object ImageUploader {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("https://pic.xiaobuawa.top/api/1/upload")
|
.url("https://pic.xiaobuawa.top/api/1/upload")
|
||||||
.header("X-API-Key", apiKey.trim()) // 重要:去除空格
|
.header("X-API-Key", apiKey.trim()) // 重要:去除空格
|
||||||
|
.header("User-Agent", "OkHttp/4.12.0") // 添加 User-Agent
|
||||||
.post(requestBody)
|
.post(requestBody)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
@ -82,6 +83,7 @@ object ImageUploader {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("https://pic.xiaobuawa.top/api/1/upload")
|
.url("https://pic.xiaobuawa.top/api/1/upload")
|
||||||
.header("X-API-Key", apiKey.trim())
|
.header("X-API-Key", apiKey.trim())
|
||||||
|
.header("User-Agent", "OkHttp/4.12.0")
|
||||||
.post(requestBody)
|
.post(requestBody)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,29 @@
|
||||||
package top.r394realms.ltdmanagertest.util
|
package top.r394realms.ltdmanagertest.util
|
||||||
|
|
||||||
class img {
|
import top.r3944realms.ltdmanager.GlobalManager
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun main() = GlobalManager.runBlockingMain {
|
||||||
|
val client = GlobalManager.cheveretoClient;
|
||||||
|
client.use { cheveretoClient ->
|
||||||
|
// 1. 测试 File 上传
|
||||||
|
val file = File("data/temp/icons8-postgresql-96.png")
|
||||||
|
val resp1 = cheveretoClient.uploadFile(file, title = "PostgreSQL Logo", tags = "db,icon,test")
|
||||||
|
println("File 上传结果: ${resp1.statusCode} -> ${resp1.image?.url}")
|
||||||
|
|
||||||
|
// 2. 测试 ByteArrayInputStream 上传
|
||||||
|
val bytes = file.readBytes()
|
||||||
|
val inputStream = ByteArrayInputStream(bytes)
|
||||||
|
val resp2 = cheveretoClient.uploadStream(inputStream, fileName = "test", title = "From Stream", description = "测试 ByteArrayInputStream 上传")
|
||||||
|
println("Stream 上传结果: ${resp2.statusCode} -> ${resp2.image?.url}")
|
||||||
|
|
||||||
|
// 3. 测试 URL 上传
|
||||||
|
val testUrl = "https://img.icons8.com/color/96/postgresql.png"
|
||||||
|
val resp3 = cheveretoClient.uploadUrl(testUrl)
|
||||||
|
println("URL 上传结果: ${resp3.statusCode} -> ${resp3.image?.url}")
|
||||||
|
if (resp3.statusCode == 400) {
|
||||||
|
println(resp3.statusTxt)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,2 +1,82 @@
|
||||||
package top.r394realms.ltdmanagertest.util
|
package top.r394realms.ltdmanagertest.util
|
||||||
|
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.cio.*
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.request.forms.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.http.content.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import top.r3944realms.ltdmanager.GlobalManager
|
||||||
|
import top.r3944realms.ltdmanager.chevereto.data.CheveretoResponse
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
|
fun main() = GlobalManager.runBlockingMain {
|
||||||
|
val client = HttpClient(CIO) {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json { ignoreUnknownKeys = true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val filePath = "./data/temp/icons8-postgresql-96.png"
|
||||||
|
val file = File(filePath)
|
||||||
|
if (!file.exists()) {
|
||||||
|
println("文件不存在: ${file.absolutePath}")
|
||||||
|
return@runBlockingMain
|
||||||
|
}
|
||||||
|
|
||||||
|
val apiKey = "XXXX"
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构建 multipart/form-data
|
||||||
|
val formDataContent = formData {
|
||||||
|
append("source", file.readBytes(), Headers.build {
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
|
||||||
|
append(HttpHeaders.ContentType, ContentType.Image.PNG.toString())
|
||||||
|
})
|
||||||
|
append("format", "json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试输出每个 part
|
||||||
|
formDataContent.forEach { part ->
|
||||||
|
println("Part Headers: ${part.headers}")
|
||||||
|
when (part) {
|
||||||
|
is PartData.FileItem -> println("Part File: ${part.originalFileName}, size=${part.provider()} bytes")
|
||||||
|
is PartData.FormItem -> println("Part Form: ${part.value}")
|
||||||
|
else -> println("Part Other: $part")
|
||||||
|
}
|
||||||
|
part.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送 POST 请求
|
||||||
|
val response: HttpResponse = client.submitFormWithBinaryData(
|
||||||
|
url = "https://pic.xiaobuawa.top/api/1/upload",
|
||||||
|
formData = formDataContent
|
||||||
|
) {
|
||||||
|
header ("X-API-Key", apiKey.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseText = response.bodyAsText()
|
||||||
|
println("服务器返回原始内容:\n$responseText")
|
||||||
|
|
||||||
|
if (response.status.isSuccess()) {
|
||||||
|
val parsed = Json { ignoreUnknownKeys = true }
|
||||||
|
.decodeFromString(CheveretoResponse.serializer(), responseText)
|
||||||
|
println("上传成功,图片 URL: ${parsed.image?.url}")
|
||||||
|
} else {
|
||||||
|
println("上传失败,HTTP 状态码: ${response.status}")
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("上传过程中出现异常:")
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
|
client.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,126 @@
|
||||||
package top.r394realms.ltdmanagertest.util
|
package top.r394realms.ltdmanagertest.util
|
||||||
|
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.cio.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.request.forms.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.utils.io.core.*
|
||||||
|
import top.r3944realms.ltdmanager.GlobalManager
|
||||||
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.io.use
|
||||||
|
|
||||||
|
suspend fun uploadImageWithKtor(filePath: String, apiKey: String): String {
|
||||||
|
val client = HttpClient(CIO) {
|
||||||
|
// 添加引擎配置
|
||||||
|
engine {
|
||||||
|
// 增加超时设置
|
||||||
|
requestTimeout = 60000
|
||||||
|
}
|
||||||
|
// 添加日志拦截器来调试
|
||||||
|
expectSuccess = false // 不自动抛出异常,让我们自己处理
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.use { httpClient ->
|
||||||
|
try {
|
||||||
|
val file = File(filePath)
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw Exception("文件不存在: $filePath")
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerUtil.logger.info("开始上传文件: ${file.name}, 大小: ${file.length()} bytes")
|
||||||
|
|
||||||
|
val response = httpClient.post("https://pic.xiaobuawa.top/api/1/upload") {
|
||||||
|
// 设置头信息
|
||||||
|
headers {
|
||||||
|
append("X-API-Key", apiKey.trim()) // 去除前后空格
|
||||||
|
append("User-Agent", "Mozilla/5.0 (compatible; MyApp/1.0)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用正确的 multipart 格式
|
||||||
|
setBody(MultiPartFormDataContent(
|
||||||
|
formData {
|
||||||
|
// 使用 appendInput 而不是 append,更接近 curl 的行为
|
||||||
|
appendInput(
|
||||||
|
"source",
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, "image/png")
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
buildPacket {
|
||||||
|
writeFully(file.readBytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append("format", "json")
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
val statusCode = response.status.value
|
||||||
|
val responseText = response.bodyAsText()
|
||||||
|
|
||||||
|
LoggerUtil.logger.info("响应状态码: $statusCode")
|
||||||
|
LoggerUtil.logger.info("响应内容: $responseText")
|
||||||
|
|
||||||
|
if (statusCode != 200) {
|
||||||
|
throw Exception("上传失败,状态码: $statusCode, 响应: $responseText")
|
||||||
|
}
|
||||||
|
|
||||||
|
return@use responseText
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoggerUtil.logger.error("上传过程中发生错误: ${e.message}", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 或者使用另一种更简单的方法
|
||||||
|
suspend fun uploadImageWithKtorSimple(filePath: String, apiKey: String): String {
|
||||||
|
val client = HttpClient(CIO)
|
||||||
|
|
||||||
|
return client.use { httpClient ->
|
||||||
|
val file = File(filePath)
|
||||||
|
|
||||||
|
val response = httpClient.submitFormWithBinaryData(
|
||||||
|
url = "https://pic.xiaobuawa.top/api/1/upload",
|
||||||
|
formData = formData {
|
||||||
|
append("source", file.readBytes(), Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, "image/png")
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
|
||||||
|
})
|
||||||
|
append("format", "json")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
header("X-API-Key", apiKey.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseText = response.bodyAsText()
|
||||||
|
LoggerUtil.logger.info("简单方法响应: $responseText")
|
||||||
|
responseText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() = GlobalManager.runBlockingMain {
|
||||||
|
// 注意:API Key 前面不要有空格!
|
||||||
|
val apiKey = "XXXX"
|
||||||
|
val filePath = "./data/temp/icons8-postgresql-96.png"
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先尝试简单方法
|
||||||
|
val result = uploadImageWithKtorSimple(filePath, apiKey)
|
||||||
|
println("上传成功: $result")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("简单方法失败,尝试详细方法: ${e.message}")
|
||||||
|
try {
|
||||||
|
val result = uploadImageWithKtor(filePath, apiKey)
|
||||||
|
println("详细方法上传成功: $result")
|
||||||
|
} catch (e2: Exception) {
|
||||||
|
println("所有方法都失败: ${e2.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,82 @@
|
||||||
package top.r394realms.ltdmanagertest.util
|
package top.r394realms.ltdmanagertest.util
|
||||||
|
|
||||||
class imgv4 {
|
import top.r3944realms.ltdmanager.GlobalManager
|
||||||
|
|
||||||
|
fun main() = GlobalManager.runBlockingMain {
|
||||||
|
// 测试配置
|
||||||
|
val apiKey = "XXX"
|
||||||
|
val filePath = "./data/temp/icons8-postgresql-96.png"
|
||||||
|
|
||||||
|
println("=== 开始测试图片上传 ===")
|
||||||
|
println("API Key: ${apiKey.take(10)}...")
|
||||||
|
println("文件路径: $filePath")
|
||||||
|
|
||||||
|
// 测试1: 同步上传
|
||||||
|
println("\n--- 测试同步上传 ---")
|
||||||
|
try {
|
||||||
|
val result = ImageUploader.uploadImage(filePath, apiKey)
|
||||||
|
println("✅ 同步上传成功!")
|
||||||
|
println("响应结果: ${result.take(200)}...") // 只显示前200个字符
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("❌ 同步上传失败: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试2: 异步上传
|
||||||
|
println("\n--- 测试异步上传 ---")
|
||||||
|
ImageUploader.uploadImageAsync(filePath, apiKey) { result ->
|
||||||
|
result.onSuccess { response ->
|
||||||
|
println("✅ 异步上传成功!")
|
||||||
|
println("响应结果: ${response.take(200)}...")
|
||||||
|
}.onFailure { error ->
|
||||||
|
println("❌ 异步上传失败: ${error.message}")
|
||||||
|
error.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待异步操作完成
|
||||||
|
println("等待异步操作完成...")
|
||||||
|
Thread.sleep(10000)
|
||||||
|
println("=== 测试结束 ===")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 GlobalManager 的测试版本(如果需要)
|
||||||
|
fun mainWithGlobalManager() = GlobalManager.runBlockingMain {
|
||||||
|
val apiKey = "XXXX"
|
||||||
|
val filePath = "./data/temp/icons8-postgresql-96.png"
|
||||||
|
|
||||||
|
println("=== 使用 GlobalManager 测试图片上传 ===")
|
||||||
|
|
||||||
|
// 测试同步上传
|
||||||
|
try {
|
||||||
|
val result = ImageUploader.uploadImage(filePath, apiKey)
|
||||||
|
println("✅ 上传成功!")
|
||||||
|
println("响应: $result")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("❌ 上传失败: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的单元测试函数
|
||||||
|
fun testImageUpload() {
|
||||||
|
val testCases = listOf(
|
||||||
|
// (文件路径, API Key, 期望结果)
|
||||||
|
"./data/temp/icons8-postgresql-96.png" to "chv_YmZ_12a0828fd88823ad4ef16a0c551b4a10ae5ce1b3e3eb65b07d87eb30162cbc91ed520334018fce2d6ba06f9d58724cef66d30ab7f6292bd4e33ad5e0d96c6499",
|
||||||
|
"./data/temp/nonexistent.png" to "chv_YmZ_12a0828fd88823ad4ef16a0c551b4a10ae5ce1b3e3eb65b07d87eb30162cbc91ed520334018fce2d6ba06f9d58724cef66d30ab7f6292bd4e33ad5e0d96c6499", // 不存在的文件
|
||||||
|
"./data/temp/icons8-postgresql-96.png" to "invalid_key" // 无效的 API Key
|
||||||
|
)
|
||||||
|
|
||||||
|
testCases.forEachIndexed { index, (filePath, apiKey) ->
|
||||||
|
println("\n测试用例 ${index + 1}:")
|
||||||
|
println("文件: $filePath")
|
||||||
|
println("API Key: ${apiKey.take(10)}...")
|
||||||
|
|
||||||
|
try {
|
||||||
|
val result = ImageUploader.uploadImage(filePath, apiKey)
|
||||||
|
println("✅ 成功: ${result.take(100)}...")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("❌ 失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user