fix: 小修改
This commit is contained in:
parent
a0f5504404
commit
e45f2f6272
|
|
@ -1,4 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ASMIdeaPluginConfiguration">
|
||||
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ repositories {
|
|||
maven {
|
||||
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
|
||||
dependencies {
|
||||
|
|
@ -37,6 +44,10 @@ repositories {
|
|||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.3") // 推荐使用kotlinx.serialization替代Gson
|
||||
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-jdbc:0.41.1")
|
||||
|
|
@ -71,6 +82,9 @@ repositories {
|
|||
//生成 二维码
|
||||
implementation("com.google.zxing:core:[3.5.3,)")
|
||||
|
||||
//命令解析
|
||||
implementation("com.mojang:brigadier:1.2.9")
|
||||
|
||||
// 测试
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation("io.ktor:ktor-client-mock:2.3.3")
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ org.gradle.downloadSources=false
|
|||
org.gradle.parallel=true
|
||||
org.gradle.degree_of_parallelism=16
|
||||
project_group=top.r3944realms.ltdmanager
|
||||
project_version=1.10-SNAPSHOT
|
||||
dg_lab_version=4.2.11.18
|
||||
project_version=1.14-SNAPSHOT
|
||||
dg_lab_version=4.3.13.18
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -2,6 +2,7 @@ package top.r3944realms.ltdmanager
|
|||
|
||||
import kotlinx.coroutines.*
|
||||
import top.r3944realms.ltdmanager.blessingskin.BlessingSkinClient
|
||||
import top.r3944realms.ltdmanager.chevereto.CheveretoClient
|
||||
import top.r3944realms.ltdmanager.core.mysql.MysqlHikariConnectPool
|
||||
import top.r3944realms.ltdmanager.mcserver.McSrvStatusClient
|
||||
import top.r3944realms.ltdmanager.module.ModuleManager
|
||||
|
|
@ -29,6 +30,9 @@ object GlobalManager {
|
|||
val blessingSkinClient: BlessingSkinClient by lazy {
|
||||
BlessingSkinClient.create()
|
||||
}
|
||||
val cheveretoClient: CheveretoClient by lazy {
|
||||
CheveretoClient.create()
|
||||
}
|
||||
|
||||
val moduleManager: ModuleManager by lazy { ModuleManager() }
|
||||
|
||||
|
|
@ -67,7 +71,8 @@ object GlobalManager {
|
|||
"NapCatClient" to { napCatClient.close() },
|
||||
"McSrvStatusClient" to { mcSrvStatusClient.close() },
|
||||
"BlessingSkinClient" to { blessingSkinClient.close() },
|
||||
"Hikari 数据源" to { dataSource.close() }
|
||||
"Hikari 数据源" to { dataSource.close() },
|
||||
"CheveretoClient" to { cheveretoClient.close() }
|
||||
)
|
||||
|
||||
resources.forEach { (name, closer) ->
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class BlessingSkinClient private constructor() : AutoCloseable {
|
|||
*/
|
||||
private suspend fun processQueueItem(item: BlessingSkinQueueItem<BlessingSkinResponse, FailedBlessingSkinResponse>) {
|
||||
semaphore.withPermit {
|
||||
val (request, deferred, _, maxRetries, expectsResponse) = item
|
||||
val (request, deferred, _, maxRetries, _) = item
|
||||
var attempt = 0
|
||||
var lastError: Exception? = null
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.*
|
||||
|
|
@ -13,95 +14,223 @@ import kotlinx.coroutines.sync.Mutex
|
|||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.serialization.Serializable
|
||||
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.Closeable
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayDeque
|
||||
|
||||
|
||||
object CheveretoUploader {
|
||||
class CheveretoClient private constructor() : Closeable {
|
||||
|
||||
private val client = HttpClient(CIO) {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
json(Json { 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(
|
||||
apiUrl: String,
|
||||
apiKey: String,
|
||||
file: File,
|
||||
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 {
|
||||
return client.submitFormWithBinaryData(
|
||||
url = apiUrl,
|
||||
formData = formData {
|
||||
append("source", file.readBytes(), Headers.build {
|
||||
append(HttpHeaders.ContentDisposition, "form-data; name=\"source\"; filename=\"${file.name}\"")
|
||||
})
|
||||
append("format", "json")
|
||||
title?.let { append("title", it) }
|
||||
description?.let { append("description", it) }
|
||||
val deferred = CompletableDeferred<CheveretoResponse>()
|
||||
val source = suspend {
|
||||
safeUpload {
|
||||
submitFormWithBinaryData(
|
||||
url = apiUrl,
|
||||
formData = formData {
|
||||
append("source", file.readBytes(), Headers.build {
|
||||
append(HttpHeaders.ContentDisposition, "form-data; name=\"source\"; filename=\"${file.name}\"")
|
||||
})
|
||||
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 {
|
||||
append("X-API-Key", apiKey)
|
||||
}
|
||||
queueMutex.withLock { queue.add(CheveretoQueueItem(source, deferred, priority)) }
|
||||
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
|
||||
*/
|
||||
suspend fun uploadFromUrl(
|
||||
apiUrl: String,
|
||||
apiKey: String,
|
||||
imageUrl: String
|
||||
suspend fun uploadUrl(
|
||||
url: 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 {
|
||||
return client.submitForm(
|
||||
url = apiUrl,
|
||||
formParameters = Parameters.build {
|
||||
append("source", imageUrl)
|
||||
append("format", "json")
|
||||
val deferred = CompletableDeferred<CheveretoResponse>()
|
||||
val source = suspend {
|
||||
safeUpload {
|
||||
submitForm(
|
||||
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 {
|
||||
append("X-API-Key", apiKey)
|
||||
}
|
||||
queueMutex.withLock { queue.add(CheveretoQueueItem(source, deferred, priority)) }
|
||||
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(
|
||||
apiUrl: String,
|
||||
apiKey: String,
|
||||
inputStream: ByteArrayInputStream,
|
||||
fileName: String,
|
||||
title: String? = null,
|
||||
description: String? = null
|
||||
): CheveretoResponse {
|
||||
val bytes = inputStream.readBytes()
|
||||
return client.submitFormWithBinaryData(
|
||||
url = apiUrl,
|
||||
formData = formData {
|
||||
append("source", bytes, Headers.build {
|
||||
append(HttpHeaders.ContentDisposition, "form-data; name=\"source\"; filename=\"$fileName\"")
|
||||
})
|
||||
append("format", "json")
|
||||
title?.let { append("title", it) }
|
||||
description?.let { append("description", it) }
|
||||
}
|
||||
) {
|
||||
headers { append("X-API-Key", apiKey) }
|
||||
}.body()
|
||||
private suspend fun safeUpload(block: suspend HttpClient.() -> HttpResponse): CheveretoResponse {
|
||||
val response = client.block()
|
||||
return try {
|
||||
response.body()
|
||||
} catch (e: Exception) {
|
||||
val raw = response.bodyAsText()
|
||||
throw RuntimeException("Upload failed (status=${response.status}): $raw", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun close() {
|
||||
scope.cancel()
|
||||
runBlocking { client.close() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(): CheveretoClient = CheveretoClient()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
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
|
||||
|
||||
@Serializable
|
||||
|
|
@ -10,5 +11,98 @@ data class CheveretoImage(
|
|||
val width: Int,
|
||||
val height: Int,
|
||||
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.Serializable
|
||||
|
|
@ -7,6 +7,8 @@ import kotlinx.serialization.Serializable
|
|||
data class CheveretoResponse(
|
||||
@SerialName("status_code")
|
||||
val statusCode: Int,
|
||||
val success: Map<String, String>? = null,
|
||||
val image: CheveretoImage? = null
|
||||
val success: Success? = null,
|
||||
val image: CheveretoImage? = null,
|
||||
@SerialName("status_txt")
|
||||
val statusTxt:String ?= null
|
||||
)
|
||||
|
|
@ -1,4 +1,13 @@
|
|||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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?.blessingSkinServer?.invitationApi?.encryptToken()
|
||||
config?.dgLab?.wsServer?.encryptPassword()
|
||||
config?.imgTu?.encryptPassword()
|
||||
}
|
||||
private fun loadConfig(): ConfigWrapper {
|
||||
if (!Files.exists(configFilePath)) {
|
||||
|
|
@ -78,6 +79,7 @@ object YamlConfigLoader {
|
|||
fun loadMailConfig(): MailConfig = config.mail
|
||||
fun loadBlessingSkinServerConfig(): BlessingSkinServerConfig = config.blessingSkinServer
|
||||
fun loadDgLabConfig(): DgLabConfig = config.dgLab
|
||||
fun loadTuImgConfig(): ImgTuConfig = config.imgTu
|
||||
data class ConfigWrapper(
|
||||
var database: DatabaseConfig = DatabaseConfig(),
|
||||
var crypto: CryptoConfig = CryptoConfig(),
|
||||
|
|
@ -88,6 +90,7 @@ object YamlConfigLoader {
|
|||
var mail: MailConfig = MailConfig(),
|
||||
var blessingSkinServer: BlessingSkinServerConfig = BlessingSkinServerConfig(),
|
||||
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.ServerOperation
|
||||
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.ServerPowerBoxSharedData
|
||||
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
|
||||
|
||||
/**
|
||||
* 全局DG_Lab单例管理器
|
||||
* DG_Lab管理器
|
||||
*/
|
||||
object DgLabManager {
|
||||
class DgLab {
|
||||
// 可空,延迟初始化
|
||||
var serverManager: ServerManager? = null
|
||||
private set
|
||||
internal var serverManager: ServerManager? = null
|
||||
get() = field
|
||||
|
||||
var clientManager: ClientManager? = null
|
||||
private set
|
||||
internal var clientManager: ClientManager? = null
|
||||
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 {
|
||||
val loadDgLabConfig = YamlConfigLoader.loadDgLabConfig()
|
||||
|
||||
val boxWSServer = PowerBoxWSServer.Builder.getBuilder()
|
||||
.port(loadDgLabConfig.wsServer.localServerPort)
|
||||
.role(WebSocketServerRole("Se-IC"))
|
||||
.role(WebSocketServerRole(SERVER_ROLE_NAME))
|
||||
.operation(operation)
|
||||
.sharedData(ServerPowerBoxSharedData())
|
||||
.build()
|
||||
|
|
@ -66,7 +108,13 @@ object DgLabManager {
|
|||
fun removeClient(key: String) {
|
||||
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"))
|
||||
.operation(operation)
|
||||
.sharedData(ClientPowerBoxSharedData())
|
||||
.useRoleMsgMode(true)
|
||||
.build()
|
||||
|
||||
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.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(
|
||||
val player: Player
|
||||
val napCatClient: NapCatClient,
|
||||
val groupId: Long,
|
||||
val playerManager: PlayerManager,
|
||||
private val playerId: Long
|
||||
) : 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() {
|
||||
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() {
|
||||
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() {
|
||||
println("Player ${player.id} failed to start client!")
|
||||
override fun ClientStartingErrorHandler(errMsg: String) {
|
||||
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() {
|
||||
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() {
|
||||
println("Player ${player.id} encountered an error while stopping.")
|
||||
override fun ClientStoppingErrorHandler(errMsg: String) {
|
||||
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() {
|
||||
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?) {
|
||||
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() {
|
||||
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() {
|
||||
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?) {
|
||||
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?) {
|
||||
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?) {
|
||||
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?) {
|
||||
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
|
||||
|
||||
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 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
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 玩家类,目前仅包含一个 ID
|
||||
* 玩家类
|
||||
*/
|
||||
@Serializable
|
||||
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
|
||||
|
||||
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.PulseWaveList
|
||||
|
||||
|
||||
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
|
||||
*
|
||||
* @param customPulseData Map<String></String>, List<int></int>[][]>>
|
||||
* @param customPulseData Map<String, List<Array<IntArray>>>
|
||||
* 每个 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> {
|
||||
val pulseWaveLists: MutableMap<String, PulseWaveList> = HashMap()
|
||||
|
|
@ -26,7 +58,10 @@ object CustomPulseDataConverter {
|
|||
// 确保每个数组长度为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)
|
||||
}
|
||||
|
||||
|
|
@ -35,12 +70,53 @@ object CustomPulseDataConverter {
|
|||
|
||||
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(
|
||||
intArrayOf(f1, f2, f3, f4),
|
||||
intArrayOf(convertFrequency(f1), convertFrequency(f2), convertFrequency(f3), convertFrequency(f4)),
|
||||
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
|
||||
|
||||
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> {
|
||||
return mapOf(
|
||||
"呼吸" to Breath,
|
||||
|
|
@ -47,7 +79,7 @@ object DefaultPulseData {
|
|||
|
||||
// 转成 PulseWave 并加入列表
|
||||
for (seg in segments) {
|
||||
list.add(PulseWave.fromArrays(seg[0], seg[1]))
|
||||
list.add(createWaveSegment(seg[0], seg[1]))
|
||||
}
|
||||
|
||||
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(68, 68, 68, 68))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
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(2, 2, 2, 2))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
list
|
||||
}
|
||||
val FastPinch: PulseWaveList by lazy {
|
||||
|
|
@ -96,7 +128,7 @@ object DefaultPulseData {
|
|||
arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
|
||||
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
|
||||
}
|
||||
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(0, 0, 0, 0))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
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))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
list
|
||||
}
|
||||
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))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
list
|
||||
}
|
||||
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(100, 100, 100, 100))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
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(0, 0, 0, 0))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
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))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
list
|
||||
}
|
||||
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(0, 0, 0, 0))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
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(0, 0, 0, 0))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
list
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +305,7 @@ object DefaultPulseData {
|
|||
arrayOf(intArrayOf(20, 20, 20, 20), intArrayOf(50, 50, 50, 50)),
|
||||
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
|
||||
}
|
||||
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(100, 100, 100, 100))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
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(100, 70, 40, 0))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
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(100, 50, 0, 0))
|
||||
)
|
||||
segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
|
||||
segments.forEach { list.add(createWaveSegment(it[0], it[1])) }
|
||||
list
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +1,50 @@
|
|||
package top.r3944realms.ltdmanager
|
||||
|
||||
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
||||
import top.r3944realms.ltdmanager.module.McServerStatusModule
|
||||
import top.r3944realms.ltdmanager.module.*
|
||||
|
||||
|
||||
fun main() = GlobalManager.runBlockingMain {
|
||||
val groupId:Long = 538751386
|
||||
val commonGroupId:Long = 538751386
|
||||
val whitelistGroupId:Long = 920719236
|
||||
val selfQQId = 3327379836
|
||||
val selfNickName = "闲趣老土豆"
|
||||
// 创建模块实例
|
||||
val groupModule = GroupRequestHandlerModule(
|
||||
moduleName = "WhiteListGroup",
|
||||
client = GlobalManager.napCatClient,
|
||||
targetGroupId = groupId
|
||||
targetGroupId = whitelistGroupId
|
||||
)
|
||||
val groupMsgPollingModule = GroupMessagePollingModule(
|
||||
moduleName = "WhiteListGroup",
|
||||
targetGroupId = groupId,
|
||||
val commonGroupMsgPollingModule = GroupMessagePollingModule(
|
||||
moduleName = "CommonGroupMsgPolling",
|
||||
targetGroupId = commonGroupId,
|
||||
pollIntervalMillis = 5_000L,
|
||||
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",
|
||||
keywords = listOf("help", "帮助"),
|
||||
groupMessagePollingModule = groupMsgPollingModule,
|
||||
groupMessagePollingModule = whiteListGroupMsgPollingModule,
|
||||
selfId = selfQQId,
|
||||
selfNickName = selfNickName,
|
||||
)
|
||||
val toolConfig = YamlConfigLoader.loadToolConfig()
|
||||
val rconModule = RconPlayerListModule(
|
||||
val corconModule = RconPlayerListModule(
|
||||
moduleName = "WhiteListGroup",
|
||||
groupMessagePollingModule = groupMsgPollingModule,
|
||||
groupMessagePollingModule = commonGroupMsgPollingModule,
|
||||
rconTimeOut = 2_000L,
|
||||
cooldownMillis = 10_000L,
|
||||
selfId = selfQQId,
|
||||
|
|
@ -46,76 +59,106 @@ fun main() = GlobalManager.runBlockingMain {
|
|||
"列表","服务器状态", "TPS", "tps", "list", "List"
|
||||
)
|
||||
)
|
||||
val mailConfig = YamlConfigLoader.loadMailConfig()
|
||||
val mailModule = MailModule(
|
||||
val rconModule = RconPlayerListModule(
|
||||
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 = groupMsgPollingModule,
|
||||
mailModule = mailModule,
|
||||
apiToken = blessingSkinConfig.invitationApi?.decryptedToken!!,
|
||||
groupMessagePollingModule = whiteListGroupMsgPollingModule,
|
||||
rconTimeOut = 2_000L,
|
||||
cooldownMillis = 10_000L,
|
||||
selfId = selfQQId,
|
||||
selfNickName = selfNickName,
|
||||
rconPath = toolConfig.rcon.mcRconToolPath.toString(),
|
||||
rconConfigPath = toolConfig.rcon.mcRconToolConfigPath.toString(),
|
||||
keywords = setOf(
|
||||
"申请皮肤站注册邀请码",
|
||||
"申请土豆服务器注册邀请码",
|
||||
"申请LTD邀请码",
|
||||
"Apply for an invitation code"
|
||||
//形容
|
||||
"土豆", "马铃薯", "Potato", "potato", "POTATO",
|
||||
"Potatoes", "potatoes", "POTATOES", "🥔",
|
||||
//正经
|
||||
"列表","服务器状态", "TPS", "tps", "list", "List"
|
||||
)
|
||||
)
|
||||
val mcServerStatusModule = McServerStatusModule(
|
||||
moduleName = "WhiteListGroup",
|
||||
groupMessagePollingModule = groupMsgPollingModule,
|
||||
// val mailConfig = YamlConfigLoader.loadMailConfig()
|
||||
// val mailModule = MailModule(
|
||||
// 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,
|
||||
cooldownMillis = 20_000L,
|
||||
selfNickName = selfNickName,
|
||||
commands = listOf("/m", "/mcs", "seek", "s"),
|
||||
commands = listOf("/m", "/mcs", "seek", "s", "test"),
|
||||
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",
|
||||
groupMessagePollingModule = groupMsgPollingModule,
|
||||
groupMessagePollingModule = whiteListGroupMsgPollingModule,
|
||||
selfId = selfQQId,
|
||||
adminsId = listOf(1283411677),
|
||||
muteCommandPrefixList = listOf("口球", "mute", "Mute", "禁言"),
|
||||
unmuteCommandPrefixList = listOf("解禁", "unmute", "Unmute", "解除禁言"),
|
||||
minBanMinutes = 1,
|
||||
maxBanMinutes = 15,
|
||||
cooldownMillis = 20_000L,
|
||||
selfNickName = selfNickName,
|
||||
commands = listOf("/m", "/mcs", "seek", "s", "test"),
|
||||
presetServer = mapOf(
|
||||
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(groupMsgPollingModule)
|
||||
GlobalManager.moduleManager.registerModule(mcServerStatusModule)
|
||||
GlobalManager.moduleManager.registerModule(commonGroupMsgPollingModule)
|
||||
GlobalManager.moduleManager.registerModule(whiteListGroupMsgPollingModule)
|
||||
GlobalManager.moduleManager.registerModule(commonMcServerStatusModule)
|
||||
GlobalManager.moduleManager.registerModule(rconModule)
|
||||
GlobalManager.moduleManager.registerModule(mailModule)
|
||||
GlobalManager.moduleManager.registerModule(invitationCodesModule)
|
||||
GlobalManager.moduleManager.registerModule(helpModule)
|
||||
GlobalManager.moduleManager.registerModule(banModule)
|
||||
GlobalManager.moduleManager.registerModule(corconModule)
|
||||
GlobalManager.moduleManager.registerModule(whitelistMcServerStatusModule)
|
||||
// GlobalManager.moduleManager.registerModule(mailModule)
|
||||
// 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.loadModule(groupModule.name)
|
||||
GlobalManager.moduleManager.loadModule(groupMsgPollingModule.name)
|
||||
GlobalManager.moduleManager.loadModule(mcServerStatusModule.name)
|
||||
GlobalManager.moduleManager.loadModule(commonGroupMsgPollingModule.name)
|
||||
GlobalManager.moduleManager.loadModule(whiteListGroupMsgPollingModule.name)
|
||||
GlobalManager.moduleManager.loadModule(commonMcServerStatusModule.name)
|
||||
GlobalManager.moduleManager.loadModule(corconModule.name)
|
||||
GlobalManager.moduleManager.loadModule(rconModule.name)
|
||||
GlobalManager.moduleManager.loadModule(mailModule.name)
|
||||
GlobalManager.moduleManager.loadModule(invitationCodesModule.name)
|
||||
GlobalManager.moduleManager.loadModule(helpModule.name)
|
||||
GlobalManager.moduleManager.loadModule(banModule.name)
|
||||
// GlobalManager.moduleManager.loadModule(mailModule.name)
|
||||
// GlobalManager.moduleManager.loadModule(invitationCodesModule.name)
|
||||
GlobalManager.moduleManager.loadModule(commonHelpModule.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)
|
||||
}
|
||||
|
|
@ -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.MessageElement
|
||||
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.message.GetFriendMsgHistoryEvent
|
||||
import top.r3944realms.ltdmanager.napcat.request.group.GetGroupShutListRequest
|
||||
import top.r3944realms.ltdmanager.napcat.request.group.SetGroupBanRequest
|
||||
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||
|
|
@ -78,7 +78,7 @@ class BanModule(
|
|||
scope?.cancel()
|
||||
}
|
||||
|
||||
private suspend fun handleMessages(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>) {
|
||||
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||
// 先过一遍过滤器,只有符合条件的才进入后续处理
|
||||
val filtered = triggerFilter.filter(messages)
|
||||
for (msg in filtered) {
|
||||
|
|
@ -91,7 +91,7 @@ class BanModule(
|
|||
* - text 段直接拼接
|
||||
* - 如果消息段里包含 @(在 MessageData 中为 qq 字段),则拼成 "@{qq}",方便 parseMentionToUserId 解析
|
||||
*/
|
||||
private fun GetFriendMsgHistoryEvent.SpecificMsg.plainText(): String {
|
||||
private fun MsgHistorySpecificMsg.plainText(): String {
|
||||
return this.message.joinToString(" ") { seg ->
|
||||
// 如果 message element 包含 qq 字段(即@用户),优先使用它
|
||||
seg.data.qq?.let { "@${it}" } ?: (seg.data.text ?: "")
|
||||
|
|
@ -100,7 +100,7 @@ class BanModule(
|
|||
/**
|
||||
* 从消息段中提取所有被 @ 的用户 ID
|
||||
*/
|
||||
private fun GetFriendMsgHistoryEvent.SpecificMsg.getMentionedUserIds(): List<ID> {
|
||||
private fun MsgHistorySpecificMsg.getMentionedUserIds(): List<ID> {
|
||||
return this.message
|
||||
.filter { it.type == MessageType.At && it.data.qq != null }
|
||||
.mapNotNull { it.data.qq }
|
||||
|
|
@ -111,7 +111,7 @@ class BanModule(
|
|||
}
|
||||
}
|
||||
}
|
||||
private suspend fun processUnBanCommand(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
|
||||
private suspend fun processUnBanCommand(msg: MsgHistorySpecificMsg) {
|
||||
try {
|
||||
pardonCommandParse.parseCommand(msg.plainText()) ?: return
|
||||
// 获取所有被 @ 的用户
|
||||
|
|
@ -149,7 +149,7 @@ class BanModule(
|
|||
saveState(banState)
|
||||
}
|
||||
}
|
||||
private suspend fun processBanCommand(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
|
||||
private suspend fun processBanCommand(msg: MsgHistorySpecificMsg) {
|
||||
try {
|
||||
val parsed = banCommandParse.parseCommand(msg.plainText()) ?: return
|
||||
val (_, argument) = parsed
|
||||
|
|
@ -171,9 +171,9 @@ class BanModule(
|
|||
is ID.LongValue -> target.value
|
||||
}
|
||||
|
||||
// 权限检查:非管理员不能禁言他人
|
||||
// 权限检查:非管理员不能禁言多个他人
|
||||
if (mentionedUserIds.isNotEmpty() && mentionedUserIds.size != 1 && msg.sender.userId !in adminsId) {
|
||||
sendGroupMessage("❌ 你没有权限禁言使用禁言多用户功能", msg.realId)
|
||||
sendGroupMessage("❌ 你没有权限使用禁言多用户功能", msg.realId)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ class BanModule(
|
|||
}
|
||||
|
||||
val selfDuration = durationSeconds * factorX
|
||||
if (Random.nextInt(100) < chance) {
|
||||
if (Random.nextInt(0,100) > chance) {
|
||||
// 触发反禁自己
|
||||
banUser(ID.long(msg.sender.userId), groupMessagePollingModule.targetGroupId, selfDuration)
|
||||
sendGroupMessage(
|
||||
|
|
@ -262,6 +262,7 @@ class BanModule(
|
|||
override fun info(): String {
|
||||
return buildString {
|
||||
append("[$name] 指令禁言模块:\n")
|
||||
append(" 管理员用户ID: ${adminsId}\n")
|
||||
append(" - 用户发送 ${banCommandParse.getCommands().joinToString("、")} 来禁言自己或指定其他用户(需管理员权限)。\n")
|
||||
append(" - 支持指定禁言分钟数或随机分钟数,范围 $minBanMinutes-$maxBanMinutes 分钟。\n")
|
||||
append(" - 支持对单个 @ 用户禁言,有概率反禁自己(骰子点数决定概率)。\n")
|
||||
|
|
|
|||
|
|
@ -84,22 +84,46 @@ abstract class BaseModule(baseName : String = "BaseModule", idName : String = ""
|
|||
* 提供访问全局 NapCatClient 的快捷方式
|
||||
*/
|
||||
protected val napCatClient get() = GlobalManager.napCatClient
|
||||
|
||||
/**
|
||||
* 提供访问全局 blessingSkinClient 的快捷方式
|
||||
*/
|
||||
protected val blessingSkinClient get() = GlobalManager.blessingSkinClient
|
||||
|
||||
/**
|
||||
* 提供访问全局 mcSrvStatusClient 的快捷方式
|
||||
*/
|
||||
protected val mcSrvStatusClient get() = GlobalManager.mcSrvStatusClient
|
||||
|
||||
/**
|
||||
* 提供访问全局 加载模块 的快捷方式
|
||||
*/
|
||||
protected val moduleMap get() = GlobalManager.moduleManager.getModules()
|
||||
|
||||
/**
|
||||
* 获取数据库连接
|
||||
* 使用 try-with-resources 时会自动关闭
|
||||
*/
|
||||
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
|
||||
|
||||
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(
|
||||
moduleName: String,
|
||||
):
|
||||
BaseModule("DGLabModule", moduleName) {
|
||||
override fun onLoad() {
|
||||
private val groupMessagePollingModule : GroupMessagePollingModule,
|
||||
private val selfId: Long,
|
||||
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() {
|
||||
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.asSharedFlow
|
||||
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.request.message.GetGroupMsgHistoryRequest
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
|
|
@ -19,11 +19,11 @@ class GroupMessagePollingModule(
|
|||
private var scope: CoroutineScope? = null
|
||||
|
||||
// 用 Flow 存消息,其他模块可以订阅
|
||||
private val _messagesFlow = MutableSharedFlow<List<GetFriendMsgHistoryEvent.SpecificMsg>>(
|
||||
private val _messagesFlow = MutableSharedFlow<List<MsgHistorySpecificMsg>>(
|
||||
replay = 1, // 保留最近一份消息
|
||||
extraBufferCapacity = 1
|
||||
)
|
||||
val messagesFlow: SharedFlow<List<GetFriendMsgHistoryEvent.SpecificMsg>> = _messagesFlow.asSharedFlow()
|
||||
val messagesFlow: SharedFlow<List<MsgHistorySpecificMsg>> = _messagesFlow.asSharedFlow()
|
||||
|
||||
override fun onLoad() {
|
||||
LoggerUtil.logger.info("[$name] 启动消息轮询 (群: $targetGroupId)")
|
||||
|
|
@ -31,12 +31,12 @@ class GroupMessagePollingModule(
|
|||
scope!!.launch {
|
||||
while (isActive && loaded) {
|
||||
try {
|
||||
val event = napCatClient.send(
|
||||
val event = getNapCatClientOrNull()?.send<GetGroupMsgHistoryEvent>(
|
||||
GetGroupMsgHistoryRequest(
|
||||
count = msgHistoryCheck,
|
||||
groupId = ID.long(targetGroupId)
|
||||
)
|
||||
) as? GetGroupMsgHistoryEvent
|
||||
)
|
||||
|
||||
val messages = event?.data?.messages ?: emptyList()
|
||||
LoggerUtil.logger.debug("[$name] 拉取到 ${messages.size} 条消息")
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class GroupRequestHandlerModule(
|
|||
try {
|
||||
getConnection().use { conn ->
|
||||
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)
|
||||
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.MessageElement
|
||||
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.other.SendGroupMsgRequest
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
|
|
@ -37,7 +37,7 @@ class HelpModule(
|
|||
|
||||
// 命令解析器
|
||||
private val commandParser = CommandParser(keywords)
|
||||
private val GetFriendMsgHistoryEvent.SpecificMsg.textContent: String
|
||||
private val MsgHistorySpecificMsg.textContent: String
|
||||
get() = message.joinToString("") { it.data.text ?: "" }
|
||||
|
||||
// 持久化文件
|
||||
|
|
@ -100,7 +100,7 @@ class HelpModule(
|
|||
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 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 textBuilder = StringBuilder()
|
||||
textBuilder.appendLine("===== $name =====")
|
||||
|
|
@ -153,7 +153,7 @@ class HelpModule(
|
|||
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()
|
||||
textBuilder.appendLine("===== $moduleName =====")
|
||||
textBuilder.appendLine(module.info())
|
||||
|
|
@ -187,7 +187,7 @@ class HelpModule(
|
|||
updateTriggerState(msg)
|
||||
}
|
||||
|
||||
private suspend fun sendText(msg: GetFriendMsgHistoryEvent.SpecificMsg, text: String) {
|
||||
private suspend fun sendText(msg: MsgHistorySpecificMsg, text: String) {
|
||||
val request = SendGroupMsgRequest(
|
||||
MessageElement.reply(ID.long(msg.realId), text),
|
||||
ID.long(groupMessagePollingModule.targetGroupId)
|
||||
|
|
@ -196,7 +196,7 @@ class HelpModule(
|
|||
updateTriggerState(msg)
|
||||
}
|
||||
|
||||
private fun updateTriggerState(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
|
||||
private fun updateTriggerState(msg: MsgHistorySpecificMsg) {
|
||||
lastTriggerState.lastTriggeredRealId = msg.realId
|
||||
lastTriggerState.lastTriggerTime = msg.time
|
||||
saveState(lastTriggerState)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import top.r3944realms.ltdmanager.module.exception.InvitationCodeException
|
|||
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.event.message.GetFriendMsgHistoryEvent
|
||||
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||
import top.r3944realms.ltdmanager.napcat.request.other.SendGroupMsgRequest
|
||||
import top.r3944realms.ltdmanager.utils.HtmlTemplateUtil
|
||||
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
|
||||
val triggerMsgs = filterTriggerMessages(messages)
|
||||
if (triggerMsgs.isEmpty()) return
|
||||
|
||||
try {
|
||||
val hadValidCodeButNotUsed = mutableListOf<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>()
|
||||
val needNewCode = mutableListOf<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>()
|
||||
val hadValidCodeButNotUsed = mutableListOf<Pair<Long, MsgHistorySpecificMsg>>()
|
||||
val needNewCode = mutableListOf<Pair<Long, MsgHistorySpecificMsg>>()
|
||||
|
||||
getIdAndSelectSituation(triggerMsgs, hadValidCodeButNotUsed, needNewCode)
|
||||
createAndSearchInvitationCodeIdsThenUpdateDate(needNewCode)
|
||||
|
|
@ -186,8 +186,8 @@ class InvitationCodesModule(
|
|||
|
||||
/** 过滤出符合条件的触发消息 */
|
||||
private suspend fun filterTriggerMessages(
|
||||
messages: List<GetFriendMsgHistoryEvent.SpecificMsg>
|
||||
): List<GetFriendMsgHistoryEvent.SpecificMsg> {
|
||||
messages: List<MsgHistorySpecificMsg>
|
||||
): List<MsgHistorySpecificMsg> {
|
||||
|
||||
// 先应用通用过滤器
|
||||
val filtered = triggerFilter.filter(messages)
|
||||
|
|
@ -198,9 +198,9 @@ class InvitationCodesModule(
|
|||
.mapNotNull { (_, msgs) -> msgs.maxByOrNull { it.time } }
|
||||
}
|
||||
|
||||
private suspend fun getIdAndSelectSituation(msgs: List<GetFriendMsgHistoryEvent.SpecificMsg>,
|
||||
hadVaildCodeButNotUseList : MutableList<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>,
|
||||
needNewCodeList: MutableList<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>) {
|
||||
private suspend fun getIdAndSelectSituation(msgs: List<MsgHistorySpecificMsg>,
|
||||
hadVaildCodeButNotUseList : MutableList<Pair<Long, MsgHistorySpecificMsg>>,
|
||||
needNewCodeList: MutableList<Pair<Long, MsgHistorySpecificMsg>>) {
|
||||
if (msgs.isEmpty()) return
|
||||
|
||||
val qqIds = msgs.map { it.userId }
|
||||
|
|
@ -273,7 +273,7 @@ class InvitationCodesModule(
|
|||
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
|
||||
|
||||
val whiteListIds = list.map { it.first }
|
||||
|
|
@ -405,7 +405,7 @@ class InvitationCodesModule(
|
|||
lastTriggerMapState = lastTriggerMapState.updateLastTrigger(qq, realId, -1)
|
||||
}
|
||||
private suspend fun createAndSearchInvitationCodeIdsThenUpdateDate(
|
||||
needNewTokenIdAndMsgPairs: List<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>,
|
||||
needNewTokenIdAndMsgPairs: List<Pair<Long, MsgHistorySpecificMsg>>,
|
||||
) {
|
||||
if (needNewTokenIdAndMsgPairs.isEmpty()) return
|
||||
|
||||
|
|
@ -461,7 +461,7 @@ class InvitationCodesModule(
|
|||
*/
|
||||
private fun validateCodeCountMatch(
|
||||
invitationCodes: List<InvitationCodeGenerationResponse.InvitationCode>?,
|
||||
needNewTokenIdAndMsgPairs: List<Pair<Long, GetFriendMsgHistoryEvent.SpecificMsg>>
|
||||
needNewTokenIdAndMsgPairs: List<Pair<Long, MsgHistorySpecificMsg>>
|
||||
) {
|
||||
if (invitationCodes == null) {
|
||||
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.MessageElement
|
||||
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.other.SendGroupMsgRequest
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
|
|
@ -112,7 +112,7 @@ class McServerStatusModule(
|
|||
LoggerUtil.logger.info("[$name] 模块已卸载完成")
|
||||
}
|
||||
|
||||
private suspend fun handleMessages(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>) {
|
||||
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||
if (messages.isEmpty()) return
|
||||
val triggerMsgs = filterTriggerMessages(messages)
|
||||
if (triggerMsgs.isEmpty()) return
|
||||
|
|
@ -129,8 +129,8 @@ class McServerStatusModule(
|
|||
}
|
||||
|
||||
private suspend fun filterTriggerMessages(
|
||||
messages: List<GetFriendMsgHistoryEvent.SpecificMsg>
|
||||
): List<GetFriendMsgHistoryEvent.SpecificMsg> = triggerFilter.filter(messages)
|
||||
messages: List<MsgHistorySpecificMsg>
|
||||
): List<MsgHistorySpecificMsg> = triggerFilter.filter(messages)
|
||||
|
||||
private suspend fun sendFailedMessage(
|
||||
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
|
||||
.firstOrNull { it.type == MessageType.Text }
|
||||
|
|
@ -226,7 +226,7 @@ class McServerStatusModule(
|
|||
// ---------------- 转发消息封装 ----------------
|
||||
private suspend fun sendStatusForwardMessage(
|
||||
client: NapCatClient,
|
||||
msg: GetFriendMsgHistoryEvent.SpecificMsg,
|
||||
msg: MsgHistorySpecificMsg,
|
||||
address: String,
|
||||
status: McServerStatus,
|
||||
realId: Long,
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ class ModGroupHandlerModule(
|
|||
}
|
||||
saveState(state)
|
||||
}
|
||||
fun getRejectRecord(userId: Long): RejectRecord? {
|
||||
private fun getRejectRecord(userId: Long): RejectRecord? {
|
||||
return getState().records[userId]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ interface PersistentState<T> {
|
|||
fun saveState(state: T)
|
||||
fun loadState(): T
|
||||
// 默认实现:统一管理 data 目录下的文件
|
||||
fun getStateFileInternal(name: String, moduleName: String): File {
|
||||
val dataDir = File("data", FileNameFilter.filterFileName(moduleName))
|
||||
fun getStateFileInternal(name: String, subName: String): File {
|
||||
val dataDir = File("data", FileNameFilter.filterFileName(subName))
|
||||
if (!dataDir.exists()) dataDir.mkdirs()
|
||||
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.MessageElement
|
||||
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.other.SendGroupMsgRequest
|
||||
import top.r3944realms.ltdmanager.utils.CmdUtil
|
||||
|
|
@ -110,7 +110,7 @@ class RconPlayerListModule(
|
|||
LoggerUtil.logger.info("[$name] 模块已卸载完成")
|
||||
}
|
||||
|
||||
private suspend fun handleMessages(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>) {
|
||||
private suspend fun handleMessages(messages: List<MsgHistorySpecificMsg>) {
|
||||
val filtered = triggerFilter.filter(messages)
|
||||
|
||||
// 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 查询")
|
||||
|
||||
val commands = listOf("forge tps", "list")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,27 @@
|
|||
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
|
||||
|
||||
/**
|
||||
* 高级命令解析器
|
||||
* 支持自定义参数语法和参数验证
|
||||
*/
|
||||
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
|
||||
|
||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
||||
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||
|
||||
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
|
||||
|
||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
||||
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||
|
||||
class TriggerMessageFilter(private val filters: List<MessageFilter>) {
|
||||
suspend fun filter(messages: List<GetFriendMsgHistoryEvent.SpecificMsg>)
|
||||
: List<GetFriendMsgHistoryEvent.SpecificMsg> {
|
||||
suspend fun filter(messages: List<MsgHistorySpecificMsg>)
|
||||
: List<MsgHistorySpecificMsg> {
|
||||
|
||||
val result = mutableListOf<GetFriendMsgHistoryEvent.SpecificMsg>()
|
||||
val result = mutableListOf<MsgHistorySpecificMsg>()
|
||||
for (msg in messages) {
|
||||
if (filters.all { it.test(msg) }) {
|
||||
result.add(msg)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,17 @@
|
|||
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.filter.MessageFilter
|
||||
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 {
|
||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
||||
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||
return msg.message.any { seg ->
|
||||
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.filter.MessageFilter
|
||||
import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
|
||||
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||
|
||||
class CooldownFilter(
|
||||
private val cooldownManager: CooldownManager<*>,
|
||||
private val sendCooldown: suspend (GetFriendMsgHistoryEvent.SpecificMsg, Long) -> Unit
|
||||
private val sendCooldown: suspend (MsgHistorySpecificMsg, Long) -> Unit
|
||||
) : MessageFilter {
|
||||
|
||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
||||
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||
val result = cooldownManager.checkAndHandle(msg.userId, msg.realId)
|
||||
if (!result.allowed && result.notify) {
|
||||
sendCooldown(msg, result.remaining)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package top.r3944realms.ltdmanager.module.common.filter.type
|
||||
|
||||
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 {
|
||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
||||
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||
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.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 {
|
||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
||||
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||
return msg.message.any { seg ->
|
||||
seg.type == MessageType.Text && seg.data.text?.let { text ->
|
||||
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.filter.MessageFilter
|
||||
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 {
|
||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
||||
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||
return msg.message.any { seg ->
|
||||
seg.type == MessageType.Text && seg.data.text?.let { text ->
|
||||
parsers.any { parser -> parser.containsCommand(text) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package top.r3944realms.ltdmanager.module.common.filter.type
|
||||
|
||||
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.LoggerUtil
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ import top.r3944realms.ltdmanager.utils.LoggerUtil
|
|||
class NewMessageFilter(
|
||||
private val getLastTrigger: (Long) -> Pair<Long, Long> // (time, realId)
|
||||
) : MessageFilter {
|
||||
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
|
||||
override suspend fun test(msg: MsgHistorySpecificMsg): Boolean {
|
||||
val (lastTime, lastRealId) = getLastTrigger(msg.userId)
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,35 @@
|
|||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.Transient
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import top.r3944realms.ltdmanager.napcat.data.GroupMember
|
||||
|
||||
/**
|
||||
* GetGroupMemberList事件
|
||||
|
|
@ -22,7 +22,7 @@ data class GetGroupMemberListEvent(
|
|||
@Transient
|
||||
val echo0: String? = null,
|
||||
|
||||
val data: JsonArray
|
||||
val data: List<GroupMember>
|
||||
) : AbstractGroupEvent(status0, retcode0, message0, wording0, echo0) {
|
||||
|
||||
override fun subtype(): String {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
|
||||
package top.r3944realms.ltdmanager.napcat.event.message
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import top.r3944realms.ltdmanager.napcat.data.ID
|
||||
import top.r3944realms.ltdmanager.napcat.data.MessageType
|
||||
import top.r3944realms.ltdmanager.napcat.data.Sender
|
||||
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||
import top.r3944realms.ltdmanager.napcat.event.group.AbstractGroupEvent
|
||||
|
||||
/**
|
||||
|
|
@ -30,106 +27,9 @@ data class GetFriendMsgHistoryEvent(
|
|||
) : AbstractGroupEvent(status0, retcode0, message0, wording0, echo0) {
|
||||
@Serializable
|
||||
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 {
|
||||
return "get_friend_msg_history"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package top.r3944realms.ltdmanager.napcat.event.message
|
|||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import top.r3944realms.ltdmanager.napcat.data.msghistory.MsgHistorySpecificMsg
|
||||
import top.r3944realms.ltdmanager.napcat.event.group.AbstractGroupEvent
|
||||
|
||||
/**
|
||||
|
|
@ -22,9 +23,13 @@ data class GetGroupMsgHistoryEvent(
|
|||
@Transient
|
||||
val echo0: String? = null,
|
||||
|
||||
val data: GetFriendMsgHistoryEvent.Data
|
||||
val data: Data
|
||||
) : AbstractGroupEvent(status0, retcode0, message0, wording0, echo0) {
|
||||
|
||||
|
||||
@Serializable
|
||||
data class Data (
|
||||
val messages: List<MsgHistorySpecificMsg>
|
||||
)
|
||||
override fun subtype(): String {
|
||||
return "get_group_msg_history"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ data class GetGroupMemberListRequest(
|
|||
@SerialName("no_cache")
|
||||
val noCache: Boolean,
|
||||
|
||||
@SerialName("user_id")
|
||||
val userId: ID
|
||||
) : AbstractGroupRequest() {
|
||||
override fun toJSON(): String = Json.encodeToString(this)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,68 @@
|
|||
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
|
||||
}
|
||||
@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
|
||||
ide-host: "127.0.0.1"
|
||||
ide-port: 5678
|
||||
img-tu:
|
||||
url: https://mysite.com/api/1/upload
|
||||
encrypted-password: 11223344bbcc
|
||||
|
|
|
|||
|
|
@ -1,2 +1,34 @@
|
|||
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
|
||||
|
||||
import top.r3944realms.ltdmanager.module.common.AdvancedCommandParser
|
||||
|
||||
/**
|
||||
* 参数提取演示类
|
||||
*/
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.HelpModule
|
||||
|
||||
|
|
@ -22,18 +22,18 @@ fun main() = GlobalManager.runBlockingMain {
|
|||
selfId = selfQQId,
|
||||
selfNickName = selfNickName,
|
||||
)
|
||||
val banModule = BanModule(
|
||||
val dgLabModule = DGLabModule(
|
||||
moduleName = "TestGroup",
|
||||
groupMessagePollingModule = groupMsgPollingModule,
|
||||
selfId = selfQQId,
|
||||
adminsId = listOf(2561098830),
|
||||
muteCommandPrefixList = listOf("禁言", "口球", "mute", "Mute", "闭嘴")
|
||||
adminIds = listOf(2561098830L),
|
||||
commandHead = listOf("dglab")
|
||||
)
|
||||
GlobalManager.moduleManager.registerModule(groupMsgPollingModule)
|
||||
GlobalManager.moduleManager.registerModule(helpModule)
|
||||
GlobalManager.moduleManager.registerModule(banModule)
|
||||
GlobalManager.moduleManager.registerModule(dgLabModule)
|
||||
|
||||
GlobalManager.moduleManager.loadModule(groupMsgPollingModule.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.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.request.message.SendForwardMsgRequest
|
||||
import top.r3944realms.ltdmanager.napcat.request.other.SendPrivateMsgRequest
|
||||
|
||||
fun main() = GlobalManager.runBlockingMain {
|
||||
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) {
|
||||
// 虚拟数据 - 模拟有审核记录的情况
|
||||
|
|
@ -45,7 +52,7 @@ private suspend fun formatAndSendForwardMessage(napCatClient: NapCatClient ,user
|
|||
|
||||
// 创建合并转发消息
|
||||
val forwardRequest = SendForwardMsgRequest(
|
||||
groupId = ID.long(339340846),
|
||||
groupId = ID.long(920719236),
|
||||
messages = listOf(
|
||||
SendForwardMsgRequest.TopForwardMsg(
|
||||
data = SendForwardMsgRequest.MessageData(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,26 @@
|
|||
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.module.GroupRequestHandlerModule
|
||||
import top.r3944realms.ltdmanager.module.StateModule
|
||||
|
||||
|
||||
fun main() = GlobalManager.runBlockingMain {
|
||||
// 创建模块实例
|
||||
val groupModule = GroupRequestHandlerModule(
|
||||
moduleName = "WhiteListGroup",
|
||||
client = GlobalManager.napCatClient,
|
||||
targetGroupId = 538751386
|
||||
val stateModule = StateModule(
|
||||
moduleName = "Globe",
|
||||
onlineName = "[\uD83D\uDFE2] 闲趣老土豆🥔",
|
||||
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
|
||||
|
||||
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.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import top.r3944realms.ltdmanager.GlobalManager
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
|
@ -42,6 +42,7 @@ object ImageUploader {
|
|||
val request = Request.Builder()
|
||||
.url("https://pic.xiaobuawa.top/api/1/upload")
|
||||
.header("X-API-Key", apiKey.trim()) // 重要:去除空格
|
||||
.header("User-Agent", "OkHttp/4.12.0") // 添加 User-Agent
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
|
|
@ -82,6 +83,7 @@ object ImageUploader {
|
|||
val request = Request.Builder()
|
||||
.url("https://pic.xiaobuawa.top/api/1/upload")
|
||||
.header("X-API-Key", apiKey.trim())
|
||||
.header("User-Agent", "OkHttp/4.12.0")
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,29 @@
|
|||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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