feat(完善banmodule,初步编写dglab模块): 更新版本好,完善BanModule,初步编写dglab模块

This commit is contained in:
叁玖领域 2025-09-22 10:49:01 +08:00
parent 88f574eea1
commit f95c6701e5
31 changed files with 1425 additions and 88 deletions

124
.idea/uiDesigner.xml Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

View File

@ -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"))

View File

@ -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

Binary file not shown.

View File

@ -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
)
}

View File

@ -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(),
)
}

View File

@ -0,0 +1,51 @@
package top.r3944realms.ltdmanager.dglab.manager
import com.r3944realms.dg_lab.manager.DGPBClientManager
class ClientManager(
private val clients: MutableMap<String, DGPBClientManager> = mutableMapOf(),
) : IManager<MutableMap<String, DGPBClientManager>> {
/**
* 添加单例客户端管理示例
* @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<String, DGPBClientManager> {
return clients
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,7 @@
package top.r3944realms.ltdmanager.dglab.manager
interface IManager<T> {
fun startAll()
fun stopAll()
fun getInstance(): T?
}

View File

@ -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<DGPBServerManager>, 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
}
}

View File

@ -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")
}
}

View File

@ -0,0 +1,5 @@
package top.r3944realms.ltdmanager.dglab.model.game
import com.r3944realms.dg_lab.websocket.handler.server.DefaultServerOperation
class GameServerOperation : DefaultServerOperation()

View File

@ -0,0 +1,8 @@
package top.r3944realms.ltdmanager.dglab.model.game
/**
* 玩家类目前仅包含一个 ID
*/
data class Player(
val id: String
)

View File

@ -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<String></String>, List<int></int>[][]>>
* 每个 int[][] 包含两个长度为 4 int 数组第一个是 frequencies第二个是 strengths
* @return Map<String></String>, PulseWaveList>
*/
fun convert(customPulseData: Map<String, List<Array<IntArray>>>): Map<String, PulseWaveList> {
val pulseWaveLists: MutableMap<String, PulseWaveList> = 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
}
}

View File

@ -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<String, PulseWaveList> {
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
}
}

View File

@ -0,0 +1,4 @@
package top.r3944realms.ltdmanager.dglab.model.pulseware
class PulseWaveClassTransform {
}

View File

@ -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<String, PulseWaveList>, file: File) {
val serializableMap = map.mapValues { it.value.toSerializable() }
file.writeText(json.encodeToString(serializableMap))
}
fun loadFromFile(file: File): Map<String, PulseWaveList> {
if (!file.exists()) return emptyMap()
val type = MapSerializer(String.serializer(), PulseWaveListSerializable.serializer())
val data: Map<String, PulseWaveListSerializable> = json.decodeFromString(type, file.readText())
return data.mapValues { it.value.toPulseWaveList() }
}
}

View File

@ -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<PulseWaveSerializable> = mutableListOf()
)

View File

@ -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
)

View File

@ -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)
}

View File

@ -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<String> = listOf("/mute"), // 默认命令前缀
private val adminsId: List<Long> = listOf(),
muteCommandPrefixList: List<String> = listOf("mute"), // 默认命令前缀
unmuteCommandPrefixList: List<String> = 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<BanModule.BanState> {
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<ID> {
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<Long>
val send =
napCatClient.send<GetGroupShutListEvent>(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<ID>
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 → 解禁指定用户")
}
}

View File

@ -0,0 +1,14 @@
package top.r3944realms.ltdmanager.module
class DGLabModule(
moduleName: String,
):
BaseModule("DGLabModule", moduleName) {
override fun onLoad() {
}
override suspend fun onUnload() {
}
}

View File

@ -187,7 +187,7 @@ class ModGroupHandlerModule(
📝 尝试答案
${ "\n" + record.reason.joinToString("\n") { "$it" }}
提示请仔细阅读文档后再在群里提问否则你会失去你的大脑🧠
提示请仔细阅读文档后再在群里提问否则你会失去你的大脑🧠
""".trimIndent()
} else {
"""
@ -198,7 +198,7 @@ class ModGroupHandlerModule(
🔹 最终评分SSS
💡 该用户尚未有审核记录
提示请仔细阅读文档后再在群里提问否则你会失去你的大脑🧠
提示请仔细阅读文档后再在群里提问否则你会失去你的大脑🧠
""".trimIndent()
}
}

View File

@ -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 ->

View File

@ -8,7 +8,9 @@ import top.r3944realms.ltdmanager.napcat.event.message.GetFriendMsgHistoryEvent
class KeywordFilter(private val keywords: Set<String>) : 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
}
}
}

View File

@ -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<CommandParser>) : 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
}
}
}

View File

@ -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<EncodeHintType, Any> = 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
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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"
}