1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-17 09:09:23 +08:00

Redesign packet recording

This commit is contained in:
Him188 2021-08-25 13:55:03 +08:00
parent c4939a7446
commit 76e2b6c64c
27 changed files with 712 additions and 271 deletions

View File

@ -0,0 +1,21 @@
<!--
~ 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
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RunRecorderKt" type="JetRunConfigurationType" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="net.mamoe.mirai.internal.bootstrap.RunRecorderKt"/>
<module name="mirai.mirai-core.jvmTest"/>
<option name="VM_PARAMETERS"
value="-Dmirai.debug.network.state.observer.logging=true -Dmirai.debug.network.show.all.components=true -Dkotlinx.coroutines.debug=on -Dmirai.debug.network.show.packet.details=true"/>
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test"/>
<method v="2">
<option name="Make" enabled="true"/>
</method>
</configuration>
</component>

View File

@ -42,6 +42,7 @@ import net.mamoe.mirai.internal.network.notice.UnconsumedNoticesAlerter
import net.mamoe.mirai.internal.network.notice.decoders.GroupNotificationDecoder
import net.mamoe.mirai.internal.network.notice.decoders.MsgInfoDecoder
import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor
import net.mamoe.mirai.internal.network.notice.group.GroupNotificationProcessor
import net.mamoe.mirai.internal.network.notice.group.GroupOrMemberListNoticeProcessor
import net.mamoe.mirai.internal.network.notice.group.GroupRecallProcessor
import net.mamoe.mirai.internal.network.notice.priv.FriendNoticeProcessor
@ -170,6 +171,7 @@ internal open class QQAndroidBot constructor(
FriendNoticeProcessor(pipelineLogger.subLogger("FriendNoticeProcessor")),
GroupOrMemberListNoticeProcessor(pipelineLogger.subLogger("GroupOrMemberListNoticeProcessor")),
GroupMessageProcessor(pipelineLogger.subLogger("GroupMessageProcessor")),
GroupNotificationProcessor(pipelineLogger.subLogger("GroupNotificationProcessor")),
PrivateMessageProcessor(),
OtherClientNoticeProcessor(),
GroupRecallProcessor(),
@ -206,9 +208,9 @@ internal open class QQAndroidBot constructor(
set(
PacketHandler,
PacketHandlerChain(
LoggingPacketHandlerAdapter(get(PacketLoggingStrategy), networkLogger),
EventBroadcasterPacketHandler(components),
CallPacketFactoryPacketHandler(bot),
LoggingPacketHandlerAdapter(get(PacketLoggingStrategy), networkLogger),
),
)
set(PacketCodec, PacketCodecImpl())

View File

@ -65,13 +65,37 @@ internal fun Group.checkIsGroupImpl(): GroupImpl {
return this
}
internal fun GroupImpl(
bot: QQAndroidBot,
parentCoroutineContext: CoroutineContext,
id: Long,
groupInfo: GroupInfo,
members: Sequence<MemberInfo>,
): GroupImpl {
return GroupImpl(bot, parentCoroutineContext, id, groupInfo, ContactList(ConcurrentLinkedQueue())).apply Group@{
members.forEach { info ->
if (info.uin == bot.id) {
botAsMember = newNormalMember(info)
if (info.permission == MemberPermission.OWNER) {
owner = botAsMember
}
} else newNormalMember(info).let { member ->
if (member.permission == MemberPermission.OWNER) {
owner = member
}
this@Group.members.delegate.add(member)
}
}
}
}
@Suppress("PropertyName")
internal class GroupImpl(
internal class GroupImpl constructor(
bot: QQAndroidBot,
parentCoroutineContext: CoroutineContext,
override val id: Long,
groupInfo: GroupInfo,
members: Sequence<MemberInfo>,
override val members: ContactList<NormalMemberImpl>,
) : Group, AbstractContact(bot, parentCoroutineContext) {
companion object
@ -84,20 +108,6 @@ internal class GroupImpl(
override val filesRoot: RemoteFile by lazy { RemoteFileImpl(this, "/") }
override val members: ContactList<NormalMemberImpl> =
ContactList(members.mapNotNullTo(ConcurrentLinkedQueue()) { info ->
if (info.uin == bot.id) {
botAsMember = newNormalMember(info)
if (info.permission == MemberPermission.OWNER) {
owner = botAsMember
}
null
} else newNormalMember(info).also { member ->
if (member.permission == MemberPermission.OWNER) {
owner = member
}
}
})
override val announcements: Announcements by lazy {
AnnouncementsImpl(

View File

@ -30,6 +30,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushP
import net.mamoe.mirai.internal.network.toPacket
import net.mamoe.mirai.internal.utils.io.ProtocolStruct
import net.mamoe.mirai.utils.*
import java.io.Closeable
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.reflect.KClass
@ -40,7 +41,15 @@ internal typealias ProcessResult = Collection<Packet>
* Centralized processor pipeline for [MessageSvcPbGetMsg] and [OnlinePushPbPushTransMsg]
*/
internal interface NoticeProcessorPipeline {
fun registerProcessor(processor: NoticeProcessor)
fun interface DisposableRegistry : Closeable {
fun dispose()
override fun close() {
dispose()
}
}
fun registerProcessor(processor: NoticeProcessor): DisposableRegistry
/**
* Process [data] into [Packet]s. Exceptions are wrapped into [ParseErrorPacket]
@ -69,7 +78,7 @@ internal value class MutableProcessResult(
val data: MutableCollection<Packet>
)
internal interface PipelineContext : BotAware {
internal interface NoticePipelineContext : BotAware {
override val bot: QQAndroidBot
val attributes: TypeSafeMap
@ -128,28 +137,32 @@ internal interface PipelineContext : BotAware {
val KEY_FROM_SYNC = TypeKey<Boolean>("fromSync")
val KEY_MSG_INFO = TypeKey<MsgInfo>("msgInfo")
val PipelineContext.fromSync get() = attributes[KEY_FROM_SYNC]
val NoticePipelineContext.fromSync get() = attributes[KEY_FROM_SYNC]
/**
* 来自 [MsgInfo] 的数据, [MsgType0x210], [MsgType0x2DC] 的处理过程之中可以使用
*/
val PipelineContext.msgInfo get() = attributes[KEY_MSG_INFO]
val NoticePipelineContext.msgInfo get() = attributes[KEY_MSG_INFO]
}
}
internal abstract class AbstractPipelineContext(
internal abstract class AbstractNoticePipelineContext(
override val bot: QQAndroidBot, override val attributes: TypeSafeMap,
) : PipelineContext {
) : NoticePipelineContext {
private val consumers: Stack<Any> = Stack()
override val isConsumed: Boolean get() = consumers.isNotEmpty()
override fun NoticeProcessor.markAsConsumed(marker: Any) {
traceLogging.info { "markAsConsumed: marker=$marker" }
consumers.push(marker)
}
override fun NoticeProcessor.markNotConsumed(marker: Any) {
if (consumers.peek() === marker) {
consumers.pop()
traceLogging.info { "markNotConsumed: Y, marker=$marker" }
} else {
traceLogging.info { "markNotConsumed: N, marker=$marker" }
}
}
@ -157,17 +170,27 @@ internal abstract class AbstractPipelineContext(
override fun collect(packet: Packet) {
collected.data.add(packet)
traceLogging.info { "collect: $packet" }
}
override fun collect(packets: Iterable<Packet>) {
this.collected.data.addAll(packets)
traceLogging.info {
val list = packets.toList()
"collect: [${list.size}] ${list.joinToString()}"
}
}
abstract override suspend fun processAlso(data: ProtocolStruct, attributes: TypeSafeMap): ProcessResult
}
internal inline val PipelineContext.context get() = this
internal inline val NoticePipelineContext.context get() = this
private val traceLogging: MiraiLogger by lazy {
MiraiLogger.Factory.create(NoticeProcessorPipelineImpl::class, "NoticeProcessorPipeline")
.withSwitch(systemProp("mirai.network.notice.pipeline.log.full", false))
}
internal open class NoticeProcessorPipelineImpl private constructor() : NoticeProcessorPipeline {
/**
@ -175,24 +198,37 @@ internal open class NoticeProcessorPipelineImpl private constructor() : NoticePr
*/
private val processors = ConcurrentLinkedQueue<NoticeProcessor>()
override fun registerProcessor(processor: NoticeProcessor) {
override fun registerProcessor(processor: NoticeProcessor): NoticeProcessorPipeline.DisposableRegistry {
processors.add(processor)
return NoticeProcessorPipeline.DisposableRegistry {
processors.remove(processor)
}
}
inner class ContextImpl(
bot: QQAndroidBot, attributes: TypeSafeMap,
) : AbstractPipelineContext(bot, attributes) {
) : AbstractNoticePipelineContext(bot, attributes) {
override suspend fun processAlso(data: ProtocolStruct, attributes: TypeSafeMap): ProcessResult {
return process(bot, data, this.attributes + attributes)
traceLogging.info { "processAlso: data=$data" }
return process(bot, data, this.attributes + attributes).also {
this.collected.data += it
traceLogging.info { "processAlso: result=$it" }
}
}
}
override suspend fun process(bot: QQAndroidBot, data: ProtocolStruct, attributes: TypeSafeMap): ProcessResult {
traceLogging.info { "process: data=$data" }
val context = ContextImpl(bot, attributes)
val diff = if (traceLogging.isEnabled) CollectionDiff<Packet>() else null
diff?.save(context.collected.data)
for (processor in processors) {
kotlin.runCatching {
val result = kotlin.runCatching {
processor.process(context, data)
}.onFailure { e ->
context.collect(
@ -205,6 +241,16 @@ internal open class NoticeProcessorPipelineImpl private constructor() : NoticePr
),
)
}
diff?.run {
val diffPackets = subtractAndSave(context.collected.data)
traceLogging.info {
"Finished ${
processor.toString().replace("net.mamoe.mirai.internal.network.notice.", "")
}, success=${result.isSuccess}, consumed=${context.isConsumed}, diff=$diffPackets"
}
}
}
return context.collected.data
}
@ -231,7 +277,7 @@ internal open class NoticeProcessorPipelineImpl private constructor() : NoticePr
* A processor handling some specific type of message.
*/
internal interface NoticeProcessor {
suspend fun process(context: PipelineContext, data: Any?)
suspend fun process(context: NoticePipelineContext, data: Any?)
}
internal abstract class AnyNoticeProcessor : SimpleNoticeProcessor<ProtocolStruct>(type())
@ -240,13 +286,13 @@ internal abstract class SimpleNoticeProcessor<in T : ProtocolStruct>(
private val type: KClass<T>,
) : NoticeProcessor {
final override suspend fun process(context: PipelineContext, data: Any?) {
final override suspend fun process(context: NoticePipelineContext, data: Any?) {
if (type.isInstance(data)) {
context.processImpl(data.uncheckedCast())
}
}
protected abstract suspend fun PipelineContext.processImpl(data: T)
protected abstract suspend fun NoticePipelineContext.processImpl(data: T)
companion object {
@JvmStatic
@ -255,11 +301,11 @@ internal abstract class SimpleNoticeProcessor<in T : ProtocolStruct>(
}
internal abstract class MsgCommonMsgProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type()) {
abstract override suspend fun PipelineContext.processImpl(data: MsgComm.Msg)
abstract override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg)
}
internal abstract class MixedNoticeProcessor : AnyNoticeProcessor() {
final override suspend fun PipelineContext.processImpl(data: ProtocolStruct) {
final override suspend fun NoticePipelineContext.processImpl(data: ProtocolStruct) {
when (data) {
is PbMsgInfo -> processImpl(data)
is MsgOnlinePush.PbPushMsg -> processImpl(data)
@ -272,13 +318,13 @@ internal abstract class MixedNoticeProcessor : AnyNoticeProcessor() {
}
}
protected open suspend fun PipelineContext.processImpl(data: MsgType0x210) {} // 528
protected open suspend fun PipelineContext.processImpl(data: MsgType0x2DC) {} // 732
protected open suspend fun PipelineContext.processImpl(data: PbMsgInfo) {}
protected open suspend fun PipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) {}
protected open suspend fun PipelineContext.processImpl(data: MsgComm.Msg) {}
protected open suspend fun PipelineContext.processImpl(data: Structmsg.StructMsg) {}
protected open suspend fun PipelineContext.processImpl(data: RequestPushStatus) {}
protected open suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) {} // 528
protected open suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) {} // 732
protected open suspend fun NoticePipelineContext.processImpl(data: PbMsgInfo) {}
protected open suspend fun NoticePipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) {}
protected open suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) {}
protected open suspend fun NoticePipelineContext.processImpl(data: Structmsg.StructMsg) {}
protected open suspend fun NoticePipelineContext.processImpl(data: RequestPushStatus) {}
protected open suspend fun PipelineContext.processImpl(data: DecodedNotifyMsgBody) {}
protected open suspend fun NoticePipelineContext.processImpl(data: DecodedNotifyMsgBody) {}
}

View File

@ -9,7 +9,7 @@
package net.mamoe.mirai.internal.network.notice
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.utils.io.ProtocolStruct
import net.mamoe.mirai.utils.MiraiLogger
@ -22,11 +22,11 @@ internal class TraceLoggingNoticeProcessor(
) : SimpleNoticeProcessor<ProtocolStruct>(type()) {
private val logger: MiraiLogger = logger.withSwitch(systemProp("mirai.network.notice.trace.logging", false))
override suspend fun PipelineContext.processImpl(data: ProtocolStruct) {
override suspend fun NoticePipelineContext.processImpl(data: ProtocolStruct) {
logger.warning { "${data::class.simpleName}: isConsumed=$isConsumed" }
}
// override suspend fun PipelineContext.processImpl(data: MsgType0x210) {
// override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) {
// logger.warning { "MsgType0x210: isConsumed=$isConsumed" }
// }
//

View File

@ -11,7 +11,7 @@ package net.mamoe.mirai.internal.network.notice
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus
@ -28,7 +28,7 @@ internal class UnconsumedNoticesAlerter(
) : MixedNoticeProcessor() {
private val logger: MiraiLogger = logger.withSwitch(systemProp("mirai.network.notice.unconsumed.logging", false))
override suspend fun PipelineContext.processImpl(data: MsgType0x210) {
override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) {
if (isConsumed) return
when (data.uSubMsgType) {
0x26L, // VIP 进群提示
@ -50,12 +50,12 @@ internal class UnconsumedNoticesAlerter(
}
}
override suspend fun PipelineContext.processImpl(data: MsgType0x2DC) {
override suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) {
if (isConsumed) return
logger.debug { "Unknown group 732 type ${data.kind}, data: " + data.buf.toUHexString() }
}
override suspend fun PipelineContext.processImpl(data: OnlinePushTrans.PbMsgInfo) {
override suspend fun NoticePipelineContext.processImpl(data: OnlinePushTrans.PbMsgInfo) {
if (isConsumed) return
when {
data.msgType == 529 && data.msgSubtype == 9 -> {
@ -89,12 +89,12 @@ internal class UnconsumedNoticesAlerter(
}
}
override suspend fun PipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) {
override suspend fun NoticePipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) {
if (isConsumed) return
}
override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) {
override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) {
if (isConsumed) return
when (data.msgHead.msgType) {
732 -> {
@ -121,7 +121,7 @@ internal class UnconsumedNoticesAlerter(
}
}
override suspend fun PipelineContext.processImpl(data: Structmsg.StructMsg) {
override suspend fun NoticePipelineContext.processImpl(data: Structmsg.StructMsg) {
if (isConsumed) return
if (logger.isEnabled && logger.isDebugEnabled) {
data.msg?.context {
@ -134,7 +134,7 @@ internal class UnconsumedNoticesAlerter(
}
}
override suspend fun PipelineContext.processImpl(data: RequestPushStatus) {
override suspend fun NoticePipelineContext.processImpl(data: RequestPushStatus) {
if (isConsumed) return
if (logger.isEnabled && logger.isDebugEnabled) {
throw contextualBugReportException(

View File

@ -11,13 +11,13 @@ package net.mamoe.mirai.internal.network.notice.decoders
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857
import net.mamoe.mirai.internal.utils.io.ProtocolStruct
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
internal class GroupNotificationDecoder : MixedNoticeProcessor() {
override suspend fun PipelineContext.processImpl(data: MsgType0x2DC) {
override suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) {
when (data.kind) {
0x10 -> {
val proto = data.buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), offset = 1)

View File

@ -14,8 +14,9 @@ import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.contact.checkIsGroupImpl
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.PipelineContext.Companion.KEY_MSG_INFO
import net.mamoe.mirai.internal.getGroupByUin
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_MSG_INFO
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController
import net.mamoe.mirai.internal.network.components.syncOnlinePush
@ -36,7 +37,7 @@ import net.mamoe.mirai.utils.toUHexString
internal class MsgInfoDecoder(
private val logger: MiraiLogger,
) : SimpleNoticeProcessor<SvcReqPushMsg>(type()) {
override suspend fun PipelineContext.processImpl(data: SvcReqPushMsg) {
override suspend fun NoticePipelineContext.processImpl(data: SvcReqPushMsg) {
// SvcReqPushMsg is fully handled here, no need to set consumed.
for (msgInfo in data.vMsgInfos) {
@ -44,16 +45,20 @@ internal class MsgInfoDecoder(
}
}
private suspend fun PipelineContext.decodeMsgInfo(data: MsgInfo) {
private suspend fun NoticePipelineContext.decodeMsgInfo(data: MsgInfo) {
if (!bot.syncController.syncOnlinePush(data)) return
when (data.shMsgType.toUShort().toInt()) {
@Suppress("MoveVariableDeclarationIntoWhen") // for debug
val id = data.shMsgType.toUShort().toInt()
when (id) {
// 528
0x210 -> processAlso(data.vMsg.loadAs(MsgType0x210.serializer()), KEY_MSG_INFO to data)
// 732
0x2dc -> {
data.vMsg.read {
val group = bot.getGroup(readUInt().toLong()) ?: return // group has not been initialized
val groupCode = readUInt().toLong()
val group = bot.getGroup(groupCode) ?: bot.getGroupByUin(groupCode)
?: return // group has not been initialized
group.checkIsGroupImpl()
val kind = readByte().toInt()

View File

@ -23,7 +23,7 @@ import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.contact.newAnonymous
import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController
import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor.MemberNick.Companion.generateMemberNickFromMember
@ -67,7 +67,7 @@ internal class GroupMessageProcessor(
}
override suspend fun PipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) {
override suspend fun NoticePipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) {
val msgHead = data.msg.msgHead
val isFromSelfAccount = msgHead.fromUin == bot.id

View File

@ -20,8 +20,7 @@ import net.mamoe.mirai.internal.contact.checkIsGroupImpl
import net.mamoe.mirai.internal.contact.checkIsMemberImpl
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.notice.NewContactSupport
import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
@ -32,9 +31,11 @@ import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.utils.*
internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSupport {
internal class GroupNotificationProcessor(
private val logger: MiraiLogger,
) : MixedNoticeProcessor(), NewContactSupport {
override suspend fun PipelineContext.processImpl(data: MsgType0x210) = data.context {
override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) = data.context {
when (data.uSubMsgType) {
0x27L -> {
val body = vProtobuf.loadAs(Submsgtype0x27.SubMsgType0x27.SubMsgType0x27MsgBody.serializer())
@ -53,7 +54,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
/**
* @see GroupNameChangeEvent
*/
private fun PipelineContext.handleGroupProfileChanged(
private fun NoticePipelineContext.handleGroupProfileChanged(
modGroupProfile: Submsgtype0x27.SubMsgType0x27.ModGroupProfile
) {
for (info in modGroupProfile.msgGroupProfileInfos) {
@ -111,7 +112,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
/**
* @see MemberCardChangeEvent
*/
private fun PipelineContext.handleGroupMemberProfileChanged(
private fun NoticePipelineContext.handleGroupMemberProfileChanged(
modGroupMemberProfile: Submsgtype0x27.SubMsgType0x27.ModGroupMemberProfile
) {
for (info in modGroupMemberProfile.msgGroupMemberProfileInfos) {
@ -132,14 +133,14 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
}
2 -> {
if (info.value.singleOrNull()?.code != 0) {
bot.logger.debug {
logger.debug {
"Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}"
}
}
continue
}
else -> {
bot.logger.debug {
logger.debug {
"Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}"
}
continue
@ -153,7 +154,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
// MsgType0x2DC
///////////////////////////////////////////////////////////////////////////
override suspend fun PipelineContext.processImpl(data: MsgType0x2DC) {
override suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) {
when (data.kind) {
0x0C -> processMute(data)
0x0E -> processAllowAnonymousChat(data)
@ -169,7 +170,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
* @see BotMuteEvent
* @see BotUnmuteEvent
*/
private fun PipelineContext.processMute(
private fun NoticePipelineContext.processMute(
data: MsgType0x2DC,
) = data.context {
fun handleMuteMemberPacket(
@ -232,7 +233,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
/**
* @see GroupAllowAnonymousChatEvent
*/
private fun PipelineContext.processAllowAnonymousChat(
private fun NoticePipelineContext.processAllowAnonymousChat(
data: MsgType0x2DC,
) = data.context {
markAsConsumed()
@ -249,7 +250,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
/**
* @see GroupAllowConfessTalkEvent
*/
private fun PipelineContext.processAllowConfessTask(
private fun NoticePipelineContext.processAllowConfessTask(
data: MsgType0x2DC,
) = data.context {
val proto = data.buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), offset = 1)
@ -268,7 +269,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
"管理员已关闭群聊坦白说" -> false
"管理员已开启群聊坦白说" -> true
else -> {
bot.network.logger.debug { "Unknown server confess talk messages $message" }
logger.debug { "Unknown server confess talk messages $message" }
return
}
}
@ -286,7 +287,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
* @see MemberHonorChangeEvent
* @see GroupTalkativeChangeEvent
*/ // gray tip: 聊天中的灰色小框系统提示信息
private fun PipelineContext.processGrayTip(
private fun NoticePipelineContext.processGrayTip(
data: MsgType0x2DC,
) = data.context {
val grayTip = buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), 1).optGeneralGrayTip
@ -324,7 +325,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu
}
else -> {
markNotConsumed()
bot.network.logger.debug {
logger.debug {
"Unknown Transformers528 0x14 template\ntemplId=${grayTip?.templId}\nPermList=${grayTip?.msgTemplParam?._miraiContentToString()}"
}
}

View File

@ -24,7 +24,7 @@ import net.mamoe.mirai.internal.getGroupByUin
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.components.ContactUpdater
import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.notice.NewContactSupport
import net.mamoe.mirai.internal.network.notice.decoders.DecodedNotifyMsgBody
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
@ -61,7 +61,7 @@ internal class GroupOrMemberListNoticeProcessor(
private val logger: MiraiLogger,
) : MixedNoticeProcessor(), NewContactSupport {
override suspend fun PipelineContext.processImpl(data: MsgType0x210) {
override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) {
if (data.uSubMsgType != 0x44L) return
markAsConsumed()
val msg = data.vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer())
@ -82,7 +82,7 @@ internal class GroupOrMemberListNoticeProcessor(
* @see MemberJoinEvent.Invite
* @see MemberLeaveEvent.Quit
*/
override suspend fun PipelineContext.processImpl(data: DecodedNotifyMsgBody) = data.context {
override suspend fun NoticePipelineContext.processImpl(data: DecodedNotifyMsgBody) = data.context {
val proto = data.buf
if (proto.optEnumType != 1) return
val tipsInfo = proto.optMsgGraytips ?: return
@ -120,7 +120,7 @@ internal class GroupOrMemberListNoticeProcessor(
* @see MemberJoinEvent.Active
* @see BotJoinGroupEvent.Active
*/
override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context {
override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) = data.context {
bot.components[ContactUpdater].groupListModifyLock.withLock {
when (data.msgHead.msgType) {
33 -> processGroupJoin33(data)
@ -134,7 +134,7 @@ internal class GroupOrMemberListNoticeProcessor(
}
// 33
private suspend fun PipelineContext.processGroupJoin33(data: MsgComm.Msg) = data.context {
private suspend fun NoticePipelineContext.processGroupJoin33(data: MsgComm.Msg) = data.context {
msgBody.msgContent.read {
val groupUin = Mirai.calculateGroupUinByGroupCode(readUInt().toLong())
val group = bot.getGroupByUin(groupUin) ?: bot.addNewGroupByUin(groupUin) ?: return
@ -178,13 +178,13 @@ internal class GroupOrMemberListNoticeProcessor(
}
// 38
private suspend fun PipelineContext.processGroupJoin38(data: MsgComm.Msg) = data.context {
private suspend fun NoticePipelineContext.processGroupJoin38(data: MsgComm.Msg) = data.context {
if (bot.getGroupByUin(msgHead.fromUin) != null) return
bot.addNewGroupByUin(msgHead.fromUin)?.let { collect(BotJoinGroupEvent.Active(it)) }
}
// 85
private suspend fun PipelineContext.processGroupJoin85(data: MsgComm.Msg) = data.context {
private suspend fun NoticePipelineContext.processGroupJoin85(data: MsgComm.Msg) = data.context {
// msgHead.authUin: 处理人
if (msgHead.toUin != bot.id) return
processGroupJoin38(data)
@ -194,7 +194,7 @@ internal class GroupOrMemberListNoticeProcessor(
// Structmsg.StructMsg
///////////////////////////////////////////////////////////////////////////
override suspend fun PipelineContext.processImpl(data: Structmsg.StructMsg) = data.msg.context {
override suspend fun NoticePipelineContext.processImpl(data: Structmsg.StructMsg) = data.msg.context {
if (this == null) return
markAsConsumed()
when (subType) {
@ -270,7 +270,7 @@ internal class GroupOrMemberListNoticeProcessor(
// OnlinePushTrans.PbMsgInfo
///////////////////////////////////////////////////////////////////////////
override suspend fun PipelineContext.processImpl(data: OnlinePushTrans.PbMsgInfo) {
override suspend fun NoticePipelineContext.processImpl(data: OnlinePushTrans.PbMsgInfo) {
markAsConsumed()
when (data.msgType) {
44 -> data.msgData.read {
@ -327,7 +327,7 @@ internal class GroupOrMemberListNoticeProcessor(
}
}
private fun PipelineContext.handleLeave(
private fun NoticePipelineContext.handleLeave(
target: Long,
kind: Int,
operator: Long,
@ -368,7 +368,7 @@ internal class GroupOrMemberListNoticeProcessor(
* @see BotGroupPermissionChangeEvent
* @see MemberPermissionChangeEvent
*/
private fun PipelineContext.handlePermissionChange(
private fun NoticePipelineContext.handlePermissionChange(
data: OnlinePushTrans.PbMsgInfo,
target: Long,
newPermissionByte: Int,
@ -395,7 +395,7 @@ internal class GroupOrMemberListNoticeProcessor(
* Owner of the group [from] transfers ownership to another member [to], or retrieve ownership.
*/
// TODO: 2021/6/26 tests
private suspend fun PipelineContext.handleGroupOwnershipTransfer(
private suspend fun NoticePipelineContext.handleGroupOwnershipTransfer(
data: OnlinePushTrans.PbMsgInfo,
from: Long,
to: Long,

View File

@ -11,15 +11,16 @@ package net.mamoe.mirai.internal.network.notice.group
import net.mamoe.mirai.event.events.MessageRecallEvent
import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC
import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.utils.mapToIntArray
internal class GroupRecallProcessor : MixedNoticeProcessor() {
override suspend fun PipelineContext.processImpl(data: MsgType0x2DC) {
val (_, group, buf) = data
override suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) {
val (kind, group, buf) = data
if (kind != 0x11) return
val proto = buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), 1)

View File

@ -21,8 +21,8 @@ import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
import net.mamoe.mirai.internal.contact.toMiraiFriendInfo
import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.PipelineContext.Companion.msgInfo
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.msgInfo
import net.mamoe.mirai.internal.network.notice.NewContactSupport
import net.mamoe.mirai.internal.network.notice.group.get
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
@ -50,7 +50,7 @@ import net.mamoe.mirai.utils.*
internal class FriendNoticeProcessor(
private val logger: MiraiLogger,
) : MixedNoticeProcessor(), NewContactSupport {
override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context {
override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) = data.context {
if (msgHead.msgType != 191) return
var fromGroup = 0L
@ -93,7 +93,7 @@ internal class FriendNoticeProcessor(
}
override suspend fun PipelineContext.processImpl(data: MsgType0x210) = data.context {
override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) = data.context {
markAsConsumed()
when (data.uSubMsgType) {
0xB3L -> {
@ -166,7 +166,7 @@ internal class FriendNoticeProcessor(
@ProtoNumber(5) val reserved: ByteArray? = null, // struct{ boolean(1), boolean(2) }
) : ProtoBuf
private fun PipelineContext.processFriendRecall(body: Sub8A) {
private fun NoticePipelineContext.processFriendRecall(body: Sub8A) {
for (info in body.msgInfo) {
if (info.botUin != bot.id) continue
collected += MessageRecallEvent.FriendRecall(
@ -181,13 +181,13 @@ internal class FriendNoticeProcessor(
}
private fun PipelineContext.handleInputStatusChanged(body: SubMsgType0x115.MsgBody) {
private fun NoticePipelineContext.handleInputStatusChanged(body: SubMsgType0x115.MsgBody) {
val friend = bot.getFriend(body.fromUin) ?: return
val item = body.msgNotifyItem ?: return
collect(FriendInputStatusChangedEvent(friend, item.eventType == 1))
}
private fun PipelineContext.handleProfileChanged(body: ModProfile) {
private fun NoticePipelineContext.handleProfileChanged(body: ModProfile) {
var containsUnknown = false
for (profileInfo in body.msgProfileInfos) {
when (profileInfo.field) {
@ -214,7 +214,7 @@ internal class FriendNoticeProcessor(
}
}
private fun PipelineContext.handleRemarkChanged(body: ModFriendRemark) {
private fun NoticePipelineContext.handleRemarkChanged(body: ModFriendRemark) {
for (new in body.msgFrdRmk) {
val friend = bot.getFriend(new.fuin)?.impl() ?: continue
@ -223,20 +223,20 @@ internal class FriendNoticeProcessor(
}
}
private fun PipelineContext.handleAvatarChanged(body: ModCustomFace) {
private fun NoticePipelineContext.handleAvatarChanged(body: ModCustomFace) {
if (body.uin == bot.id) {
collect(BotAvatarChangedEvent(bot))
}
collect(FriendAvatarChangedEvent(bot.getFriend(body.uin) ?: return))
}
private fun PipelineContext.handleFriendDeleted(body: DelFriend) {
private fun NoticePipelineContext.handleFriendDeleted(body: DelFriend) {
for (id in body.uint64Uins) {
collect(FriendDeleteEvent(bot.removeFriend(id) ?: continue))
}
}
private suspend fun PipelineContext.handleFriendAddedA(
private suspend fun NoticePipelineContext.handleFriendAddedA(
body: Submsgtype0x44.MsgBody,
) = body.msgFriendMsgSync.context {
if (this == null) return
@ -255,20 +255,21 @@ internal class FriendNoticeProcessor(
}
}
private fun PipelineContext.handleFriendAddedB(data: MsgType0x210, body: SubMsgType0xb3.MsgBody) = data.context {
val info = FriendInfoImpl(
uin = body.msgAddFrdNotify.fuin,
nick = body.msgAddFrdNotify.fuinNick,
remark = "",
)
private fun NoticePipelineContext.handleFriendAddedB(data: MsgType0x210, body: SubMsgType0xb3.MsgBody) =
data.context {
val info = FriendInfoImpl(
uin = body.msgAddFrdNotify.fuin,
nick = body.msgAddFrdNotify.fuinNick,
remark = "",
)
val removed = bot.removeStranger(info.uin)
val added = bot.addNewFriendAndRemoveStranger(info) ?: return
collect(FriendAddEvent(added))
if (removed != null) collect(StrangerRelationChangeEvent.Friended(removed, added))
val removed = bot.removeStranger(info.uin)
val added = bot.addNewFriendAndRemoveStranger(info) ?: return
collect(FriendAddEvent(added))
if (removed != null) collect(StrangerRelationChangeEvent.Friended(removed, added))
}
private fun PipelineContext.handlePrivateNudge(body: Submsgtype0x122.Submsgtype0x122.MsgBody) {
private fun NoticePipelineContext.handlePrivateNudge(body: Submsgtype0x122.Submsgtype0x122.MsgBody) {
val action = body.msgTemplParam["action_str"].orEmpty()
val from = body.msgTemplParam["uin_str1"]?.findFriendOrStranger() ?: bot.asFriend
val target = body.msgTemplParam["uin_str2"]?.findFriendOrStranger() ?: bot.asFriend

View File

@ -26,7 +26,7 @@ import net.mamoe.mirai.internal.message.OnlineMessageSourceFromFriendImpl
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.components.ContactUpdater
import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
@ -48,7 +48,7 @@ internal class OtherClientNoticeProcessor : MixedNoticeProcessor() {
* @see OtherClientOnlineEvent
* @see OtherClientOfflineEvent
*/
override suspend fun PipelineContext.processImpl(data: RequestPushStatus) {
override suspend fun NoticePipelineContext.processImpl(data: RequestPushStatus) {
markAsConsumed()
bot.components[ContactUpdater].otherClientsLock.withLock {
val instanceInfo = data.vecInstanceList?.firstOrNull()
@ -103,7 +103,7 @@ internal class OtherClientNoticeProcessor : MixedNoticeProcessor() {
/**
* @see OtherClientMessageEvent
*/
override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context {
override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) = data.context {
if (msgHead.msgType != 529) return
// top_package/awbk.java:3765

View File

@ -13,8 +13,8 @@ import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.getGroupByUin
import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.PipelineContext.Companion.fromSync
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.fromSync
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor
@ -35,7 +35,7 @@ import net.mamoe.mirai.utils.context
* @see GroupTempMessageSyncEvent
*/
internal class PrivateMessageProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type()) {
override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context {
override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) = data.context {
markAsConsumed()
if (msgHead.fromUin == bot.id && fromSync) {
// Bot send message to himself? or from other client? I am not the implementer.
@ -67,7 +67,7 @@ internal class PrivateMessageProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type
}
private suspend fun PipelineContext.handlePrivateMessage(
private suspend fun NoticePipelineContext.handlePrivateMessage(
data: MsgComm.Msg,
user: AbstractUser,
) = data.context {

View File

@ -414,7 +414,7 @@ internal class ImMsgBody : ProtoBuf {
@Serializable
internal class ExtraInfo(
@ProtoNumber(1) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(1) @JvmField val nick: String = "",
@ProtoNumber(2) @JvmField val groupCard: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(3) @JvmField val level: Int = 0,
@ProtoNumber(4) @JvmField val flags: Int = 0,

View File

@ -22,8 +22,8 @@ import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.MultiPacket
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline
import net.mamoe.mirai.internal.network.components.PipelineContext.Companion.KEY_FROM_SYNC
import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController
import net.mamoe.mirai.internal.network.components.syncGetMessage
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm

View File

@ -12,8 +12,8 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline
import net.mamoe.mirai.internal.network.components.PipelineContext.Companion.KEY_FROM_SYNC
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf

View File

@ -45,9 +45,9 @@ internal class MockBotBuilder(
}
@Suppress("TestFunctionName")
internal fun MockBot(conf: MockBotBuilder.() -> Unit = {}): QQAndroidBot {
internal fun MockBot(account: BotAccount = MockAccount, conf: MockBotBuilder.() -> Unit = {}): QQAndroidBot {
return MockBotBuilder(MockConfiguration.copy()).apply(conf).run {
object : QQAndroidBot(MockAccount, this.conf) {
object : QQAndroidBot(account, this.conf) {
override fun createBotLevelComponents(): ConcurrentComponentStorage {
return super.createBotLevelComponents().apply {
val componentsProvider = additionalComponentsProvider

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.internal.network.framework
import net.mamoe.mirai.internal.BotAccount
import net.mamoe.mirai.internal.MockAccount
import net.mamoe.mirai.internal.MockConfiguration
import net.mamoe.mirai.internal.QQAndroidBot
@ -43,8 +44,10 @@ internal sealed class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abstr
abstract val factory: NetworkHandlerFactory<H>
abstract val network: H
var bot: QQAndroidBot by lateinitMutableProperty {
object : QQAndroidBot(MockAccount, MockConfiguration.copy()) {
var bot: QQAndroidBot by lateinitMutableProperty { createBot() }
protected open fun createBot(account: BotAccount = MockAccount): QQAndroidBot {
return object : QQAndroidBot(account, MockConfiguration.copy()) {
override fun createBotLevelComponents(): ConcurrentComponentStorage =
super.createBotLevelComponents().apply { setAll(overrideComponents) }

View File

@ -0,0 +1,182 @@
/*
* 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.serialization.decodeFromString
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.internal.utils.codegen.*
import net.mamoe.mirai.utils.*
import net.mamoe.yamlkt.Yaml
import net.mamoe.yamlkt.YamlBuilder
import kotlin.reflect.KType
import kotlin.reflect.typeOf
private val logger: MiraiLogger by lazy { MiraiLogger.Factory.create(Desensitizer::class) }
internal class Desensitizer private constructor(
val rules: Map<String, String>,
) {
fun desensitize(value: String): String {
return rules.entries.fold(value) { acc, entry ->
acc.replace(entry.key, entry.value)
}
}
fun desensitize(value: ByteArray): ByteArray {
return desensitize(value.toUHexString()).hexToBytes()
}
fun desensitize(value: Array<Byte>): Array<Byte> {
return desensitize(value.toUHexString()).hexToBytes().toTypedArray()
}
companion object {
private val instance by lateinitMutableProperty {
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!")
format.decodeFromString(file.readText())
}.also {
logger.info { "Loaded ${it.size} desensitization rules." }
}
)
}
/**
* Loaded from local.desensitization.yml
*/
val local get() = instance
fun desensitize(string: String): String = instance.desensitize(string)
fun ConstructorCallCodegenFacade.generateAndDesensitize(
value: Any?,
type: KType,
desensitizer: Desensitizer = instance,
): String {
val a = analyze(value, type).apply {
accept(DesensitizationVisitor(desensitizer))
}
return generate(a)
}
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> ConstructorCallCodegenFacade.generateAndDesensitize(
value: T,
desensitizer: Desensitizer = instance,
): String = generateAndDesensitize(value, typeOf<T>(), desensitizer)
fun create(rules: Map<String, String>): Desensitizer {
val map = HashMap<String, String>()
map.putAll(rules)
fun addExtraRulesForString(value: String, replacement: String) {
// in proto, strings have lengths field, we must ensure that their lengths are intact.
when {
value.length > replacement.length -> {
map[value.toByteArray().toUHexString()] =
(replacement + "0".repeat(value.length - replacement.length)).toByteArray()
.toUHexString() // fix it to the same length
}
value.length < replacement.length -> {
error("Replacement '$replacement' must not be longer than '$value'")
}
else -> {
map.putIfAbsent(value.toByteArray().toUHexString(), replacement.toByteArray().toUHexString())
}
}
}
fun addExtraRulesForNumber(value: Long, replacement: Long) {
map.putIfAbsent(value.toString(), replacement.toString())
// 某些地方会 readLong, readInt, desensitizer visit 不到这些目标
map.putIfAbsent(value.toByteArray().toUHexString(), replacement.toByteArray().toUHexString())
if (value in Int.MIN_VALUE.toLong()..UInt.MAX_VALUE.toLong()
&& replacement in Int.MIN_VALUE.toLong()..UInt.MAX_VALUE.toLong()
) {
map.putIfAbsent(
value.toInt().toByteArray().toUHexString(),
replacement.toInt().toByteArray().toUHexString()
)
}
// 不需要处理 proto, 所有 proto 都会被反序列化为结构类型由 desensitizer 处理
}
rules.forEach { (t, u) ->
if (t.toLongOrNull() != null && u.toLongOrNull() != null) {
addExtraRulesForNumber(t.toLong(), u.toLong())
addExtraRulesForNumber(
Mirai.calculateGroupUinByGroupCode(t.toLong()),
Mirai.calculateGroupUinByGroupCode(u.toLong())
) // putIfAbsent, code prevails
}
addExtraRulesForString(t, u)
}
return Desensitizer(map)
}
}
}
private val format = 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
}
private class DesensitizationVisitor(
private val desensitizer: Desensitizer,
) : ValueDescVisitor {
override fun visitPlain(desc: PlainValueDesc) {
desc.value = desensitizer.desensitize(desc.value)
}
override fun visitObjectArray(desc: ObjectArrayValueDesc) {
if (desc.arrayType.arguments.first().type?.classifier == Byte::class) { // variance is ignored
@Suppress("UNCHECKED_CAST")
desc.value = desensitizer.desensitize(desc.value as Array<Byte>)
} else {
for (element in desc.elements) {
element.accept(this)
}
}
}
override fun visitCollection(desc: CollectionValueDesc) {
for (element in desc.elements) {
element.accept(this)
}
}
override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc) {
if (desc.value is ByteArray) {
desc.value = desensitizer.desensitize(desc.value as ByteArray)
}
}
}

View File

@ -12,144 +12,39 @@ 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.NoticePipelineContext
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.notice.Desensitizer.Companion.generateAndDesensitize
import net.mamoe.mirai.internal.utils.codegen.ConstructorCallCodegenFacade
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
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.info
/**
* How to inject recorder?
* ### How to use recorder?
*
* 0. Configure desensitization. See mirai-core/src/commonTest/recording/configs/desensitization.yml
* 1. Inject the recorder as follows:
*
* ```
* bot.components[NoticeProcessorPipeline].registerProcessor(recorder)
* ```
*
* 2. Do something
* 3. Recorded values are shown in logs. Check 'decoded' to ensure that all sensitive values are replaced.
*/
internal class RecordingNoticeProcessor : SimpleNoticeProcessor<ProtocolStruct>(type()) {
private val id = atomic(0)
private val lock = Mutex()
override suspend fun PipelineContext.processImpl(data: ProtocolStruct) {
override suspend fun NoticePipelineContext.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() }
logger.info { "Desensitized: \n\n\u001B[0m" + ConstructorCallCodegenFacade.generateAndDesensitize(data) + "\n\n" }
}
}
@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)
}
}
}
private val logger: MiraiLogger by lazy { MiraiLogger.Factory.create(RecordingNoticeProcessor::class) }

View File

@ -0,0 +1,180 @@
/*
* 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.processors
import net.mamoe.mirai.Bot
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.internal.BotAccount
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.contact.NormalMemberImpl
import net.mamoe.mirai.internal.contact.info.GroupInfoImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.network.components.LoggingPacketHandlerAdapter
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.noticeProcessorPipeline
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipelineImpl
import net.mamoe.mirai.internal.network.components.PacketLoggingStrategyImpl
import net.mamoe.mirai.internal.network.components.ProcessResult
import net.mamoe.mirai.internal.network.framework.AbstractNettyNHTest
import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket
import net.mamoe.mirai.internal.utils.io.ProtocolStruct
import net.mamoe.mirai.utils.TypeSafeMap
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.hexToUBytes
import java.util.*
/**
* To add breakpoint, see [NoticeProcessorPipelineImpl.process]
*/
internal abstract class AbstractNoticeProcessorTest : AbstractNettyNHTest(), GroupExtensions {
init {
System.setProperty("mirai.network.notice.pipeline.log.full", "true")
}
protected object UseTestContext {
val EMPTY_BYTE_ARRAY get() = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
fun String.hexToBytes() = hexToUBytes().toByteArray()
}
protected suspend inline fun use(
attributes: TypeSafeMap = TypeSafeMap(),
block: UseTestContext.() -> ProtocolStruct
): ProcessResult {
val handler = LoggingPacketHandlerAdapter(PacketLoggingStrategyImpl(bot), bot.logger)
return bot.components.noticeProcessorPipeline.process(bot, block(UseTestContext), attributes).also { list ->
for (packet in list) {
handler.handlePacket(IncomingPacket("test", packet))
}
}
}
private val properties = Properties().apply {
load(Thread.currentThread().contextClassLoader.getResourceAsStream("recording/data/notice/NoticeProcessorTestData.properties"))
}
fun setBot(id: Long): QQAndroidBot {
bot = createBot(BotAccount(id, "a"))
return bot
}
}
internal interface GroupExtensions {
@Suppress("TestFunctionName")
fun GroupInfo(
uin: Long,
owner: Long,
groupCode: Long,
memo: String = "",
name: String,
allowMemberInvite: Boolean = false,
allowAnonymousChat: Boolean = false,
autoApprove: Boolean = false,
confessTalk: Boolean = false,
muteAll: Boolean = false,
botMuteTimestamp: Int = 0,
): GroupInfoImpl =
GroupInfoImpl(
uin, owner, groupCode, memo, name,
allowMemberInvite, allowAnonymousChat, autoApprove, confessTalk, muteAll,
botMuteTimestamp
)
fun Bot.addGroup(group: Group) {
groups.delegate.add(group)
}
fun Bot.addFriend(friend: Friend) {
friends.delegate.add(friend)
}
fun Group.addMember(member: NormalMember) {
members.delegate.add(member)
}
fun Bot.addGroup(
id: Long,
owner: Long,
botPermission: MemberPermission = MemberPermission.MEMBER,
memo: String = "",
name: String = "Test Group",
allowMemberInvite: Boolean = false,
allowAnonymousChat: Boolean = false,
autoApprove: Boolean = false,
confessTalk: Boolean = false,
muteAll: Boolean = false,
botMuteTimestamp: Int = 0,
): GroupImpl {
val impl = GroupImpl(
bot.cast(), coroutineContext, id,
GroupInfo(
Mirai.calculateGroupUinByGroupCode(id), owner, id, memo, name, allowMemberInvite,
allowAnonymousChat, autoApprove, confessTalk, muteAll, botMuteTimestamp
),
ContactList(),
)
addGroup(impl)
impl.botAsMember = impl.addMember(bot.id, nick = bot.nick, permission = botPermission)
return impl
}
fun Bot.addGroup(
id: Long,
info: GroupInfoImpl,
botPermission: MemberPermission = MemberPermission.MEMBER,
): Group {
val impl = GroupImpl(
bot.cast(), coroutineContext, id, info,
ContactList(),
)
addGroup(impl)
impl.botAsMember = impl.addMember(bot.id, nick = bot.nick, permission = botPermission)
return impl
}
fun Group.addMember(
id: Long,
nick: String,
permission: MemberPermission,
remark: String = "",
nameCard: String = "",
specialTitle: String = "",
muteTimestamp: Int = 0,
anonymousId: String? = null,
joinTimestamp: Int = currentTimeSeconds().toInt(),
lastSpeakTimestamp: Int = 0,
isOfficialBot: Boolean = false,
): NormalMemberImpl {
val member = NormalMemberImpl(
this.cast(), this.coroutineContext,
MemberInfoImpl(
id, nick, permission, remark, nameCard,
specialTitle, muteTimestamp, anonymousId, joinTimestamp, lastSpeakTimestamp, isOfficialBot
)
)
members.delegate.add(
member
)
return member
}
fun Group.addMember(
id: Long,
info: MemberInfoImpl,
): Group {
members.delegate.add(NormalMemberImpl(this.cast(), this.coroutineContext, info))
return this
}
}

View File

@ -11,22 +11,25 @@ package net.mamoe.mirai.internal.notice.test
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.protobuf.ProtoNumber
import net.mamoe.mirai.internal.MockBot
import net.mamoe.mirai.internal.network.components.AbstractPipelineContext
import net.mamoe.mirai.internal.network.components.AbstractNoticePipelineContext
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.notice.Desensitizer.Companion.generateAndDesensitize
import net.mamoe.mirai.internal.test.AbstractTest
import net.mamoe.mirai.internal.utils.codegen.ConstructorCallCodegenFacade
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 net.mamoe.yamlkt.YamlBuilder
import kotlin.test.Test
import kotlin.test.assertEquals
internal class RecordingNoticeProcessorTest : AbstractTest() {
class MyContext(attributes: TypeSafeMap) : AbstractPipelineContext(MockBot(), attributes) {
class MyContext(attributes: TypeSafeMap) : AbstractNoticePipelineContext(MockBot(), attributes) {
override suspend fun processAlso(data: ProtocolStruct, attributes: TypeSafeMap): ProcessResult {
throw UnsupportedOperationException()
}
@ -42,29 +45,23 @@ internal class RecordingNoticeProcessorTest : AbstractTest() {
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))
val serialize = ConstructorCallCodegenFacade.generateAndDesensitize(struct)
assertEquals(
mapOf(
"123456789" to "111",
"987654321" to "111"
), desensitizer.rules
"""
net.mamoe.mirai.internal.notice.test.RecordingNoticeProcessorTest.MyProtocolStruct(
value="vvv",
)
""".trimIndent(),
serialize
)
// val deserialized = KotlinScriptExternalDependencies
//
// assertEquals(context.attributes, deserialized.attributes)
// assertEquals(struct, deserialized.struct)
}
@Test
fun `test desensitization`() {
fun `test plain desensitization`() {
val text = Thread.currentThread().contextClassLoader.getResource("recording/configs/test.desensitization.yml")!!
.readText()
val desensitizer = Desensitizer.create(Yaml.decodeFromString(text))
@ -82,6 +79,48 @@ internal class RecordingNoticeProcessorTest : AbstractTest() {
""".trim()
)
)
}
@Serializable
data class TestProto(
@ProtoNumber(1) val proto: Proto
) : ProtocolStruct {
@Serializable
data class Proto(
@ProtoNumber(1) val int: Int
)
}
@Serializable
data class ByteArrayWrapper(
val value: ByteArray
)
val format = 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
}
@Test
fun `test long as byte array desensitization`() {
val text = Thread.currentThread().contextClassLoader.getResource("recording/configs/test.desensitization.yml")!!
.readText()
val desensitizer = Desensitizer.create(Yaml.decodeFromString(text))
val proto = TestProto(TestProto.Proto(123456789))
assertEquals(
TestProto(TestProto.Proto(111)),
format.decodeFromString(
TestProto.serializer(),
desensitizer.desensitize(format.encodeToString(TestProto.serializer(), proto))
)
)
}
}

View File

@ -2,10 +2,11 @@
#
# Format:
# ```
# <sensitive value>: <replacer>
# <sensitive value>: <replacement>
# ```
#
# If key is a number, its group uin counterpart will also be processed, with calculated replacer.
# WARNING: Ensure the <replacement> is not longer than <sensitive value>.
#
# For example, if your account id is 147258369, you may add:
# ```

View File

@ -0,0 +1,52 @@
/*
* 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.bootstrap
import kotlinx.serialization.Serializable
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotFactory
import net.mamoe.mirai.internal.asQQAndroidBot
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline
import net.mamoe.mirai.internal.notice.Desensitizer
import net.mamoe.mirai.internal.notice.RecordingNoticeProcessor
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.readResource
import net.mamoe.yamlkt.Yaml
import kotlin.concurrent.thread
@Serializable
data class LocalAccount(
val id: Long,
val password: String
)
suspend fun main() {
Runtime.getRuntime().addShutdownHook(thread(start = false) {
Bot.instances.forEach {
it.close()
}
})
Desensitizer.local.desensitize("") // verify rules
val account = Yaml.decodeFromString(LocalAccount.serializer(), readResource("local.account.yml"))
val bot = BotFactory.newBot(account.id, account.password) {
enableContactCache()
fileBasedDeviceInfo("local.device.json")
protocol = BotConfiguration.MiraiProtocol.ANDROID_PHONE
}.asQQAndroidBot()
bot.components[NoticeProcessorPipeline].registerProcessor(RecordingNoticeProcessor())
bot.login()
bot.join()
}

View File

@ -0,0 +1,2 @@
id: 123
password: ""