diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 0c6b2d8..5e3b662 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,7 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+fun k(v: String) = project.property(v) as String
+
plugins {
kotlin("jvm") version "1.9.23"
kotlin("plugin.serialization") version "1.9.23" // 添加序列化插件
@@ -7,8 +9,8 @@ plugins {
id("com.github.johnrengelman.shadow") version "8.0.0" // fat jar
}
-group = project.property("project_group") as String
-version = project.property("project_version") as String
+group = k("project_group")
+version = k("project_version")
repositories {
@@ -58,6 +60,16 @@ repositories {
// 协程
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
+ implementation("org.apache.commons:commons-lang3:3.17.0")
+ implementation("com.google.guava:guava:33.3.0-jre")
+
+ //DG_Lab 依赖库导入
+ implementation("io.netty:netty-all:4.1.109.Final")
+ implementation("com.google.code.gson:gson:2.10.1")
+ implementation(files("libs/DgLab-common-${k("dg_lab_version")}.jar"))
+
+ //生成 二维码
+ implementation("com.google.zxing:core:[3.5.3,)")
// 测试
testImplementation(kotlin("test"))
diff --git a/gradle.properties b/gradle.properties
index 60a346b..28b6dbe 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,4 +3,5 @@ org.gradle.downloadSources=false
org.gradle.parallel=true
org.gradle.degree_of_parallelism=16
project_group=top.r3944realms.ltdmanager
-project_version=1.6-SNAPSHOT
+project_version=1.10-SNAPSHOT
+dg_lab_version=4.2.11.18
diff --git a/libs/DgLab-common-4.2.11.18.jar b/libs/DgLab-common-4.2.11.18.jar
new file mode 100644
index 0000000..548593c
Binary files /dev/null and b/libs/DgLab-common-4.2.11.18.jar differ
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/core/config/DgLabConfig.kt b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/DgLabConfig.kt
new file mode 100644
index 0000000..add2ff5
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/DgLabConfig.kt
@@ -0,0 +1,130 @@
+package top.r3944realms.ltdmanager.core.config
+
+import top.r3944realms.ltdmanager.utils.CryptoUtil
+import top.r3944realms.ltdmanager.utils.YamlUpdater
+
+
+data class DgLabConfig(
+ var wsServer: WsServerConfig = WsServerConfig(),
+ var dgLabClient: DgLabClientConfig = DgLabClientConfig(),
+ var pulseData: PulseDataConfig = PulseDataConfig(),
+ var commandText: CommandTextConfig = CommandTextConfig(),
+ var replyText: ReplyTextConfig = ReplyTextConfig(),
+ var debug: DebugConfig = DebugConfig()
+) {
+ data class WsServerConfig(
+ var localServerUrl: String = "0.0.0.0",
+ var localServerPort: Int = 4567,
+ var localServerPublishUrl: String = "ws://127.0.0.1:4567",
+ var localServerSecure: Boolean = false,
+ var localServerSslCert: String = "",
+ var localServerSslKey: String = "",
+ var encryptedLocalServerSslPassword: String? = null
+ ) {
+ val decryptedLocalServerSslPassword: String?
+ get() {
+ if (encryptedLocalServerSslPassword == null) return null
+ if (!isEncrypted()) return encryptedLocalServerSslPassword
+ return try {
+ val cipherText = encryptedLocalServerSslPassword!!.substring(4, encryptedLocalServerSslPassword!!.length - 1)
+ CryptoUtil.decrypt(cipherText)
+ } catch (e: Exception) {
+ throw IllegalStateException("localServerSslPassword 解密失败", e)
+ }
+ }
+
+ fun encryptPassword() {
+ if (encryptedLocalServerSslPassword == null || isEncrypted()) return
+ try {
+ encryptedLocalServerSslPassword = "ENC(${CryptoUtil.encrypt(encryptedLocalServerSslPassword!!)})"
+ YamlUpdater.updateYaml(
+ YamlConfigLoader.configFilePath.toString(),
+ "dg-lab.ws-server.encrypted-local-server-ssl-password",
+ encryptedLocalServerSslPassword!!
+ )
+ } catch (e: Exception) {
+ throw IllegalStateException("SSL 密码加密失败", e)
+ }
+ }
+
+ private fun isEncrypted(): Boolean {
+ return encryptedLocalServerSslPassword != null &&
+ encryptedLocalServerSslPassword!!.startsWith("ENC(") &&
+ encryptedLocalServerSslPassword!!.endsWith(")")
+ }
+ //TODO: 添加有效性检测
+ fun validate() {
+ require(localServerUrl.isNotBlank()) { "localServerUrl 未配置" }
+ require(localServerPort > 0) { "localServerPort 必须大于 0" }
+ require(localServerPublishUrl.isNotBlank()) { "localServerPublishUrl 未配置" }
+ if (localServerSecure) {
+ require(localServerSslCert.isNotBlank()) { "启用 SSL 时必须配置 localServerSslCert" }
+ require(localServerSslKey.isNotBlank()) { "启用 SSL 时必须配置 localServerSslKey" }
+ }
+ }
+ }
+
+ data class DgLabClientConfig(
+ var bindTimeout: Double = 90.0,
+ var registerTimeout: Double = 30.0
+ )
+
+ data class PulseDataConfig(
+ var customPulseData: String = "data/dg-lab-play/customPulseData.json",
+ var durationPerPost: Double = 8.0,
+ var postInterval: Double = 1.0,
+ var sleepAfterClear: Double = 0.5
+ ) {
+ fun validate(maxLength: Double) {
+ require(durationPerPost <= maxLength * 0.1) { "PulseDataConfig.durationPerPost 超出最大时长" }
+ }
+ }
+
+ data class CommandTextConfig(
+ var appendPulse: String = "增加波形",
+ var currentPulse: String = "当前波形",
+ var currentStrength: String = "当前强度",
+ var decreaseStrength: String = "减小强度",
+ var dgLabDeviceJoin: String = "绑定郊狼",
+ var exitGame: String = "退出游戏",
+ var increaseStrength: String = "加大强度",
+ var randomPulse: String = "随机波形",
+ var randomStrength: String = "随机强度",
+ var resetPulse: String = "重置波形",
+ var showPlayers: String = "当前玩家",
+ var showPulses: String = "可用波形",
+ var usage: String = "郊狼玩法"
+ )
+
+ data class ReplyTextConfig(
+ var bindTimeout: String = "绑定超时",
+ var currentPlayers: String = "当前玩家:",
+ var currentPulse: String = "当前波形循环为:【{}】",
+ var currentStrength: String = "A通道:{0}/{1} B通道:{2}/{3}",
+ var failedToCreateClient: String = "创建 DG-Lab 控制终端失败",
+ var failedToFetchStrengthInfo: String = "获取通道强度状态失败",
+ var failedToFetchStrengthLimit: String = "获取通道强度上限失败,控制失败",
+ var gameExited: String = "已退出游戏",
+ var invalidPulseParam: String = "波形参数错误,控制失败",
+ var invalidStrengthParam: String = "强度参数错误,控制失败",
+ var invalidTarget: String = "目标玩家不存在或郊狼 App 未绑定",
+ var noAvailablePulse: String = "无可用波形",
+ var noPlayer: String = "当前没有已连接的玩家,你可以绑定试试~",
+ var notBindYet: String = "你目前没有绑定 DG-Lab App",
+ var pleaseAtTarget: String = "使用命令的同时请 @ 想要控制的玩家",
+ var pleaseScanQrcode: String = "请用 DG-Lab App 扫描二维码以连接",
+ var pleaseSetPulseFirst: String = "请先设置郊狼波形:{}",
+ var pulsesEmpty: String = "当前波形循环为空",
+ var successfullyBind: String = "绑定成功,可以开始色色了!",
+ var successfullyDecreased: String = "郊狼强度减小了 {}%",
+ var successfullyIncreased: String = "郊狼强度加强了 {}%!",
+ var successfullySetPulse: String = "郊狼波形成功设置为【{}】!",
+ var successfullySetToStrength: String = "郊狼强度成功设置为 {}%!"
+ )
+
+ data class DebugConfig(
+ var enableDebug: Boolean = false,
+ var ideHost: String = "127.0.0.1",
+ var idePort: Int = 5678
+ )
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/core/config/YamlConfigLoader.kt b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/YamlConfigLoader.kt
index 4847206..306ce2a 100644
--- a/src/main/kotlin/top/r3944realms/ltdmanager/core/config/YamlConfigLoader.kt
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/YamlConfigLoader.kt
@@ -34,6 +34,7 @@ object YamlConfigLoader {
config?.mail?.encryptPassword()
config?.tools?.rcon?.encryptPassword()
config?.blessingSkinServer?.invitationApi?.encryptToken()
+ config?.dgLab?.wsServer?.encryptPassword()
}
private fun loadConfig(): ConfigWrapper {
if (!Files.exists(configFilePath)) {
@@ -76,6 +77,7 @@ object YamlConfigLoader {
fun loadToolConfig(): ToolConfig = config.tools
fun loadMailConfig(): MailConfig = config.mail
fun loadBlessingSkinServerConfig(): BlessingSkinServerConfig = config.blessingSkinServer
+ fun loadDgLabConfig(): DgLabConfig = config.dgLab
data class ConfigWrapper(
var database: DatabaseConfig = DatabaseConfig(),
var crypto: CryptoConfig = CryptoConfig(),
@@ -85,6 +87,7 @@ object YamlConfigLoader {
var tools: ToolConfig = ToolConfig(),
var mail: MailConfig = MailConfig(),
var blessingSkinServer: BlessingSkinServerConfig = BlessingSkinServerConfig(),
+ var dgLab: DgLabConfig = DgLabConfig(),
)
}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ClientManager.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ClientManager.kt
new file mode 100644
index 0000000..faff82f
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ClientManager.kt
@@ -0,0 +1,51 @@
+package top.r3944realms.ltdmanager.dglab.manager
+
+import com.r3944realms.dg_lab.manager.DGPBClientManager
+
+class ClientManager(
+ private val clients: MutableMap = mutableMapOf(),
+) : IManager> {
+
+ /**
+ * 添加单例客户端管理示例
+ * @param key 唯一标识客户端管理的 key,比如 ID 或 name
+ */
+ fun addClient(key: String, client: DGPBClientManager) {
+ clients[key] = client
+ }
+
+ /**
+ * 移除单例客户端管理实例
+ */
+ fun removeClient(key: String) {
+ clients.remove(key)?.stop()
+ }
+
+ /**
+ * 根据 key 获取客户端
+ */
+ fun getClient(key: String): DGPBClientManager? {
+ return clients[key]
+ }
+
+ /**
+ * 启动所有客户端
+ */
+ override fun startAll() {
+ clients.values.forEach { it.start() }
+ }
+
+ /**
+ * 停止所有客户端
+ */
+ override fun stopAll() {
+ clients.values.forEach { it.stop() }
+ }
+
+ /**
+ * 获取内部 Map 实例
+ */
+ override fun getInstance(): MutableMap {
+ return clients
+ }
+}
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/DgLabManager.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/DgLabManager.kt
new file mode 100644
index 0000000..81147d5
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/DgLabManager.kt
@@ -0,0 +1,102 @@
+package top.r3944realms.ltdmanager.dglab.manager
+
+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
+import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketServerRole
+import com.r3944realms.dg_lab.manager.DGPBClientManager
+import com.r3944realms.dg_lab.manager.DGPBServerManager
+import com.r3944realms.dg_lab.websocket.PowerBoxWSClient
+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 kotlin.io.path.Path
+
+/**
+ * 全局DG_Lab单例管理器
+ */
+object DgLabManager {
+ // 可空,延迟初始化
+ var serverManager: ServerManager? = null
+ private set
+
+ var clientManager: ClientManager? = null
+ private set
+
+
+ fun createServerManager(operation: ServerOperation): DGPBServerManager {
+ val loadDgLabConfig = YamlConfigLoader.loadDgLabConfig()
+ val boxWSServer = PowerBoxWSServer.Builder.getBuilder()
+ .port(loadDgLabConfig.wsServer.localServerPort)
+ .role(WebSocketServerRole("Se-IC"))
+ .operation(operation)
+ .sharedData(ServerPowerBoxSharedData())
+ .build()
+ if (loadDgLabConfig.wsServer.localServerSecure) {
+ boxWSServer.enableSSL(Path(loadDgLabConfig.wsServer.localServerSslCert).toFile(), Path(loadDgLabConfig.wsServer.localServerSslKey).toFile(), loadDgLabConfig.wsServer.decryptedLocalServerSslPassword)
+ }
+ val dgpbServerManager = DGPBServerManager(boxWSServer)
+ return dgpbServerManager
+ }
+
+ /**
+ * 初始化 服务器管理类
+ */
+ fun initServerManager(server: DGPBServerManager) {
+ serverManager = ServerManager(server)
+ }
+ /**
+ * 初始化 客户端管理类
+ */
+ fun initClientManager() {
+ clientManager = ClientManager()
+ }
+
+ /**
+ * 添加 客户端管理类
+ */
+ fun addClient(key: String, client: DGPBClientManager) {
+ clientManager?.addClient(key, client)
+ }
+
+ /**
+ * 移除 客户端管理类
+ */
+ fun removeClient(key: String) {
+ clientManager?.removeClient(key)
+ }
+
+ /**
+ * 获取 客户端管理类
+ */
+ fun getClient(key: String): DGPBClientManager? {
+ return clientManager?.getClient(key)
+ }
+ /**
+ * 获取 & 创建 客户端管理类
+ */
+ fun getClientOrCreate(key: String, operation: ClientOperation): DGPBClientManager {
+ val client = getClient(key)
+ if (client == null) {
+ val loadDgLabConfig = YamlConfigLoader.loadDgLabConfig()
+ val boxWSClient = PowerBoxWSClient.Builder.getBuilder()
+ .address(loadDgLabConfig.wsServer.localServerUrl)
+ .port(loadDgLabConfig.wsServer.localServerPort)
+ .role(WebSocketClientRole("QQ-$key"))
+ .operation(operation)
+ .sharedData(ClientPowerBoxSharedData())
+ .build()
+
+ if (loadDgLabConfig.wsServer.localServerSecure) {
+ boxWSClient.enableSSL()
+ }
+ val clientManager = DGPBClientManager(
+ boxWSClient
+ )
+ this.clientManager?.addClient(key, clientManager)
+ return clientManager
+ }
+ return client
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/IManager.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/IManager.kt
new file mode 100644
index 0000000..a6555f9
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/IManager.kt
@@ -0,0 +1,7 @@
+package top.r3944realms.ltdmanager.dglab.manager
+
+interface IManager {
+ fun startAll()
+ fun stopAll()
+ fun getInstance(): T?
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ServerManager.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ServerManager.kt
new file mode 100644
index 0000000..1971e10
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ServerManager.kt
@@ -0,0 +1,43 @@
+package top.r3944realms.ltdmanager.dglab.manager
+
+import com.r3944realms.dg_lab.api.manager.IDGLabManager
+import com.r3944realms.dg_lab.api.manager.Status
+import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData
+import com.r3944realms.dg_lab.manager.DGPBServerManager
+
+class ServerManager(
+ private val server: DGPBServerManager
+) : IManager, IDGLabManager {
+
+ override fun startAll() {
+ start()
+ }
+
+ override fun stopAll() {
+ stop()
+ }
+
+ override fun start() {
+ server.start()
+ }
+
+ override fun stop() {
+ server.stop()
+ }
+
+ override fun getSharedData(): ISharedData {
+ return server.sharedData
+ }
+
+ override fun getStatus(): Status {
+ return server.status
+ }
+
+ override fun setStatus(p0: Status?) {
+ server.status = p0
+ }
+
+ override fun getInstance(): DGPBServerManager {
+ return server
+ }
+}
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameClientOperation.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameClientOperation.kt
new file mode 100644
index 0000000..1d5cc11
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameClientOperation.kt
@@ -0,0 +1,61 @@
+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
+
+class GameClientOperation(
+ val player: Player
+) : ClientOperation {
+
+ override fun ClientStartingHandler() {
+ println("Player ${player.id} is starting the client...")
+ }
+
+ override fun ClientStartedHandler() {
+ println("Player ${player.id} client started successfully.")
+ }
+
+ override fun ClientStartingErrorHandler() {
+ println("Player ${player.id} failed to start client!")
+ }
+
+ override fun ClientStoppingHandler() {
+ println("Player ${player.id} is stopping the client...")
+ }
+
+ override fun ClientStoppingErrorHandler() {
+ println("Player ${player.id} encountered an error while stopping.")
+ }
+
+ override fun ClientStoppedHandler() {
+ println("Player ${player.id} client stopped.")
+ }
+
+ override fun QrCodeUrlHandler(p0: String?) {
+ println("Player ${player.id} QR code received: $p0")
+ }
+
+ override fun ShowQrCodeHandler() {
+ println("Player ${player.id} should display QR code.")
+ }
+
+ override fun ConnectSuccessfulNoticeHandler() {
+ println("Player ${player.id} connected successfully.")
+ }
+
+ override fun DisconnectHandler(p0: PowerBoxData?) {
+ println("Player ${player.id} disconnected: $p0")
+ }
+
+ override fun ErrorHandler(p0: PowerBoxData?) {
+ println("Player ${player.id} error occurred: $p0")
+ }
+
+ override fun HeartBeatHandler(p0: PowerBoxData?) {
+ println("Heartbeat from player ${player.id}: $p0")
+ }
+
+ override fun OtherMessageHandler(p0: PowerBoxData?) {
+ println("Other message for player ${player.id}: $p0")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameServerOperation.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameServerOperation.kt
new file mode 100644
index 0000000..e5a46ac
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameServerOperation.kt
@@ -0,0 +1,5 @@
+package top.r3944realms.ltdmanager.dglab.model.game
+
+import com.r3944realms.dg_lab.websocket.handler.server.DefaultServerOperation
+
+class GameServerOperation : DefaultServerOperation()
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/Player.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/Player.kt
new file mode 100644
index 0000000..7defc55
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/Player.kt
@@ -0,0 +1,8 @@
+package top.r3944realms.ltdmanager.dglab.model.game
+
+/**
+ * 玩家类,目前仅包含一个 ID
+ */
+data class Player(
+ val id: String
+)
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/CustomPulseDataConverter.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/CustomPulseDataConverter.kt
new file mode 100644
index 0000000..39f0dd8
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/CustomPulseDataConverter.kt
@@ -0,0 +1,56 @@
+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 {
+ /**
+ * 将自定义波形数据转换为 PulseWaveList
+ *
+ * @param customPulseData Map, List[][]>>
+ * 每个 int[][] 包含两个长度为 4 的 int 数组,第一个是 frequencies,第二个是 strengths
+ * @return Map, PulseWaveList>
+ */
+ fun convert(customPulseData: Map>>): Map {
+ val pulseWaveLists: MutableMap = HashMap()
+
+ for ((name, operations) in customPulseData) {
+ val waveList = PulseWaveList()
+ waveList.name = name
+
+ for (op in operations) {
+ val freqs = op[0]
+ val strengths = op[1]
+
+ // 确保每个数组长度为4
+ require(!(freqs.size != 4 || strengths.size != 4)) { "每个波形段必须包含 4 个频率和 4 个强度值" }
+
+ val wave = PulseWave.fromArrays(freqs, strengths)
+ waveList.add(wave)
+ }
+
+ pulseWaveLists[name] = waveList
+ }
+
+ return pulseWaveLists
+ }
+ fun PulseWave.toSerializable(): PulseWaveSerializable =
+ PulseWaveSerializable(f1(), f2(), f3(), f4(), s1(), s2(), s3(), s4())
+
+ fun PulseWaveSerializable.toPulseWave(): PulseWave =
+ PulseWave.fromArrays(
+ intArrayOf(f1, f2, f3, f4),
+ intArrayOf(s1, s2, s3, s4)
+ )
+
+ fun PulseWaveList.toSerializable(): PulseWaveListSerializable =
+ PulseWaveListSerializable(name, list.map { it.toSerializable() }.toMutableList())
+
+ fun PulseWaveListSerializable.toPulseWaveList(): PulseWaveList {
+ val listObj = PulseWaveList()
+ listObj.setName(name)
+ list.forEach { listObj.add(it.toPulseWave()) }
+ return listObj
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/DefaultPulseData.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/DefaultPulseData.kt
new file mode 100644
index 0000000..c4266a9
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/DefaultPulseData.kt
@@ -0,0 +1,313 @@
+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 DefaultPulseData {
+
+ fun allPulseWaveLists(): Map {
+ return mapOf(
+ "呼吸" to Breath,
+ "潮汐" to Tide,
+ "连击" to Combo,
+ "快速按捏" to FastPinch,
+ "按捏渐强" to PinchGradual,
+ "心跳节奏" to Heartbeat,
+ "压缩" to Compress,
+ "节奏步伐" to RhythmStep,
+ "颗粒摩擦" to GranularFriction,
+ "渐变弹跳" to GradualBounce,
+ "波浪涟漪" to WaveRipple,
+ "雨水冲刷" to RainWash,
+ "变速敲击" to SpeedHit,
+ "信号灯" to SignalLight,
+ "挑逗1" to Tease1,
+ "挑逗2" to Tease2
+ )
+ }
+
+ val Breath: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "呼吸"
+
+ // 每段频率和强度
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 5, 10, 20)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(20, 25, 30, 40)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(40, 45, 50, 60)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(60, 65, 70, 80)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0))
+ )
+
+ // 转成 PulseWave 并加入列表
+ for (seg in segments) {
+ list.add(PulseWave.fromArrays(seg[0], seg[1]))
+ }
+
+ list
+ }
+ val Tide: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "潮汐"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 4, 8, 17)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(17, 21, 25, 33)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(50, 50, 50, 50)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(50, 54, 58, 67)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(67, 71, 75, 83)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 98, 96, 92)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(92, 90, 88, 84)),
+ 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])) }
+ list
+ }
+
+ val Combo: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "连击"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 92, 84, 67)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(67, 58, 50, 33)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ 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])) }
+ list
+ }
+ val FastPinch: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "快速按捏"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ 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])) }
+ list
+ }
+ val PinchGradual: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "按捏渐强"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(29, 29, 29, 29)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(52, 52, 52, 52)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(2, 2, 2, 2)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(73, 73, 73, 73)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(87, 87, 87, 87)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
+ )
+ segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
+ list
+ }
+
+ val Heartbeat: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "心跳节奏"
+ val segments = listOf(
+ arrayOf(intArrayOf(110, 110, 110, 110), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(110, 110, 110, 110), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(75, 75, 75, 75)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(75, 77, 79, 83)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(83, 85, 88, 92)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
+ )
+ segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
+ list
+ }
+ val Compress: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "压缩"
+ val segments = listOf(
+ arrayOf(intArrayOf(25, 25, 24, 24), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(24, 23, 23, 23), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(22, 22, 22, 21), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(21, 21, 20, 20), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(20, 19, 19, 19), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(18, 18, 18, 17), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(17, 16, 16, 16), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(15, 15, 15, 14), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(14, 14, 13, 13), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(13, 12, 12, 12), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(11, 11, 11, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
+ )
+ segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
+ list
+ }
+ val RhythmStep: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "节奏步伐"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 5, 10, 20)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(20, 25, 30, 40)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(40, 45, 50, 60)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(60, 65, 70, 80)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 6, 12, 25)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(25, 31, 38, 50)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(50, 56, 62, 75)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 8, 16, 33)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(33, 42, 50, 67)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 12, 25, 50)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100))
+ )
+ segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
+ list
+ }
+
+ val GranularFriction: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "颗粒摩擦"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0))
+ )
+ segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
+ list
+ }
+
+ val GradualBounce: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "渐变弹跳"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(1, 1, 1, 1)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(1, 9, 18, 34)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(34, 42, 50, 67)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0))
+ )
+ segments.forEach { list.add(PulseWave.fromArrays(it[0], it[1])) }
+ list
+ }
+ val WaveRipple: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "波浪涟漪"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(1, 1, 1, 1)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(1, 3, 7, 13)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(13, 25, 40, 60)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(60, 75, 90, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ 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])) }
+ list
+ }
+
+ val RainWash: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "雨水冲刷"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 5, 15, 30)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(40, 50, 60, 70)),
+ 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])) }
+ list
+ }
+
+ val SpeedHit: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "变速敲击"
+ val segments = listOf(
+ arrayOf(intArrayOf(15, 15, 15, 15), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(20, 20, 20, 20), intArrayOf(50, 50, 50, 50)),
+ arrayOf(intArrayOf(25, 25, 25, 25), intArrayOf(100, 100, 100, 100)),
+ 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])) }
+ list
+ }
+ val SignalLight: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "信号灯"
+ val segments = listOf(
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)),
+ arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(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])) }
+ list
+ }
+
+ val Tease1: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "挑逗1"
+ val segments = listOf(
+ 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])) }
+ list
+ }
+
+ val Tease2: PulseWaveList by lazy {
+ val list = PulseWaveList()
+ list.name = "挑逗2"
+ val segments = listOf(
+ 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])) }
+ list
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveClassTransform.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveClassTransform.kt
new file mode 100644
index 0000000..5f1d25e
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveClassTransform.kt
@@ -0,0 +1,4 @@
+package top.r3944realms.ltdmanager.dglab.model.pulseware
+
+class PulseWaveClassTransform {
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveJsonIO.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveJsonIO.kt
new file mode 100644
index 0000000..187e7c9
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveJsonIO.kt
@@ -0,0 +1,29 @@
+package top.r3944realms.ltdmanager.dglab.model.pulseware
+
+import com.r3944realms.dg_lab.api.message.data.PulseWaveList
+import kotlinx.serialization.builtins.MapSerializer
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import top.r3944realms.ltdmanager.dglab.model.pulseware.CustomPulseDataConverter.toPulseWaveList
+import top.r3944realms.ltdmanager.dglab.model.pulseware.CustomPulseDataConverter.toSerializable
+import java.io.File
+
+object PulseWaveJsonIO {
+ private val json = Json {
+ prettyPrint = true
+ encodeDefaults = true
+ }
+
+ fun saveToFile(map: Map, file: File) {
+ val serializableMap = map.mapValues { it.value.toSerializable() }
+ file.writeText(json.encodeToString(serializableMap))
+ }
+
+ fun loadFromFile(file: File): Map {
+ if (!file.exists()) return emptyMap()
+ val type = MapSerializer(String.serializer(), PulseWaveListSerializable.serializer())
+ val data: Map = json.decodeFromString(type, file.readText())
+ return data.mapValues { it.value.toPulseWaveList() }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveListSerializable.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveListSerializable.kt
new file mode 100644
index 0000000..faa5bb2
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveListSerializable.kt
@@ -0,0 +1,9 @@
+package top.r3944realms.ltdmanager.dglab.model.pulseware
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PulseWaveListSerializable(
+ var name: String = "",
+ val list: MutableList = mutableListOf()
+)
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveSerializable.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveSerializable.kt
new file mode 100644
index 0000000..af912d4
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/PulseWaveSerializable.kt
@@ -0,0 +1,9 @@
+package top.r3944realms.ltdmanager.dglab.model.pulseware
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PulseWaveSerializable(
+ val f1: Int, val f2: Int, val f3: Int, val f4: Int,
+ val s1: Int, val s2: Int, val s3: Int, val s4: Int
+)
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/main.kt b/src/main/kotlin/top/r3944realms/ltdmanager/main.kt
index e655696..525a10e 100644
--- a/src/main/kotlin/top/r3944realms/ltdmanager/main.kt
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/main.kt
@@ -23,6 +23,7 @@ fun main() = GlobalManager.runBlockingMain {
)
val helpModule = HelpModule(
moduleName = "WhiteListGroup",
+ keywords = listOf("help", "帮助"),
groupMessagePollingModule = groupMsgPollingModule,
selfId = selfQQId,
selfNickName = selfNickName,
@@ -83,16 +84,18 @@ fun main() = GlobalManager.runBlockingMain {
moduleName = "WhiteListGroup",
groupMessagePollingModule = groupMsgPollingModule,
selfId = selfQQId,
- commandPrefixList = listOf("口球", "mute", "杂鱼三九"),
+ adminsId = listOf(1283411677),
+ muteCommandPrefixList = listOf("口球", "mute", "Mute", "禁言"),
+ unmuteCommandPrefixList = listOf("解禁", "unmute", "Unmute", "解除禁言"),
minBanMinutes = 1,
maxBanMinutes = 15,
)
- val modGroupHandlerModule = ModGroupHandlerModule(
- moduleName = "ModGroup",
- targetGroupId = 339340846,
- answers = listOf("戏鸢", "一只戏鸢", "折戏鸢", "LostInLinearPast", "lostinlinearpast"),
- pollIntervalMillis = 15_000L,
- )
+// val modGroupHandlerModule = ModGroupHandlerModule(
+// moduleName = "ModGroup",
+// targetGroupId = 339340846,
+// answers = listOf("戏鸢", "一只戏鸢", "折戏鸢", "LostInLinearPast", "lostinlinearpast"),
+// pollIntervalMillis = 15_000L,
+// )
// 注册模块到全局模块管理器
GlobalManager.moduleManager.registerModule(groupModule)
@@ -103,7 +106,7 @@ fun main() = GlobalManager.runBlockingMain {
GlobalManager.moduleManager.registerModule(invitationCodesModule)
GlobalManager.moduleManager.registerModule(helpModule)
GlobalManager.moduleManager.registerModule(banModule)
- GlobalManager.moduleManager.registerModule(modGroupHandlerModule)
+// GlobalManager.moduleManager.registerModule(modGroupHandlerModule)
// 加载模块
GlobalManager.moduleManager.loadModule(groupModule.name)
@@ -114,5 +117,5 @@ fun main() = GlobalManager.runBlockingMain {
GlobalManager.moduleManager.loadModule(invitationCodesModule.name)
GlobalManager.moduleManager.loadModule(helpModule.name)
GlobalManager.moduleManager.loadModule(banModule.name)
- GlobalManager.moduleManager.loadModule(modGroupHandlerModule.name)
+// GlobalManager.moduleManager.loadModule(modGroupHandlerModule.name)
}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/module/BanModule.kt b/src/main/kotlin/top/r3944realms/ltdmanager/module/BanModule.kt
index 3aac270..c61f853 100644
--- a/src/main/kotlin/top/r3944realms/ltdmanager/module/BanModule.kt
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/module/BanModule.kt
@@ -6,12 +6,15 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import top.r3944realms.ltdmanager.module.common.CommandParser
import top.r3944realms.ltdmanager.module.common.filter.TriggerMessageFilter
-import top.r3944realms.ltdmanager.module.common.filter.type.CommandFilter
import top.r3944realms.ltdmanager.module.common.filter.type.IgnoreSelfFilter
+import top.r3944realms.ltdmanager.module.common.filter.type.MultiCommandFilter
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.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
import top.r3944realms.ltdmanager.utils.LoggerUtil
@@ -25,13 +28,18 @@ class BanModule(
moduleName: String,
private val groupMessagePollingModule : GroupMessagePollingModule,
private val selfId: Long,
- commandPrefixList: List = listOf("/mute"), // 默认命令前缀
+ private val adminsId: List = listOf(),
+ muteCommandPrefixList: List = listOf("mute"), // 默认命令前缀
+ unmuteCommandPrefixList: List = listOf("unmute"),
private val minBanMinutes: Int = 1,
- private val maxBanMinutes: Int = 15
+ private val maxBanMinutes: Int = 15,
+ private val factorX: Int = 2, // 系数 x,禁言倍数
+
) : BaseModule("BanModule", moduleName), PersistentState {
- private val commandParser = CommandParser(commandPrefixList)
- private val commandFilter = CommandFilter(commandParser)
+ private val banCommandParse = CommandParser(muteCommandPrefixList)
+ private val pardonCommandParse = CommandParser(unmuteCommandPrefixList)
+ private val multiCommandFilter = MultiCommandFilter(listOf(banCommandParse, pardonCommandParse))
private val stateFile: File = getStateFileInternal("command_ban_state.json", name)
private val stateBackupFile: File = getStateFileInternal("command_ban_state.json.bak", name)
private var banState = loadState()
@@ -44,7 +52,7 @@ class BanModule(
NewMessageFilter { userId ->
banState.getLastTriggerTime(userId) to banState.getLastTriggerRealId(userId)
},
- commandFilter
+ multiCommandFilter
)
)
}
@@ -75,6 +83,7 @@ class BanModule(
val filtered = triggerFilter.filter(messages)
for (msg in filtered) {
processBanCommand(msg)
+ processUnBanCommand(msg)
}
}
/**
@@ -88,45 +97,160 @@ class BanModule(
seg.data.qq?.let { "@${it}" } ?: (seg.data.text ?: "")
}.trim()
}
+ /**
+ * 从消息段中提取所有被 @ 的用户 ID
+ */
+ private fun GetFriendMsgHistoryEvent.SpecificMsg.getMentionedUserIds(): List {
+ return this.message
+ .filter { it.type == MessageType.At && it.data.qq != null }
+ .mapNotNull { it.data.qq }
+ .distinctBy {
+ when (it) {
+ is ID.StringValue -> it.value
+ is ID.LongValue -> it.value
+ }
+ }
+ }
+ private suspend fun processUnBanCommand(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
+ try {
+ pardonCommandParse.parseCommand(msg.plainText()) ?: return
+ // 获取所有被 @ 的用户
+ val mentionedUserIds = msg.getMentionedUserIds().map {
+ when (it) {
+ is ID.StringValue -> it.value.toLong()
+ is ID.LongValue -> it.value
+ }
+ } // List
+ val send =
+ napCatClient.send(GetGroupShutListRequest(ID.long(groupMessagePollingModule.targetGroupId)))
+ val muteList = send.data.map { it.uin.toLong() }
+ for (target in mentionedUserIds) {
+ if(target !in muteList) {
+ sendGroupMessage("❌ 目标用户未被禁言",
+ msg.realId
+ )
+ } else {
+ banUser(ID.long(target), groupMessagePollingModule.targetGroupId, 0)
+ sendGroupMessage(
+ "✅ 已解禁对方@(${target})",
+ msg.realId
+ )
+ }
+
+ }
+
+ // 更新状态
+ banState = banState.updateLastTrigger(msg.userId, msg.realId, msg.time)
+ saveState(banState)
+ } catch (e: Exception) {
+ LoggerUtil.logger.error("[$name] 执行解禁言指令失败", e)
+ sendGroupMessage("❌ 执行解禁言失败,请检查解指令格式或权限", msg.realId)
+ banState = banState.updateLastTrigger(msg.sender.userId, msg.realId, msg.time)
+ saveState(banState)
+ }
+ }
private suspend fun processBanCommand(msg: GetFriendMsgHistoryEvent.SpecificMsg) {
try {
- val parsed = commandParser.parseCommand(msg.plainText()) ?: return
- val (command, argument) = parsed
+ val parsed = banCommandParse.parseCommand(msg.plainText()) ?: return
+ val (_, argument) = parsed
- // 参数格式: [分钟]
- // 示例:/mute 5 → 自己禁言 5 分钟
- // /mute → 自己随机禁言
val parts = argument.split(" ").filter { it.isNotBlank() }
+ // 解析禁言时间
val durationMinutes = parts.getOrNull(0)?.toIntOrNull()
?: Random.nextInt(minBanMinutes, maxBanMinutes + 1)
val durationSeconds = durationMinutes.coerceIn(minBanMinutes, maxBanMinutes) * 60
- val targetUserId = msg.sender.userId
+ // 获取所有被 @ 的用户
+ val mentionedUserIds = msg.getMentionedUserIds() // List
+ val targets = mentionedUserIds.ifEmpty { listOf(ID.long(msg.sender.userId)) }
- banUser(targetUserId, groupMessagePollingModule.targetGroupId, durationSeconds)
- sendGroupMessage("✅ 你已被禁言 $durationMinutes 分钟", msg.realId)
+ for (target in targets) {
+ val targetLongId = when (target) {
+ is ID.StringValue -> target.value.toLong()
+ is ID.LongValue -> target.value
+ }
- // 更新状态(保证状态保存正确)
- // 禁言成功后更新状态
- banState = banState.updateLastTrigger(targetUserId, msg.realId, msg.time)
+ // 权限检查:非管理员不能禁言他人
+ if (mentionedUserIds.isNotEmpty() && mentionedUserIds.size != 1 && msg.sender.userId !in adminsId) {
+ sendGroupMessage("❌ 你没有权限禁言使用禁言多用户功能", msg.realId)
+ continue
+ }
+
+ // 禁言机器人跳过
+ if (targetLongId == selfId) {
+ sendGroupMessage("❌ 你没有权限禁言机器人", msg.realId)
+ continue
+ }
+ if (targetLongId in adminsId) {
+ sendGroupMessage("❌ 不支持禁言管理员", msg.realId)
+ continue
+ }
+
+ // 单 @ 且非自己,可能触发反禁自己
+ if (mentionedUserIds.size == 1 && targetLongId != msg.sender.userId && msg.sender.userId !in adminsId) {
+ val dice = Random.nextInt(1, 7) // 1~6
+ val chance = when (dice) {
+ 6 -> 100
+ 5 -> 80
+ 4 -> 60
+ 3 -> 50
+ 2 -> 20
+ 1 -> 0
+ else -> 0
+ }
+
+ val selfDuration = durationSeconds * factorX
+ if (Random.nextInt(100) < chance) {
+ // 触发反禁自己
+ banUser(ID.long(msg.sender.userId), groupMessagePollingModule.targetGroupId, selfDuration)
+ sendGroupMessage(
+ "⚠️ 骰子点数: $dice, 成功概率: ${chance}% → 失败,你触发了反禁,禁言 ${selfDuration / 60} 分钟",
+ msg.realId
+ )
+ } else {
+ // 未触发反禁自己,禁言目标
+ banUser(target, groupMessagePollingModule.targetGroupId, durationSeconds)
+ sendGroupMessage(
+ "✅ 骰子点数: $dice, 成功概率: ${chance}% → 成功禁言 <@${targetLongId}>",
+ msg.realId
+ )
+ }
+ } else {
+ // 多 @ 或管理员操作,直接禁言目标
+ banUser(target, groupMessagePollingModule.targetGroupId, durationSeconds)
+ sendGroupMessage(
+ if (targetLongId == msg.sender.userId) {
+ "✅ 你已被禁言 ${durationSeconds/ 60} 分钟"
+ } else {
+ "✅ 已禁言 <@${targetLongId}> ${durationSeconds/ 60} 分钟"
+ },
+ msg.realId
+ )
+ }
+
+
+ }
+ // 更新状态
+ banState = banState.updateLastTrigger(msg.userId, msg.realId, msg.time)
saveState(banState)
-
} catch (e: Exception) {
LoggerUtil.logger.error("[$name] 执行禁言指令失败", e)
sendGroupMessage("❌ 执行禁言失败,请检查指令格式或权限", msg.realId)
+ banState = banState.updateLastTrigger(msg.sender.userId, msg.realId, msg.time)
+ saveState(banState)
}
}
- private suspend fun banUser(userId: Long, groupId: Long, seconds: Int) {
+
+ private suspend fun banUser(userId: ID, groupId: Long, seconds: Int) {
val request = SetGroupBanRequest(
duration = seconds.toDouble(),
groupId = ID.long(groupId),
- userId = ID.long(userId)
+ userId = userId
)
napCatClient.sendUnit(request)
LoggerUtil.logger.info("[$name] 已对用户 $userId 执行 $seconds 秒禁言")
}
-
private suspend fun sendGroupMessage(text: String, replyTo: Long? = null) {
val request = SendGroupMsgRequest(
MessageElement.reply(ID.long(replyTo ?: 0), text),
@@ -136,20 +260,36 @@ class BanModule(
}
override fun info(): String {
- return "[$name] 指令禁言模块:用户发送 ${commandParser.getCommands().joinToString("、")} 来禁言自己," +
- "支持指定分钟数或随机分钟数,范围 $minBanMinutes-$maxBanMinutes 分钟。"
+ return buildString {
+ append("[$name] 指令禁言模块:\n")
+ append(" - 用户发送 ${banCommandParse.getCommands().joinToString("、")} 来禁言自己或指定其他用户(需管理员权限)。\n")
+ append(" - 支持指定禁言分钟数或随机分钟数,范围 $minBanMinutes-$maxBanMinutes 分钟。\n")
+ append(" - 支持对单个 @ 用户禁言,有概率反禁自己(骰子点数决定概率)。\n")
+ append(" - 管理员可以禁言其他用户;非管理员尝试多个禁言对象会收到无权限提示。\n")
+ append(" - 用户发送 ${pardonCommandParse.getCommands().joinToString("、")} 来解禁指定用户。\n")
+ append(" - 仅支持对单个 @ 用户解禁言。\n")
+ }
}
override fun help(): String {
return buildString {
appendLine("📖 [$name] 使用帮助:")
- appendLine(" - ${commandParser.getCommands().joinToString("、")} [分钟]")
- appendLine(" · 不写分钟数 → 随机禁言 (范围 $minBanMinutes-$maxBanMinutes 分钟)")
- appendLine(" · 写分钟数 → 自己禁言指定分钟数")
- appendLine()
+ appendLine("指令格式:${banCommandParse.getCommands().joinToString("、")} [分钟] [@用户...]")
appendLine("示例:")
- appendLine(" - /mute → 随机禁言自己")
- appendLine(" - /mute 5 → 禁言自己 5 分钟")
+ appendLine(" - <指令> → 随机禁言自己")
+ appendLine(" - <指令> 5 → 禁言自己 5 分钟")
+ appendLine(" - <指令> 4 @User123 → 禁言指定用户 4 分钟(可能失败)")
+ appendLine(" - <指令> 4 @User123 @User22 → 禁言指定多用户 4 分钟(需在程序管理员列表中)")
+ appendLine()
+ appendLine("⚠️ 特殊说明:")
+ appendLine(" - 如果 @ 单个用户且执行者非需在程序管理员,有 y% 概率触发反禁自己,")
+ appendLine(" 骰子点数决定概率:6 → 100%, 5 → 80%, 4 → 60%, 3 → 50%, 2 → 20%, 1 → 0%")
+ appendLine(" - 禁言机器人自身不会生效")
+ appendLine(" - 禁言状态会自动保存以便下次使用")
+ appendLine()
+ appendLine("指令格式:${pardonCommandParse.getCommands().joinToString("、")} [@用户]")
+ appendLine("示例:")
+ appendLine(" - <指令> @User123 → 解禁指定用户")
}
}
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/module/DGLabModule.kt b/src/main/kotlin/top/r3944realms/ltdmanager/module/DGLabModule.kt
new file mode 100644
index 0000000..35950ac
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/module/DGLabModule.kt
@@ -0,0 +1,14 @@
+package top.r3944realms.ltdmanager.module
+
+class DGLabModule(
+ moduleName: String,
+):
+ BaseModule("DGLabModule", moduleName) {
+ override fun onLoad() {
+
+ }
+
+ override suspend fun onUnload() {
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/module/ModGroupHandlerModule.kt b/src/main/kotlin/top/r3944realms/ltdmanager/module/ModGroupHandlerModule.kt
index b972695..cf5d0ae 100644
--- a/src/main/kotlin/top/r3944realms/ltdmanager/module/ModGroupHandlerModule.kt
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/module/ModGroupHandlerModule.kt
@@ -187,7 +187,7 @@ class ModGroupHandlerModule(
📝 尝试答案:
${ "\n" + record.reason.joinToString("\n") { " • $it" }}
- ⚠️ 提示:请仔细阅读文档后再在群里提问,否则你会失去你的大脑🧠
+ ⚠️ 提示:请仔细阅读群文档后再在群里提问,否则你会失去你的大脑🧠
""".trimIndent()
} else {
"""
@@ -198,7 +198,7 @@ class ModGroupHandlerModule(
🔹 最终评分:SSS ⭐
💡 该用户尚未有审核记录
- ⚠️ 提示:请仔细阅读文档后再在群里提问,否则你会失去你的大脑🧠
+ ⚠️ 提示:请仔细阅读群文档后再在群里提问,否则你会失去你的大脑🧠
""".trimIndent()
}
}
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/module/RconPlayerListModule.kt b/src/main/kotlin/top/r3944realms/ltdmanager/module/RconPlayerListModule.kt
index f474eb0..08bc764 100644
--- a/src/main/kotlin/top/r3944realms/ltdmanager/module/RconPlayerListModule.kt
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/module/RconPlayerListModule.kt
@@ -153,6 +153,11 @@ class RconPlayerListModule(
LoggerUtil.logger.error("[$name] RCON 查询失败", ex)
if (ex is TimeoutException) {
sendFailedMessage(napCatClient, msg.realId, msg.time, "⏳ RCON 连接超时")
+ // ✅ 更新触发状态 & 持久化
+ lastTriggerState.lastTriggeredRealId = msg.realId
+ lastTriggerState.lastTriggerTime = msg.time
+ saveState(lastTriggerState)
+ return
}
throw ex
}.onSuccess { output ->
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/module/common/filter/type/KeywordFilter.kt b/src/main/kotlin/top/r3944realms/ltdmanager/module/common/filter/type/KeywordFilter.kt
index c4690bd..a4a39c2 100644
--- a/src/main/kotlin/top/r3944realms/ltdmanager/module/common/filter/type/KeywordFilter.kt
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/module/common/filter/type/KeywordFilter.kt
@@ -8,7 +8,9 @@ import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
class KeywordFilter(private val keywords: Set) : MessageFilter {
override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
return msg.message.any { seg ->
- seg.type == MessageType.Text && seg.data.text?.let { it in keywords } == true
+ seg.type == MessageType.Text && seg.data.text?.let { text ->
+ keywords.any { keyword -> text.startsWith(keyword) }
+ } == true
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/module/common/filter/type/MultiCommandFilter.kt b/src/main/kotlin/top/r3944realms/ltdmanager/module/common/filter/type/MultiCommandFilter.kt
new file mode 100644
index 0000000..c049489
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/module/common/filter/type/MultiCommandFilter.kt
@@ -0,0 +1,17 @@
+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
+
+/** 多命令解析器匹配 */
+class MultiCommandFilter(private val parsers: List) : MessageFilter {
+ override suspend fun test(msg: GetFriendMsgHistoryEvent.SpecificMsg): Boolean {
+ return msg.message.any { seg ->
+ seg.type == MessageType.Text && seg.data.text?.let { text ->
+ parsers.any { parser -> parser.containsCommand(text) }
+ } == true
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/utils/QRCodeUtil.kt b/src/main/kotlin/top/r3944realms/ltdmanager/utils/QRCodeUtil.kt
new file mode 100644
index 0000000..f77ee74
--- /dev/null
+++ b/src/main/kotlin/top/r3944realms/ltdmanager/utils/QRCodeUtil.kt
@@ -0,0 +1,60 @@
+package top.r3944realms.ltdmanager.utils
+
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.EncodeHintType
+import com.google.zxing.MultiFormatWriter
+import com.google.zxing.WriterException
+import com.google.zxing.common.BitArray
+import com.google.zxing.common.BitMatrix
+import java.awt.image.BufferedImage
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.util.*
+import javax.imageio.ImageIO
+
+object QRCodeUtil {
+ private const val CHARSET = "utf-8"
+ private const val FORMAT = "png"
+ @Throws(IOException::class, WriterException::class)
+ fun generateQRCode(string: String?): InputStream {
+ return generateQRCode(string, 256, 256)
+ }
+
+ @Throws(IOException::class, WriterException::class)
+ fun generateQRCode(text: String?, width: Int, height: Int): ByteArrayInputStream {
+ val hints: MutableMap = EnumMap(EncodeHintType::class.java)
+ hints[EncodeHintType.CHARACTER_SET] = CHARSET
+
+ // 创建二维码编码器
+ val bitMatrix: BitMatrix =
+ MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints)
+
+ // 将BitMatrix转换为BufferedImage
+ val image = toBufferedImage(bitMatrix)
+
+ val outputStream = ByteArrayOutputStream()
+ ImageIO.write(image, FORMAT, outputStream)
+
+ return ByteArrayInputStream(outputStream.toByteArray()) // 返回 ByteArrayInputStream
+ }
+
+ fun toBufferedImage(matrix: BitMatrix): BufferedImage {
+ val width: Int = matrix.width
+ val height: Int = matrix.height
+ val image = BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY)
+ val onColor = -0x1000000
+ val offColor = -0x1
+ val rowPixels = IntArray(width)
+ var row: BitArray = BitArray(width)
+ for (y in 0 until height) {
+ row = matrix.getRow(y, row)
+ for (x in 0 until width) {
+ rowPixels[x] = if (row.get(x)) onColor else offColor
+ }
+ image.setRGB(0, y, width, 1, rowPixels, 0, width)
+ }
+ return image
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index c23e1b6..10c0551 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -42,4 +42,62 @@ blessing-skin-server:
path: "/api/invitation-codes/generate"
# 格式为 ENC(XXX),若不是则会在加载完成配置后自动加密
encrypted-token: "your-secret-token"
-
+dg-lab:
+ ws-server:
+ local-server-url: "0.0.0.0"
+ local-server-port: 4567
+ local-server-publish-url: "ws://127.0.0.1:4567"
+ local-server-secure: false
+ local-server-ssl-cert: "config/cert.p12"
+ local-server-ssl-key: "config/key.p12"
+ encrypted-local-server-ssl-password: "ENC(xxxxx)"
+ dg-lab-client:
+ bind-timeout: 90.0
+ register-timeout: 30.0
+ pulse-data:
+ custom-pulse-data: "data/dg-lab-play/customPulseData.json"
+ duration-per-post: 8.0
+ post-interval: 1.0
+ sleep-after-clear: 0.5
+ command-text:
+ append-pulse: "增加波形"
+ current-pulse: "当前波形"
+ current-strength: "当前强度"
+ decrease-strength: "减小强度"
+ dg-lab-device-join: "绑定郊狼"
+ exit-game: "退出游戏"
+ increase-strength: "加大强度"
+ random-pulse: "随机波形"
+ random-strength: "随机强度"
+ reset-pulse: "重置波形"
+ show-players: "当前玩家"
+ show-pulses: "可用波形"
+ usage: "郊狼玩法"
+ reply-text:
+ bind-timeout: "绑定超时"
+ current-players: "当前玩家:"
+ current-pulse: "当前波形循环为:【{}】"
+ current-strength: "A通道:{0}/{1} B通道:{2}/{3}"
+ failed-to-create-client: "创建 DG-Lab 控制终端失败"
+ failed-to-fetch-strength-info: "获取通道强度状态失败"
+ failed-to-fetch-strength-limit: "获取通道强度上限失败,控制失败"
+ game-exited: "已退出游戏"
+ invalid-pulse-param: "波形参数错误,控制失败"
+ invalid-strength-param: "强度参数错误,控制失败"
+ invalid-target: "目标玩家不存在或郊狼 App 未绑定"
+ no-available-pulse: "无可用波形"
+ no-player: "当前没有已连接的玩家,你可以绑定试试~"
+ not-bind-yet: "你目前没有绑定 DG-Lab App"
+ please-at-target: "使用命令的同时请 @ 想要控制的玩家"
+ please-scan-qrcode: "请用 DG-Lab App 扫描二维码以连接"
+ please-set-pulse-first: "请先设置郊狼波形:{}"
+ pulses-empty: "当前波形循环为空"
+ successfully-bind: "绑定成功,可以开始色色了!"
+ successfully-decreased: "郊狼强度减小了 {}%"
+ successfully-increased: "郊狼强度加强了 {}%!"
+ successfully-set-pulse: "郊狼波形成功设置为【{}】!"
+ successfully-set-to-strength: "郊狼强度成功设置为 {}%!"
+ debug:
+ enable-debug: false
+ ide-host: "127.0.0.1"
+ ide-port: 5678
diff --git a/src/test/kotlin/top/r394realms/ltdmanagertest/help/helpTest.kt b/src/test/kotlin/top/r394realms/ltdmanagertest/help/helpTest.kt
index e2d368f..1c31bd3 100644
--- a/src/test/kotlin/top/r394realms/ltdmanagertest/help/helpTest.kt
+++ b/src/test/kotlin/top/r394realms/ltdmanagertest/help/helpTest.kt
@@ -1,7 +1,39 @@
package top.r394realms.ltdmanagertest.help
import top.r3944realms.ltdmanager.GlobalManager
+import top.r3944realms.ltdmanager.module.BanModule
+import top.r3944realms.ltdmanager.module.GroupMessagePollingModule
+import top.r3944realms.ltdmanager.module.HelpModule
fun main() = GlobalManager.runBlockingMain {
+ val groupId:Long = 920719236
+ val selfQQId = 3327379836
+ val selfNickName = "闲趣老土豆"
+ // 创建模块实例
+ val groupMsgPollingModule = GroupMessagePollingModule(
+ moduleName = "TestGroup",
+ targetGroupId = groupId,
+ pollIntervalMillis = 5_000L,
+ msgHistoryCheck = 15
+ )
+ val helpModule = HelpModule(
+ moduleName = "TestGroup",
+ groupMessagePollingModule = groupMsgPollingModule,
+ selfId = selfQQId,
+ selfNickName = selfNickName,
+ )
+ val banModule = BanModule(
+ moduleName = "TestGroup",
+ groupMessagePollingModule = groupMsgPollingModule,
+ selfId = selfQQId,
+ adminsId = listOf(2561098830),
+ muteCommandPrefixList = listOf("禁言", "口球", "mute", "Mute", "闭嘴")
+ )
+ GlobalManager.moduleManager.registerModule(groupMsgPollingModule)
+ GlobalManager.moduleManager.registerModule(helpModule)
+ GlobalManager.moduleManager.registerModule(banModule)
+ GlobalManager.moduleManager.loadModule(groupMsgPollingModule.name)
+ GlobalManager.moduleManager.loadModule(helpModule.name)
+ GlobalManager.moduleManager.loadModule(banModule.name)
}
\ No newline at end of file
diff --git a/src/test/kotlin/top/r394realms/ltdmanagertest/mod/ModTest.kt b/src/test/kotlin/top/r394realms/ltdmanagertest/mod/ModTest.kt
index 78b4075..2534075 100644
--- a/src/test/kotlin/top/r394realms/ltdmanagertest/mod/ModTest.kt
+++ b/src/test/kotlin/top/r394realms/ltdmanagertest/mod/ModTest.kt
@@ -1,7 +1,12 @@
package top.r394realms.ltdmanagertest.mod
+import kotlinx.coroutines.delay
import top.r3944realms.ltdmanager.GlobalManager
+import top.r3944realms.ltdmanager.GlobalManager.napCatClient
import top.r3944realms.ltdmanager.module.ModGroupHandlerModule
+import top.r3944realms.ltdmanager.napcat.data.ID
+import top.r3944realms.ltdmanager.napcat.data.MessageType
+import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
fun main() = GlobalManager.runBlockingMain {
@@ -22,4 +27,4 @@ fun main() = GlobalManager.runBlockingMain {
// 加载模块
GlobalManager.moduleManager.loadModule(modGroupHandlerModule.name)
-}
\ No newline at end of file
+}
diff --git a/src/test/kotlin/top/r394realms/ltdmanagertest/msg/NapCatMsgTest.kt b/src/test/kotlin/top/r394realms/ltdmanagertest/msg/NapCatMsgTest.kt
index fcb7a70..88191f8 100644
--- a/src/test/kotlin/top/r394realms/ltdmanagertest/msg/NapCatMsgTest.kt
+++ b/src/test/kotlin/top/r394realms/ltdmanagertest/msg/NapCatMsgTest.kt
@@ -1,51 +1,85 @@
package top.r394realms.ltdmanagertest.msg
+import kotlinx.coroutines.delay
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.request.other.SendGroupMsgRequest
+import top.r3944realms.ltdmanager.napcat.data.MessageType
+import top.r3944realms.ltdmanager.napcat.request.message.SendForwardMsgRequest
+
fun main() = GlobalManager.runBlockingMain {
val napCatClient = NapCatClient.create()
-
- // 生成9x9乘法表字符串
- val multiplicationTable = buildString {
- for (i in 1..9) {
- for (j in 1..i) {
- append("$j×$i=${i * j}\t")
- }
- appendLine() // 换行
- }
- }
-
- // 生成对齐检查字符
- val alignmentCheck = buildString {
- appendLine("📏 对齐检查(每个数字占位):")
- appendLine("1234567890") // 数字标尺
- appendLine("─".repeat(20)) // 分隔线
-
- for (i in 1..9) {
- for (j in 1..i) {
- val product = i * j
- val placeholder = "X".repeat("$j×$i=$product".length)
- append("$placeholder\t")
- }
- appendLine()
- }
- }
-
- napCatClient.sendUnit(
- SendGroupMsgRequest(
- listOf(
- MessageElement.at(ID.long(2561098830), "幸福亮亮"),
- MessageElement.text("\n"),
- MessageElement.text("9×9乘法表:\n"),
- MessageElement.text(multiplicationTable),
- MessageElement.text("\n────────────────────\n"),
- MessageElement.text(alignmentCheck),
- MessageElement.text("\n提问前,请看文档,不看文档就提问直接肘击(")
- ),
- ID.long(339340846)
- )
+ formatAndSendForwardMessage(napCatClient, 2561098830L, "幸福亮亮")
+}
+private suspend fun formatAndSendForwardMessage(napCatClient: NapCatClient ,userId: Long, requesterNick: String) {
+ // 虚拟数据 - 模拟有审核记录的情况
+ val virtualRecord = ModGroupHandlerModule.RejectRecord(
+ userId = userId,
+ reason = mutableListOf(
+ "模组作者是张三",
+ "作者是李四",
+ "制作人是王五",
+ "我不知道",
+ "可能是赵六吧"
+ ),
+ rejectCount = 5
)
+
+ // 虚拟数据 - 模拟无审核记录的情况(注释掉下面这行来测试)
+ // val virtualRecord = null
+
+ val record = virtualRecord
+ val content = """
+ 📊 用户审核记录
+ ──────────────────
+ 🔹 用户QQ号:${record.userId}
+ 🔹 尝试次数:${record.rejectCount}
+ 🔹 最终评分:${rate(record.rejectCount)}
+
+ 📝 尝试答案:
+ ${"\n" + record.reason.joinToString("\n") { " • $it" }}
+
+ ⚠️ 提示:请仔细阅读文档后再在群里提问,否则你会失去你的大脑🧠
+ """.trimIndent()
+
+ // 创建合并转发消息
+ val forwardRequest = SendForwardMsgRequest(
+ groupId = ID.long(339340846),
+ messages = listOf(
+ SendForwardMsgRequest.TopForwardMsg(
+ data = SendForwardMsgRequest.MessageData(
+ content = listOf(
+ SendForwardMsgRequest.Message(
+ data = SendForwardMsgRequest.PurpleData(
+ text = content
+ ),
+ type = MessageType.Text
+ )
+ ),
+ nickname = "审核系统",
+ userId = ID.long(0) // 系统ID
+ ),
+ type = MessageType.Text
+ )
+ ),
+ news = listOf(
+ SendForwardMsgRequest.ForwardModelNews("用户审核记录详情")
+ ),
+ prompt = "📋 ${requesterNick}入群审核评分${rate(record.rejectCount ?: 0)}",
+ source = "审核系统",
+ summary = "点击查看用户 $requesterNick 的审核详情"
+ )
+
+ // 发送合并转发消息
+ napCatClient.sendUnit(forwardRequest)
+}
+
+private fun rate(count: Int): String = when (count) {
+ 0 -> "SSS"
+ 1 -> "A"
+ 2 -> "B"
+ 3 -> "C"
+ 4 -> "D"
+ else -> "F"
}
\ No newline at end of file