Kaynağa Gözat

Redesign packet recording

Him188 4 yıl önce
ebeveyn
işleme
76e2b6c64c
27 değiştirilmiş dosya ile 712 ekleme ve 271 silme
  1. 21 0
      .run/RunRecorderKt.run.xml
  2. 3 1
      mirai-core/src/commonMain/kotlin/QQAndroidBot.kt
  3. 26 16
      mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt
  4. 70 24
      mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt
  5. 3 3
      mirai-core/src/commonMain/kotlin/network/notice/TraceLoggingNoticeProcessor.kt
  6. 8 8
      mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt
  7. 2 2
      mirai-core/src/commonMain/kotlin/network/notice/decoders/GroupNotificationDecoder.kt
  8. 11 6
      mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgInfoDecoder.kt
  9. 2 2
      mirai-core/src/commonMain/kotlin/network/notice/group/GroupMessageProcessor.kt
  10. 16 15
      mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt
  11. 12 12
      mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt
  12. 4 3
      mirai-core/src/commonMain/kotlin/network/notice/group/GroupRecallProcessor.kt
  13. 23 22
      mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt
  14. 3 3
      mirai-core/src/commonMain/kotlin/network/notice/priv/OtherClientNoticeProcessor.kt
  15. 4 4
      mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt
  16. 1 1
      mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt
  17. 1 1
      mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
  18. 1 1
      mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbC2CMsgSync.kt
  19. 2 2
      mirai-core/src/commonTest/kotlin/MockBot.kt
  20. 5 2
      mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt
  21. 182 0
      mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt
  22. 16 121
      mirai-core/src/commonTest/kotlin/notice/RecordingNoticeHandler.kt
  23. 180 0
      mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt
  24. 60 21
      mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt
  25. 2 1
      mirai-core/src/commonTest/resources/recording/configs/desensitization.yml
  26. 52 0
      mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt
  27. 2 0
      mirai-core/src/jvmTest/resources/account.yml

+ 21 - 0
.run/RunRecorderKt.run.xml

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

+ 3 - 1
mirai-core/src/commonMain/kotlin/QQAndroidBot.kt

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

+ 26 - 16
mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt

@@ -65,13 +65,37 @@ internal fun Group.checkIsGroupImpl(): GroupImpl {
     return this
     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")
 @Suppress("PropertyName")
-internal class GroupImpl(
+internal class GroupImpl constructor(
     bot: QQAndroidBot,
     bot: QQAndroidBot,
     parentCoroutineContext: CoroutineContext,
     parentCoroutineContext: CoroutineContext,
     override val id: Long,
     override val id: Long,
     groupInfo: GroupInfo,
     groupInfo: GroupInfo,
-    members: Sequence<MemberInfo>,
+    override val members: ContactList<NormalMemberImpl>,
 ) : Group, AbstractContact(bot, parentCoroutineContext) {
 ) : Group, AbstractContact(bot, parentCoroutineContext) {
     companion object
     companion object
 
 
@@ -84,20 +108,6 @@ internal class GroupImpl(
 
 
     override val filesRoot: RemoteFile by lazy { RemoteFileImpl(this, "/") }
     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 {
     override val announcements: Announcements by lazy {
         AnnouncementsImpl(
         AnnouncementsImpl(

+ 70 - 24
mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt

@@ -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.network.toPacket
 import net.mamoe.mirai.internal.utils.io.ProtocolStruct
 import net.mamoe.mirai.internal.utils.io.ProtocolStruct
 import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.*
+import java.io.Closeable
 import java.util.*
 import java.util.*
 import java.util.concurrent.ConcurrentLinkedQueue
 import java.util.concurrent.ConcurrentLinkedQueue
 import kotlin.reflect.KClass
 import kotlin.reflect.KClass
@@ -40,7 +41,15 @@ internal typealias ProcessResult = Collection<Packet>
  * Centralized processor pipeline for [MessageSvcPbGetMsg] and [OnlinePushPbPushTransMsg]
  * Centralized processor pipeline for [MessageSvcPbGetMsg] and [OnlinePushPbPushTransMsg]
  */
  */
 internal interface NoticeProcessorPipeline {
 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]
      * Process [data] into [Packet]s. Exceptions are wrapped into [ParseErrorPacket]
@@ -69,7 +78,7 @@ internal value class MutableProcessResult(
     val data: MutableCollection<Packet>
     val data: MutableCollection<Packet>
 )
 )
 
 
-internal interface PipelineContext : BotAware {
+internal interface NoticePipelineContext : BotAware {
     override val bot: QQAndroidBot
     override val bot: QQAndroidBot
 
 
     val attributes: TypeSafeMap
     val attributes: TypeSafeMap
@@ -128,28 +137,32 @@ internal interface PipelineContext : BotAware {
         val KEY_FROM_SYNC = TypeKey<Boolean>("fromSync")
         val KEY_FROM_SYNC = TypeKey<Boolean>("fromSync")
         val KEY_MSG_INFO = TypeKey<MsgInfo>("msgInfo")
         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] 的处理过程之中可以使用
          * 来自 [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,
     override val bot: QQAndroidBot, override val attributes: TypeSafeMap,
-) : PipelineContext {
+) : NoticePipelineContext {
     private val consumers: Stack<Any> = Stack()
     private val consumers: Stack<Any> = Stack()
 
 
     override val isConsumed: Boolean get() = consumers.isNotEmpty()
     override val isConsumed: Boolean get() = consumers.isNotEmpty()
     override fun NoticeProcessor.markAsConsumed(marker: Any) {
     override fun NoticeProcessor.markAsConsumed(marker: Any) {
+        traceLogging.info { "markAsConsumed: marker=$marker" }
         consumers.push(marker)
         consumers.push(marker)
     }
     }
 
 
     override fun NoticeProcessor.markNotConsumed(marker: Any) {
     override fun NoticeProcessor.markNotConsumed(marker: Any) {
         if (consumers.peek() === marker) {
         if (consumers.peek() === marker) {
             consumers.pop()
             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) {
     override fun collect(packet: Packet) {
         collected.data.add(packet)
         collected.data.add(packet)
+        traceLogging.info { "collect: $packet" }
     }
     }
 
 
     override fun collect(packets: Iterable<Packet>) {
     override fun collect(packets: Iterable<Packet>) {
         this.collected.data.addAll(packets)
         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
     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 {
 internal open class NoticeProcessorPipelineImpl private constructor() : NoticeProcessorPipeline {
     /**
     /**
@@ -175,24 +198,37 @@ internal open class NoticeProcessorPipelineImpl private constructor() : NoticePr
      */
      */
     private val processors = ConcurrentLinkedQueue<NoticeProcessor>()
     private val processors = ConcurrentLinkedQueue<NoticeProcessor>()
 
 
-    override fun registerProcessor(processor: NoticeProcessor) {
+    override fun registerProcessor(processor: NoticeProcessor): NoticeProcessorPipeline.DisposableRegistry {
         processors.add(processor)
         processors.add(processor)
+        return NoticeProcessorPipeline.DisposableRegistry {
+            processors.remove(processor)
+        }
     }
     }
 
 
 
 
     inner class ContextImpl(
     inner class ContextImpl(
         bot: QQAndroidBot, attributes: TypeSafeMap,
         bot: QQAndroidBot, attributes: TypeSafeMap,
-    ) : AbstractPipelineContext(bot, attributes) {
+    ) : AbstractNoticePipelineContext(bot, attributes) {
         override suspend fun processAlso(data: ProtocolStruct, attributes: TypeSafeMap): ProcessResult {
         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 {
     override suspend fun process(bot: QQAndroidBot, data: ProtocolStruct, attributes: TypeSafeMap): ProcessResult {
+        traceLogging.info { "process: data=$data" }
         val context = ContextImpl(bot, attributes)
         val context = ContextImpl(bot, attributes)
+
+        val diff = if (traceLogging.isEnabled) CollectionDiff<Packet>() else null
+        diff?.save(context.collected.data)
+
         for (processor in processors) {
         for (processor in processors) {
-            kotlin.runCatching {
+
+            val result = kotlin.runCatching {
                 processor.process(context, data)
                 processor.process(context, data)
             }.onFailure { e ->
             }.onFailure { e ->
                 context.collect(
                 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
         return context.collected.data
     }
     }
@@ -231,7 +277,7 @@ internal open class NoticeProcessorPipelineImpl private constructor() : NoticePr
  * A processor handling some specific type of message.
  * A processor handling some specific type of message.
  */
  */
 internal interface NoticeProcessor {
 internal interface NoticeProcessor {
-    suspend fun process(context: PipelineContext, data: Any?)
+    suspend fun process(context: NoticePipelineContext, data: Any?)
 }
 }
 
 
 internal abstract class AnyNoticeProcessor : SimpleNoticeProcessor<ProtocolStruct>(type())
 internal abstract class AnyNoticeProcessor : SimpleNoticeProcessor<ProtocolStruct>(type())
@@ -240,13 +286,13 @@ internal abstract class SimpleNoticeProcessor<in T : ProtocolStruct>(
     private val type: KClass<T>,
     private val type: KClass<T>,
 ) : NoticeProcessor {
 ) : NoticeProcessor {
 
 
-    final override suspend fun process(context: PipelineContext, data: Any?) {
+    final override suspend fun process(context: NoticePipelineContext, data: Any?) {
         if (type.isInstance(data)) {
         if (type.isInstance(data)) {
             context.processImpl(data.uncheckedCast())
             context.processImpl(data.uncheckedCast())
         }
         }
     }
     }
 
 
-    protected abstract suspend fun PipelineContext.processImpl(data: T)
+    protected abstract suspend fun NoticePipelineContext.processImpl(data: T)
 
 
     companion object {
     companion object {
         @JvmStatic
         @JvmStatic
@@ -255,11 +301,11 @@ internal abstract class SimpleNoticeProcessor<in T : ProtocolStruct>(
 }
 }
 
 
 internal abstract class MsgCommonMsgProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type()) {
 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() {
 internal abstract class MixedNoticeProcessor : AnyNoticeProcessor() {
-    final override suspend fun PipelineContext.processImpl(data: ProtocolStruct) {
+    final override suspend fun NoticePipelineContext.processImpl(data: ProtocolStruct) {
         when (data) {
         when (data) {
             is PbMsgInfo -> processImpl(data)
             is PbMsgInfo -> processImpl(data)
             is MsgOnlinePush.PbPushMsg -> 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) {}
 }
 }

+ 3 - 3
mirai-core/src/commonMain/kotlin/network/notice/TraceLoggingNoticeProcessor.kt

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

+ 8 - 8
mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt

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

+ 2 - 2
mirai-core/src/commonMain/kotlin/network/notice/decoders/GroupNotificationDecoder.kt

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

+ 11 - 6
mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgInfoDecoder.kt

@@ -14,8 +14,9 @@ import kotlinx.io.core.readBytes
 import kotlinx.io.core.readUInt
 import kotlinx.io.core.readUInt
 import net.mamoe.mirai.internal.contact.GroupImpl
 import net.mamoe.mirai.internal.contact.GroupImpl
 import net.mamoe.mirai.internal.contact.checkIsGroupImpl
 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.SimpleNoticeProcessor
 import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController
 import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController
 import net.mamoe.mirai.internal.network.components.syncOnlinePush
 import net.mamoe.mirai.internal.network.components.syncOnlinePush
@@ -36,7 +37,7 @@ import net.mamoe.mirai.utils.toUHexString
 internal class MsgInfoDecoder(
 internal class MsgInfoDecoder(
     private val logger: MiraiLogger,
     private val logger: MiraiLogger,
 ) : SimpleNoticeProcessor<SvcReqPushMsg>(type()) {
 ) : 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.
         // SvcReqPushMsg is fully handled here, no need to set consumed.
 
 
         for (msgInfo in data.vMsgInfos) {
         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
         if (!bot.syncController.syncOnlinePush(data)) return
-        when (data.shMsgType.toUShort().toInt()) {
+        @Suppress("MoveVariableDeclarationIntoWhen") // for debug
+        val id = data.shMsgType.toUShort().toInt()
+        when (id) {
             // 528
             // 528
             0x210 -> processAlso(data.vMsg.loadAs(MsgType0x210.serializer()), KEY_MSG_INFO to data)
             0x210 -> processAlso(data.vMsg.loadAs(MsgType0x210.serializer()), KEY_MSG_INFO to data)
 
 
             // 732
             // 732
             0x2dc -> {
             0x2dc -> {
                 data.vMsg.read {
                 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()
                     group.checkIsGroupImpl()
 
 
                     val kind = readByte().toInt()
                     val kind = readByte().toInt()

+ 2 - 2
mirai-core/src/commonMain/kotlin/network/notice/group/GroupMessageProcessor.kt

@@ -23,7 +23,7 @@ import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
 import net.mamoe.mirai.internal.contact.newAnonymous
 import net.mamoe.mirai.internal.contact.newAnonymous
 import net.mamoe.mirai.internal.message.toMessageChainOnline
 import net.mamoe.mirai.internal.message.toMessageChainOnline
 import net.mamoe.mirai.internal.network.Packet
 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.SimpleNoticeProcessor
 import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController
 import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController
 import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor.MemberNick.Companion.generateMemberNickFromMember
 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 msgHead = data.msg.msgHead
 
 
         val isFromSelfAccount = msgHead.fromUin == bot.id
         val isFromSelfAccount = msgHead.fromUin == bot.id

+ 16 - 15
mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt

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

+ 12 - 12
mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt

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

+ 4 - 3
mirai-core/src/commonMain/kotlin/network/notice/group/GroupRecallProcessor.kt

@@ -11,15 +11,16 @@ package net.mamoe.mirai.internal.network.notice.group
 
 
 import net.mamoe.mirai.event.events.MessageRecallEvent
 import net.mamoe.mirai.event.events.MessageRecallEvent
 import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
 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.notice.decoders.MsgType0x2DC
 import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857
 import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857
 import net.mamoe.mirai.internal.utils.io.serialization.loadAs
 import net.mamoe.mirai.internal.utils.io.serialization.loadAs
 import net.mamoe.mirai.utils.mapToIntArray
 import net.mamoe.mirai.utils.mapToIntArray
 
 
 internal class GroupRecallProcessor : MixedNoticeProcessor() {
 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)
         val proto = buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), 1)
 
 

+ 23 - 22
mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt

@@ -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.info.StrangerInfoImpl
 import net.mamoe.mirai.internal.contact.toMiraiFriendInfo
 import net.mamoe.mirai.internal.contact.toMiraiFriendInfo
 import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
 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.NewContactSupport
 import net.mamoe.mirai.internal.network.notice.group.get
 import net.mamoe.mirai.internal.network.notice.group.get
 import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
 import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
@@ -50,7 +50,7 @@ import net.mamoe.mirai.utils.*
 internal class FriendNoticeProcessor(
 internal class FriendNoticeProcessor(
     private val logger: MiraiLogger,
     private val logger: MiraiLogger,
 ) : MixedNoticeProcessor(), NewContactSupport {
 ) : 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
         if (msgHead.msgType != 191) return
 
 
         var fromGroup = 0L
         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()
         markAsConsumed()
         when (data.uSubMsgType) {
         when (data.uSubMsgType) {
             0xB3L -> {
             0xB3L -> {
@@ -166,7 +166,7 @@ internal class FriendNoticeProcessor(
         @ProtoNumber(5) val reserved: ByteArray? = null, // struct{ boolean(1), boolean(2) }
         @ProtoNumber(5) val reserved: ByteArray? = null, // struct{ boolean(1), boolean(2) }
     ) : ProtoBuf
     ) : ProtoBuf
 
 
-    private fun PipelineContext.processFriendRecall(body: Sub8A) {
+    private fun NoticePipelineContext.processFriendRecall(body: Sub8A) {
         for (info in body.msgInfo) {
         for (info in body.msgInfo) {
             if (info.botUin != bot.id) continue
             if (info.botUin != bot.id) continue
             collected += MessageRecallEvent.FriendRecall(
             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 friend = bot.getFriend(body.fromUin) ?: return
         val item = body.msgNotifyItem ?: return
         val item = body.msgNotifyItem ?: return
         collect(FriendInputStatusChangedEvent(friend, item.eventType == 1))
         collect(FriendInputStatusChangedEvent(friend, item.eventType == 1))
     }
     }
 
 
-    private fun PipelineContext.handleProfileChanged(body: ModProfile) {
+    private fun NoticePipelineContext.handleProfileChanged(body: ModProfile) {
         var containsUnknown = false
         var containsUnknown = false
         for (profileInfo in body.msgProfileInfos) {
         for (profileInfo in body.msgProfileInfos) {
             when (profileInfo.field) {
             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) {
         for (new in body.msgFrdRmk) {
             val friend = bot.getFriend(new.fuin)?.impl() ?: continue
             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) {
         if (body.uin == bot.id) {
             collect(BotAvatarChangedEvent(bot))
             collect(BotAvatarChangedEvent(bot))
         }
         }
         collect(FriendAvatarChangedEvent(bot.getFriend(body.uin) ?: return))
         collect(FriendAvatarChangedEvent(bot.getFriend(body.uin) ?: return))
     }
     }
 
 
-    private fun PipelineContext.handleFriendDeleted(body: DelFriend) {
+    private fun NoticePipelineContext.handleFriendDeleted(body: DelFriend) {
         for (id in body.uint64Uins) {
         for (id in body.uint64Uins) {
             collect(FriendDeleteEvent(bot.removeFriend(id) ?: continue))
             collect(FriendDeleteEvent(bot.removeFriend(id) ?: continue))
         }
         }
     }
     }
 
 
-    private suspend fun PipelineContext.handleFriendAddedA(
+    private suspend fun NoticePipelineContext.handleFriendAddedA(
         body: Submsgtype0x44.MsgBody,
         body: Submsgtype0x44.MsgBody,
     ) = body.msgFriendMsgSync.context {
     ) = body.msgFriendMsgSync.context {
         if (this == null) return
         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 action = body.msgTemplParam["action_str"].orEmpty()
         val from = body.msgTemplParam["uin_str1"]?.findFriendOrStranger() ?: bot.asFriend
         val from = body.msgTemplParam["uin_str1"]?.findFriendOrStranger() ?: bot.asFriend
         val target = body.msgTemplParam["uin_str2"]?.findFriendOrStranger() ?: bot.asFriend
         val target = body.msgTemplParam["uin_str2"]?.findFriendOrStranger() ?: bot.asFriend

+ 3 - 3
mirai-core/src/commonMain/kotlin/network/notice/priv/OtherClientNoticeProcessor.kt

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

+ 4 - 4
mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt

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

+ 1 - 1
mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt

@@ -414,7 +414,7 @@ internal class ImMsgBody : ProtoBuf {
 
 
     @Serializable
     @Serializable
     internal class ExtraInfo(
     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(2) @JvmField val groupCard: ByteArray = EMPTY_BYTE_ARRAY,
         @ProtoNumber(3) @JvmField val level: Int = 0,
         @ProtoNumber(3) @JvmField val level: Int = 0,
         @ProtoNumber(4) @JvmField val flags: Int = 0,
         @ProtoNumber(4) @JvmField val flags: Int = 0,

+ 1 - 1
mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt

@@ -22,8 +22,8 @@ import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.network.MultiPacket
 import net.mamoe.mirai.internal.network.MultiPacket
 import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.QQAndroidClient
 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.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.SyncController.Companion.syncController
 import net.mamoe.mirai.internal.network.components.syncGetMessage
 import net.mamoe.mirai.internal.network.components.syncGetMessage
 import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
 import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm

+ 1 - 1
mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbC2CMsgSync.kt

@@ -12,8 +12,8 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.ByteReadPacket
 import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.network.Packet
 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.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.data.proto.MsgOnlinePush
 import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
 import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
 import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
 import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf

+ 2 - 2
mirai-core/src/commonTest/kotlin/MockBot.kt

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

+ 5 - 2
mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt

@@ -9,6 +9,7 @@
 
 
 package net.mamoe.mirai.internal.network.framework
 package net.mamoe.mirai.internal.network.framework
 
 
+import net.mamoe.mirai.internal.BotAccount
 import net.mamoe.mirai.internal.MockAccount
 import net.mamoe.mirai.internal.MockAccount
 import net.mamoe.mirai.internal.MockConfiguration
 import net.mamoe.mirai.internal.MockConfiguration
 import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.QQAndroidBot
@@ -43,8 +44,10 @@ internal sealed class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abstr
     abstract val factory: NetworkHandlerFactory<H>
     abstract val factory: NetworkHandlerFactory<H>
     abstract val network: 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 =
             override fun createBotLevelComponents(): ConcurrentComponentStorage =
                 super.createBotLevelComponents().apply { setAll(overrideComponents) }
                 super.createBotLevelComponents().apply { setAll(overrideComponents) }
 
 

+ 182 - 0
mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt

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

+ 16 - 121
mirai-core/src/commonTest/kotlin/notice/RecordingNoticeHandler.kt

@@ -12,144 +12,39 @@ package net.mamoe.mirai.internal.notice
 import kotlinx.atomicfu.atomic
 import kotlinx.atomicfu.atomic
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 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.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.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)
  * 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()) {
 internal class RecordingNoticeProcessor : SimpleNoticeProcessor<ProtocolStruct>(type()) {
     private val id = atomic(0)
     private val id = atomic(0)
     private val lock = Mutex()
     private val lock = Mutex()
 
 
-    override suspend fun PipelineContext.processImpl(data: ProtocolStruct) {
+    override suspend fun NoticePipelineContext.processImpl(data: ProtocolStruct) {
         lock.withLock {
         lock.withLock {
             id.getAndDecrement()
             id.getAndDecrement()
             logger.info { "Recorded #${id.value} ${data::class.simpleName}" }
             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()
-            )
+            logger.info { "Desensitized: \n\n\u001B[0m" + ConstructorCallCodegenFacade.generateAndDesensitize(data) + "\n\n" }
         }
         }
-
-        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) }

+ 180 - 0
mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt

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

+ 60 - 21
mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt

@@ -11,22 +11,25 @@ package net.mamoe.mirai.internal.notice.test
 
 
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.decodeFromString
 import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.protobuf.ProtoNumber
 import net.mamoe.mirai.internal.MockBot
 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.network.components.ProcessResult
 import net.mamoe.mirai.internal.notice.Desensitizer
 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.test.AbstractTest
+import net.mamoe.mirai.internal.utils.codegen.ConstructorCallCodegenFacade
 import net.mamoe.mirai.internal.utils.io.ProtocolStruct
 import net.mamoe.mirai.internal.utils.io.ProtocolStruct
 import net.mamoe.mirai.utils.MutableTypeSafeMap
 import net.mamoe.mirai.utils.MutableTypeSafeMap
 import net.mamoe.mirai.utils.TypeSafeMap
 import net.mamoe.mirai.utils.TypeSafeMap
 import net.mamoe.yamlkt.Yaml
 import net.mamoe.yamlkt.Yaml
+import net.mamoe.yamlkt.YamlBuilder
 import kotlin.test.Test
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertEquals
 
 
 internal class RecordingNoticeProcessorTest : AbstractTest() {
 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 {
         override suspend fun processAlso(data: ProtocolStruct, attributes: TypeSafeMap): ProcessResult {
             throw UnsupportedOperationException()
             throw UnsupportedOperationException()
         }
         }
@@ -42,29 +45,23 @@ internal class RecordingNoticeProcessorTest : AbstractTest() {
         val context = MyContext(MutableTypeSafeMap(mapOf("test" to "value")))
         val context = MyContext(MutableTypeSafeMap(mapOf("test" to "value")))
         val struct = MyProtocolStruct("vvv")
         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(
         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
     @Test
-    fun `test desensitization`() {
+    fun `test plain desensitization`() {
         val text = Thread.currentThread().contextClassLoader.getResource("recording/configs/test.desensitization.yml")!!
         val text = Thread.currentThread().contextClassLoader.getResource("recording/configs/test.desensitization.yml")!!
             .readText()
             .readText()
         val desensitizer = Desensitizer.create(Yaml.decodeFromString(text))
         val desensitizer = Desensitizer.create(Yaml.decodeFromString(text))
@@ -82,6 +79,48 @@ internal class RecordingNoticeProcessorTest : AbstractTest() {
         """.trim()
         """.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))
+            )
+        )
     }
     }
 }
 }

+ 2 - 1
mirai-core/src/commonTest/resources/recording/configs/desensitization.yml

@@ -2,10 +2,11 @@
 #
 #
 # Format:
 # Format:
 # ```
 # ```
-# <sensitive value>: <replacer>
+# <sensitive value>: <replacement>
 # ```
 # ```
 #
 #
 # If key is a number, its group uin counterpart will also be processed, with calculated replacer.
 # 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:
 # For example, if your account id is 147258369, you may add:
 # ```
 # ```

+ 52 - 0
mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt

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

+ 2 - 0
mirai-core/src/jvmTest/resources/account.yml

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