Add RecordingNoticeProcessor

This commit is contained in:
Him188 2021-08-16 16:09:12 +08:00
parent a86c0384d4
commit 8663978d65
6 changed files with 266 additions and 0 deletions

2
.gitignore vendored
View File

@ -51,3 +51,5 @@ bintray.key.txt
/build-gpg-sign /build-gpg-sign
# Name for IDEA direction sorting # Name for IDEA direction sorting
build-secret-keys/ build-secret-keys/
**/local.*

View File

@ -80,6 +80,7 @@ kotlin {
commonTest { commonTest {
dependencies { dependencies {
implementation(kotlin("script-runtime")) implementation(kotlin("script-runtime"))
api(yamlkt)
} }
} }

View File

@ -0,0 +1,155 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.notice
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.serializer
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.ProcessResult
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.io.ProtocolStruct
import net.mamoe.mirai.utils.*
import net.mamoe.yamlkt.Yaml
import net.mamoe.yamlkt.YamlBuilder
import kotlin.reflect.full.createType
/**
* How to inject recorder?
*
* ```
* bot.components[NoticeProcessorPipeline].registerProcessor(recorder)
* ```
*/
internal class RecordingNoticeProcessor : SimpleNoticeProcessor<ProtocolStruct>(type()) {
private val id = atomic(0)
private val lock = Mutex()
override suspend fun PipelineContext.processImpl(data: ProtocolStruct) {
lock.withLock {
id.getAndDecrement()
logger.info { "Recorded #${id.value} ${data::class.simpleName}" }
val serial = serialize(this, data)
logger.info { "original: $serial" }
logger.info { "desensitized: " + desensitize(serial) }
logger.info { "decoded: " + deserialize(desensitize(serial)).struct._miraiContentToString() }
}
}
@Serializable
data class RecordNode(
val structType: String,
val struct: String,
val attributes: Map<String, String>,
)
@Serializable
data class DeserializedRecord(
val attributes: TypeSafeMap,
val struct: ProtocolStruct
)
companion object {
private val logger = MiraiLogger.Factory.create(RecordingNoticeProcessor::class)
private val yaml = Yaml {
// one-line
classSerialization = YamlBuilder.MapSerialization.FLOW_MAP
mapSerialization = YamlBuilder.MapSerialization.FLOW_MAP
listSerialization = YamlBuilder.ListSerialization.FLOW_SEQUENCE
stringSerialization = YamlBuilder.StringSerialization.DOUBLE_QUOTATION
encodeDefaultValues = false
}
fun serialize(context: PipelineContext, data: ProtocolStruct): String {
return serialize(context.attributes.toMap(), data)
}
fun serialize(attributes: Map<String, @Contextual Any?>, data: ProtocolStruct): String {
return yaml.encodeToString(
RecordNode(
data::class.java.name,
yaml.encodeToString(data),
attributes.mapValues { yaml.encodeToString(it.value) })
)
}
fun deserialize(string: String): DeserializedRecord {
val (type, struct, attributes) = yaml.decodeFromString(RecordNode.serializer(), string)
val serializer = serializer(Class.forName(type).kotlin.createType())
return DeserializedRecord(
TypeSafeMap(attributes.mapValues { yaml.decodeAnyFromString(it.value) }),
yaml.decodeFromString(serializer, struct).cast()
)
}
private val desensitizer by lateinitMutableProperty {
Desensitizer.create(
run<Map<String, String>> {
val filename =
systemProp("mirai.network.recording.desensitization.filepath", "local.desensitization.yml")
val file =
Thread.currentThread().contextClassLoader.getResource(filename)
?: Thread.currentThread().contextClassLoader.getResource("recording/configs/$filename")
?: error("Could not find desensitization configuration!")
yaml.decodeFromString(file.readText())
}.also {
logger.info { "Loaded ${it.size} desensitization rules." }
}
)
}
fun desensitize(string: String): String = desensitizer.desensitize(string)
}
}
internal suspend fun NoticeProcessorPipeline.processRecording(
bot: QQAndroidBot,
record: RecordingNoticeProcessor.DeserializedRecord
): ProcessResult {
return this.process(bot, record.struct, record.attributes)
}
internal class Desensitizer private constructor(
val rules: Map<String, String>,
) {
companion object {
fun create(rules: Map<String, String>): Desensitizer {
val map = HashMap<String, String>()
map.putAll(rules)
rules.forEach { (t, u) ->
if (t.toLongOrNull() != null && u.toLongOrNull() != null) {
map.putIfAbsent(
Mirai.calculateGroupUinByGroupCode(t.toLong()).toString(),
Mirai.calculateGroupUinByGroupCode(u.toLong()).toString()
)
}
}
return Desensitizer(rules)
}
}
fun desensitize(value: String): String {
return rules.entries.fold(value) { acc, entry ->
acc.replace(entry.key, entry.value)
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.notice.test
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import net.mamoe.mirai.internal.MockBot
import net.mamoe.mirai.internal.network.components.AbstractPipelineContext
import net.mamoe.mirai.internal.network.components.ProcessResult
import net.mamoe.mirai.internal.notice.Desensitizer
import net.mamoe.mirai.internal.notice.RecordingNoticeProcessor
import net.mamoe.mirai.internal.test.AbstractTest
import net.mamoe.mirai.internal.utils.io.ProtocolStruct
import net.mamoe.mirai.utils.MutableTypeSafeMap
import net.mamoe.mirai.utils.TypeSafeMap
import net.mamoe.yamlkt.Yaml
import kotlin.test.Test
import kotlin.test.assertEquals
internal class RecordingNoticeProcessorTest : AbstractTest() {
class MyContext(attributes: TypeSafeMap) : AbstractPipelineContext(MockBot(), attributes) {
override suspend fun processAlso(data: ProtocolStruct, attributes: TypeSafeMap): ProcessResult {
throw UnsupportedOperationException()
}
}
@Serializable
data class MyProtocolStruct(
val value: String
) : ProtocolStruct
@Test
fun `can serialize and deserialize reflectively`() {
val context = MyContext(MutableTypeSafeMap(mapOf("test" to "value")))
val struct = MyProtocolStruct("vvv")
val serialize = RecordingNoticeProcessor.serialize(context, struct)
println(serialize)
val deserialized = RecordingNoticeProcessor.deserialize(serialize)
assertEquals(context.attributes, deserialized.attributes)
assertEquals(struct, deserialized.struct)
}
@Test
fun `can read desensitization config`() {
val text = Thread.currentThread().contextClassLoader.getResource("recording/configs/test.desensitization.yml")!!
.readText()
val desensitizer = Desensitizer.create(Yaml.decodeFromString(text))
assertEquals(
mapOf(
"123456789" to "111",
"987654321" to "111"
), desensitizer.rules
)
}
@Test
fun `test desensitization`() {
val text = Thread.currentThread().contextClassLoader.getResource("recording/configs/test.desensitization.yml")!!
.readText()
val desensitizer = Desensitizer.create(Yaml.decodeFromString(text))
assertEquals(
"""
"111": s1av12sad3
"222": s1av12sad3
""".trim(),
desensitizer.desensitize(
"""
"123456789": s1av12sad3
"987654321": s1av12sad3
""".trim()
)
)
}
}

View File

@ -0,0 +1,19 @@
# Template for Desensitization in recordings
#
# Format:
# ```
# <sensitive value>: <replacer>
# ```
#
# If key is a number, its group uin counterpart will also be processed, with calculated replacer.
#
# For example, if your account id is 147258369, you may add:
# ```
# 147258369: 123456
# ```
# Then your id will be replaced with 123456.
#
#
# To use desensitization, duplicate this file into name "local.desensitization.yml".
123456789: 111

View File

@ -0,0 +1,2 @@
123456789: 111
987654321: 222