From 1e885dbf7a96ff58bf67e6a40e45b062e0778512 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Sat, 9 May 2020 16:11:29 +0800
Subject: [PATCH] Rearrange MessageSvc and OnlinePush

---
 .../mirai/qqandroid/contact/GroupImpl.kt      |   6 +-
 .../mirai/qqandroid/contact/MemberImpl.kt     |   6 +-
 .../net/mamoe/mirai/qqandroid/contact/util.kt |   6 +-
 .../qqandroid/message/outgoingSourceImpl.kt   |   4 +-
 .../network/QQAndroidBotNetworkHandler.kt     |   6 +-
 .../protocol/data/proto/msgType0x210.kt       |   2 +-
 .../network/protocol/packet/PacketFactory.kt  |  50 +-
 .../chat/receive/MessageSvc.PbDeleteMsg.kt    |  55 ++
 .../chat/receive/MessageSvc.PbGetMsg.kt       | 346 +++++++++
 .../chat/receive/MessageSvc.PbSendMsg.kt      | 237 ++++++
 .../receive/MessageSvc.PushForceOffline.kt    |  29 +
 .../chat/receive/MessageSvc.PushNotify.kt     |  42 ++
 .../packet/chat/receive/MessageSvc.kt         | 600 ---------------
 .../chat/receive/OnlinePush.PbPushGroupMsg.kt | 115 +++
 .../chat/receive/OnlinePush.PbPushTransMsg.kt | 144 ++++
 .../packet/chat/receive/OnlinePush.ReqPush.kt | 518 +++++++++++++
 .../packet/chat/receive/OnlinePush.kt         | 698 ------------------
 .../net.mamoe.mirai/event/events/BotEvents.kt |   2 +-
 18 files changed, 1526 insertions(+), 1340 deletions(-)
 create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbDeleteMsg.kt
 create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
 create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt
 create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushForceOffline.kt
 create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt
 delete mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
 create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt
 create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushTransMsg.kt
 create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt
 delete mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt

diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt
index 3de8f7418..2040aa7f7 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt
@@ -32,7 +32,7 @@ import net.mamoe.mirai.qqandroid.message.firstIsInstanceOrNull
 import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
 import net.mamoe.mirai.qqandroid.network.protocol.packet.list.ProfileService
 import net.mamoe.mirai.qqandroid.utils.estimateLength
 import net.mamoe.mirai.utils.*
@@ -358,7 +358,7 @@ internal class GroupImpl(
 
         lateinit var source: MessageSourceToGroupImpl
         bot.network.run {
-            val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.createToGroup(
+            val response: MessageSvcPbSendMsg.Response = MessageSvcPbSendMsg.createToGroup(
                 bot.client,
                 this@GroupImpl,
                 msg,
@@ -366,7 +366,7 @@ internal class GroupImpl(
             ) {
                 source = it
             }.sendAndExpect()
-            if (response is MessageSvc.PbSendMsg.Response.Failed) {
+            if (response is MessageSvcPbSendMsg.Response.Failed) {
                 when (response.resultType) {
                     120 -> throw BotIsBeingMutedException(this@GroupImpl)
                     34 -> {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt
index 495bb45e1..467818cdf 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt
@@ -32,7 +32,7 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.message.MessageSourceToTempImpl
 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
 import net.mamoe.mirai.utils.*
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
@@ -68,13 +68,13 @@ internal class MemberImpl constructor(
         lateinit var source: MessageSourceToTempImpl
         bot.network.run {
             check(
-                MessageSvc.PbSendMsg.createToTemp(
+                MessageSvcPbSendMsg.createToTemp(
                     bot.client,
                     this@MemberImpl,
                     message.asMessageChain()
                 ) {
                     source = it
-                }.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
+                }.sendAndExpect<MessageSvcPbSendMsg.Response>() is MessageSvcPbSendMsg.Response.SUCCESS
             ) { "send message failed" }
         }
         return MessageReceipt(source, this, null)
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt
index 83564cbca..65afec207 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt
@@ -23,7 +23,7 @@ import net.mamoe.mirai.qqandroid.message.MessageSourceToFriendImpl
 import net.mamoe.mirai.qqandroid.message.ensureSequenceIdAvailable
 import net.mamoe.mirai.qqandroid.message.firstIsInstanceOrNull
 import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
 import net.mamoe.mirai.utils.MiraiExperimentalAPI
 import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.verbose
@@ -38,13 +38,13 @@ internal suspend fun <T : Contact> Friend.sendMessageImpl(generic: T, message: M
     lateinit var source: MessageSourceToFriendImpl
     (bot.network as QQAndroidBotNetworkHandler).run {
         check(
-            MessageSvc.PbSendMsg.createToFriend(
+            MessageSvcPbSendMsg.createToFriend(
                 bot.asQQAndroidBot().client,
                 this@sendMessageImpl,
                 event.message
             ) {
                 source = it
-            }.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
+            }.sendAndExpect<MessageSvcPbSendMsg.Response>() is MessageSvcPbSendMsg.Response.SUCCESS
         ) { "send message failed" }
     }
     return MessageReceipt(source, generic, null)
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt
index b73f0479b..b26951e7b 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt
@@ -25,7 +25,7 @@ import net.mamoe.mirai.message.data.OnlineMessageSource
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePushPbPushGroupMsg.SendGroupMessageReceipt
 import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
 
 
@@ -117,7 +117,7 @@ internal class MessageSourceToGroupImpl(
     override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
 
     private val sequenceIdDeferred: Deferred<Int?> =
-        coroutineScope.asyncFromEventOrNull<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
+        coroutineScope.asyncFromEventOrNull<SendGroupMessageReceipt, Int>(
             timeoutMillis = 3000
         ) {
             if (it.messageRandom == this@MessageSourceToGroupImpl.internalId) {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
index 515fd7869..cbd16b01c 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
@@ -32,7 +32,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopNum
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.*
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvcPbGetMsg
 import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
@@ -362,8 +362,8 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
 
         logger.info { "Syncing friend message history..." }
         withTimeoutOrNull(30000) {
-            launch { syncFromEvent<MessageSvc.PbGetMsg.GetMsgSuccess, Unit> { Unit } }
-            MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()
+            launch { syncFromEvent<MessageSvcPbGetMsg.GetMsgSuccess, Unit> { Unit } }
+            MessageSvcPbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()
         } ?: error("timeout syncing friend message history")
         logger.info { "Syncing friend message history: Success" }
 
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/msgType0x210.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/msgType0x210.kt
index 208cc38f4..a5ecfbcea 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/msgType0x210.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/msgType0x210.kt
@@ -897,7 +897,7 @@ internal class Submsgtype0x27 {
         ) : ProtoBuf
 
         @Serializable
-        internal class MsgBody(
+        internal class SubMsgType0x27MsgBody(
             @ProtoId(1) @JvmField val msgModInfos: List<ForwardBody> = listOf()
         ) : ProtoBuf
 
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
index a1ae4a7b7..54be09f01 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
@@ -19,8 +19,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.*
 import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
 import net.mamoe.mirai.qqandroid.network.protocol.packet.list.ProfileService
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc
@@ -132,10 +131,10 @@ internal object KnownPacketFactories {
         WtLogin.Login,
         StatSvc.Register,
         StatSvc.GetOnlineStatus,
-        MessageSvc.PbGetMsg,
-        MessageSvc.PushForceOffline,
-        MessageSvc.PbSendMsg,
-        MessageSvc.Del,
+        MessageSvcPbGetMsg,
+        MessageSvcPushForceOffline,
+        MessageSvcPbSendMsg,
+        MessageSvcPbDeleteMsg,
         FriendList.GetFriendGroupList,
         FriendList.GetTroopListSimplify,
         FriendList.GetTroopMemberList,
@@ -157,15 +156,15 @@ internal object KnownPacketFactories {
     )
 
     object IncomingFactories : List<IncomingPacketFactory<*>> by mutableListOf(
-        OnlinePush.PbPushGroupMsg,
-        OnlinePush.ReqPush,
-        OnlinePush.PbPushTransMsg,
-        MessageSvc.PushNotify,
+        OnlinePushPbPushGroupMsg,
+        OnlinePushReqPush,
+        OnlinePushPbPushTransMsg,
+        MessageSvcPushNotify,
         ConfigPushSvc.PushReq,
         StatSvc.ReqMSFOffline
     )
     // SvcReqMSFLoginNotify 自己的其他设备上限
-    // MessageSvc.PushReaded 电脑阅读了别人的消息, 告知手机
+    // MessageSvcPushReaded 电脑阅读了别人的消息, 告知手机
     // OnlinePush.PbC2CMsgSync 电脑发消息给别人, 同步给手机
 
     @Suppress("MemberVisibilityCanBePrivate") // debugging use
@@ -259,21 +258,20 @@ internal object KnownPacketFactories {
         PacketLogger.info { "Handle packet: ${it.commandName}" }
         it.data.withUse {
             when (flag2) {
-                0, 1 ->
-                    when (it.packetFactory) {
-                        is OutgoingPacketFactory<*> -> consumer(
-                            it.packetFactory as OutgoingPacketFactory<T>,
-                            it.packetFactory.run { decode(bot, it.data) },
-                            it.packetFactory.commandName,
-                            it.sequenceId
-                        )
-                        is IncomingPacketFactory<*> -> consumer(
-                            it.packetFactory as IncomingPacketFactory<T>,
-                            it.packetFactory.run { decode(bot, it.data, it.sequenceId) },
-                            it.packetFactory.receivingCommandName,
-                            it.sequenceId
-                        )
-                    }
+                0, 1 -> when (it.packetFactory) {
+                    is OutgoingPacketFactory<*> -> consumer(
+                        it.packetFactory as OutgoingPacketFactory<T>,
+                        it.packetFactory.run { decode(bot, it.data) },
+                        it.packetFactory.commandName,
+                        it.sequenceId
+                    )
+                    is IncomingPacketFactory<*> -> consumer(
+                        it.packetFactory as IncomingPacketFactory<T>,
+                        it.packetFactory.run { decode(bot, it.data, it.sequenceId) },
+                        it.packetFactory.receivingCommandName,
+                        it.sequenceId
+                    )
+                }
 
                 2 -> it.data.parseOicqResponse(
                     bot,
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbDeleteMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbDeleteMsg.kt
new file mode 100644
index 000000000..5bd263ec4
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbDeleteMsg.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+import kotlinx.io.core.ByteReadPacket
+import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.network.QQAndroidClient
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
+import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
+import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
+
+internal object MessageSvcPbDeleteMsg : OutgoingPacketFactory<Nothing?>("MessageSvcPbDeleteMsg") {
+
+    internal operator fun invoke(client: QQAndroidClient, items: List<MsgSvc.PbDeleteMsgReq.MsgItem>) =
+        buildOutgoingUniPacket(client) {
+
+            writeProtoBuf(
+                MsgSvc.PbDeleteMsgReq.serializer(),
+                MsgSvc.PbDeleteMsgReq(
+                    msgItems = items
+                )
+            )
+        }
+
+    internal suspend fun delete(bot: QQAndroidBot, messages: Flow<MsgComm.Msg>) =
+        bot.network.run {
+
+            val map = messages.map {
+                MsgSvc.PbDeleteMsgReq.MsgItem(
+                    fromUin = it.msgHead.fromUin,
+                    toUin = it.msgHead.toUin,
+                    // 群为84、好友为187。群通过其他方法删除,但测试结果显示通过187也能删除群消息。
+                    msgType = 187,
+                    msgSeq = it.msgHead.msgSeq,
+                    msgUid = it.msgHead.msgUid
+                )
+            }.toList()
+
+            MessageSvcPbDeleteMsg(bot.client, map).sendWithoutExpect()
+        }
+
+    override suspend fun ByteReadPacket.decode(bot: QQAndroidBot) = null
+}
\ No newline at end of file
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
new file mode 100644
index 000000000..b4ebdebfa
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
+
+import kotlinx.atomicfu.loop
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.*
+import kotlinx.io.core.ByteReadPacket
+import net.mamoe.mirai.LowLevelAPI
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.data.MemberInfo
+import net.mamoe.mirai.event.AbstractEvent
+import net.mamoe.mirai.event.Event
+import net.mamoe.mirai.event.events.BotJoinGroupEvent
+import net.mamoe.mirai.event.events.MemberJoinEvent
+import net.mamoe.mirai.getFriendOrNull
+import net.mamoe.mirai.message.FriendMessageEvent
+import net.mamoe.mirai.message.TempMessageEvent
+import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.contact.GroupImpl
+import net.mamoe.mirai.qqandroid.contact.checkIsFriendImpl
+import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
+import net.mamoe.mirai.qqandroid.message.toMessageChain
+import net.mamoe.mirai.qqandroid.network.MultiPacket
+import net.mamoe.mirai.qqandroid.network.Packet
+import net.mamoe.mirai.qqandroid.network.QQAndroidClient
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
+import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.NewContact
+import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
+import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
+import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
+import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
+import net.mamoe.mirai.utils.*
+
+
+/**
+ * 获取好友消息和消息记录
+ */
+@OptIn(MiraiInternalAPI::class)
+internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Response>("MessageSvcPbGetMsg") {
+    @Suppress("SpellCheckingInspection")
+    operator fun invoke(
+        client: QQAndroidClient,
+        syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START,
+        msgTime: Long //PbPushMsg.msg.msgHead.msgTime
+    ): OutgoingPacket = buildOutgoingUniPacket(
+        client
+    ) {
+        //println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}")
+        writeProtoBuf(
+            MsgSvc.PbGetMsgReq.serializer(),
+            MsgSvc.PbGetMsgReq(
+                msgReqType = 1, // from.ctype.toInt()
+                contextFlag = 1,
+                rambleFlag = 0,
+                latestRambleNumber = 20,
+                otherRambleNumber = 3,
+                onlineSyncFlag = 1,
+                whisperSessionId = 0,
+                syncFlag = syncFlag,
+                //  serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
+                syncCookie = client.c2cMessageSync.syncCookie
+                    ?: SyncCookie(time = msgTime).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it },
+                // syncFlag = client.c2cMessageSync.syncFlag,
+                //msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
+                //pubaccountCookie = client.c2cMessageSync.pubAccountCookie
+            )
+        )
+    }
+
+    @OptIn(MiraiInternalAPI::class)
+    open class GetMsgSuccess(delegate: List<Packet>) : Response(MsgSvc.SyncFlag.STOP, delegate), Event,
+        Packet.NoLog {
+        override fun toString(): String = "MessageSvcPbGetMsg.GetMsgSuccess(messages=<Iterable>))"
+    }
+
+    /**
+     * 不要直接 expect 这个 class. 它可能还没同步完成
+     */
+    @MiraiInternalAPI
+    open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List<Packet>) :
+        AbstractEvent(),
+        MultiPacket<Packet>,
+        Iterable<Packet> by (delegate) {
+
+        override fun toString(): String =
+            "MessageSvcPbGetMsg.Response(syncFlagFromServer=$syncFlagFromServer, messages=<Iterable>))"
+    }
+
+    object EmptyResponse : GetMsgSuccess(emptyList())
+
+    private suspend fun MsgComm.Msg.getNewGroup(bot: QQAndroidBot): Group? {
+        val troopNum = bot.network.run {
+            FriendList.GetTroopListSimplify(bot.client)
+                .sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
+        }.groups.firstOrNull { it.groupUin == msgHead.fromUin } ?: return null
+
+        @Suppress("DuplicatedCode")
+        return GroupImpl(
+            bot = bot,
+            coroutineContext = bot.coroutineContext,
+            id = Group.calculateGroupCodeByGroupUin(msgHead.fromUin),
+            groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply {
+                this as GroupInfoImpl
+
+                if (this.delegate.groupName == null) {
+                    this.delegate.groupName = troopNum.groupName
+                }
+
+                if (this.delegate.groupMemo == null) {
+                    this.delegate.groupMemo = troopNum.groupMemo
+                }
+
+                if (this.delegate.groupUin == null) {
+                    this.delegate.groupUin = troopNum.groupUin
+                }
+
+                this.delegate.groupCode = troopNum.groupCode
+            },
+            members = bot._lowLevelQueryGroupMemberList(
+                troopNum.groupUin,
+                troopNum.groupCode,
+                troopNum.dwGroupOwnerUin
+            )
+        )
+    }
+
+    @OptIn(LowLevelAPI::class)
+    private fun MsgComm.Msg.getNewMemberInfo(): MemberInfo {
+        return object : MemberInfo {
+            override val nameCard: String get() = ""
+            override val permission: MemberPermission get() = MemberPermission.MEMBER
+            override val specialTitle: String get() = ""
+            override val muteTimestamp: Int get() = 0
+            override val uin: Long get() = msgHead.authUin
+            override val nick: String = msgHead.authNick.takeIf { it.isNotEmpty() }
+                ?: msgHead.fromNick
+        }
+    }
+
+    @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class, FlowPreview::class, LowLevelAPI::class)
+    override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
+        // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00
+        val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer())
+
+        if (resp.result != 0) {
+            bot.network.logger
+                .warning { "MessageSvcPushNotify: result != 0, result = ${resp.result}, errorMsg=${resp.errmsg}" }
+            return EmptyResponse
+        }
+
+        bot.client.c2cMessageSync.syncCookie = resp.syncCookie
+        bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie
+        bot.client.c2cMessageSync.msgCtrlBuf = resp.msgCtrlBuf
+
+        if (resp.uinPairMsgs == null) {
+            return EmptyResponse
+        }
+
+        val messages = resp.uinPairMsgs.asFlow()
+            .filterNot { it.msg == null }
+            .flatMapConcat { it.msg!!.asFlow() }
+            .also {
+                MessageSvcPbDeleteMsg.delete(
+                    bot,
+                    it)
+            } // 删除消息
+            .mapNotNull<MsgComm.Msg, Packet> { msg ->
+
+                when (msg.msgHead.msgType) {
+                    33 -> { // 邀请入群
+
+                        val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
+                        if (msg.msgHead.authUin == bot.id) {
+                            if (group != null) {
+                                return@mapNotNull null
+                            }
+                            // 新群
+
+                            val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null
+                            @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+                            bot.groups.delegate.addLast(newGroup)
+                            return@mapNotNull BotJoinGroupEvent(newGroup)
+                        } else {
+                            group ?: return@mapNotNull null
+
+                            if (group.members.contains(msg.msgHead.authUin)) {
+                                return@mapNotNull null
+                            }
+
+                            @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+                            return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo())
+                                .also { group.members.delegate.addLast(it) })
+                        }
+                    }
+                    34 -> { // 主动入群
+
+                        // 27 0B 60 E7 01 44 71 47 90 03 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 36 42 35 35 46 45 32 45 35 36 43 45 45 44 30 38 30 35 31 41 35 42 37 36 39 35 34 45 30 46 43 43 36 36 45 44 43 46 45 43 42 39 33 41 41 44 32 32
+                        val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
+                        group ?: return@mapNotNull null
+
+                        if (group.members.contains(msg.msgHead.authUin)) {
+                            return@mapNotNull null
+                        }
+                        @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+                        return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo())
+                            .also { group.members.delegate.addLast(it) })
+                    }
+                    166 -> {
+
+                        if (msg.msgHead.fromUin == bot.id) {
+                            loop@ while (true) {
+                                val instance = bot.client.getFriendSeq()
+                                if (instance < msg.msgHead.msgSeq) {
+                                    if (bot.client.setFriendSeq(instance, msg.msgHead.msgSeq)) {
+                                        break@loop
+                                    }
+                                } else break@loop
+                            }
+                            return@mapNotNull null
+                        }
+                        val friend = bot.getFriendOrNull(msg.msgHead.fromUin) ?: return@mapNotNull null
+                        friend.checkIsFriendImpl()
+
+                        if (!bot.firstLoginSucceed) {
+                            return@mapNotNull null
+                        }
+
+                        friend.lastMessageSequence.loop { instant ->
+                            if (msg.msgHead.msgSeq > instant) {
+                                if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
+                                    return@mapNotNull FriendMessageEvent(
+                                        friend,
+                                        msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true),
+                                        msg.msgHead.msgTime
+                                    )
+                                }
+                            } else return@mapNotNull null
+                        }
+                    }
+                    208 -> {
+                        // friend ptt
+                        return@mapNotNull null
+                    }
+                    529 -> {
+                        // 好友文件
+                        return@mapNotNull null
+                    }
+                    141 -> {
+                        val tmpHead = msg.msgHead.c2cTmpMsgHead ?: return@mapNotNull null
+                        val member = bot.getGroupByUinOrNull(tmpHead.groupUin)?.getOrNull(msg.msgHead.fromUin)
+                            ?: return@mapNotNull null
+
+                        member.checkIsMemberImpl()
+
+                        if (msg.msgHead.fromUin == bot.id || !bot.firstLoginSucceed) {
+                            return@mapNotNull null
+                        }
+
+                        member.lastMessageSequence.loop { instant ->
+                            if (msg.msgHead.msgSeq > instant) {
+                                if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
+                                    return@mapNotNull TempMessageEvent(
+                                        member,
+                                        msg.toMessageChain(
+                                            bot,
+                                            groupIdOrZero = 0,
+                                            onlineSource = true,
+                                            isTemp = true
+                                        ),
+                                        msg.msgHead.msgTime
+                                    )
+                                }
+                            } else return@mapNotNull null
+                        }
+                    }
+                    84, 87 -> { // 请求入群验证 和 被要求入群
+                        bot.network.run {
+                            NewContact.SystemMsgNewGroup(bot.client).sendWithoutExpect()
+                        }
+                        return@mapNotNull null
+                    }
+                    187 -> { // 请求加好友验证
+                        bot.network.run {
+                            NewContact.SystemMsgNewFriend(bot.client).sendWithoutExpect()
+                        }
+                        return@mapNotNull null
+                    }
+                    // 732:  27 0B 60 E7 0C 01 3E 03 3F A2 5E 90 60 E2 00 01 44 71 47 90 00 00 02 58
+                    else -> {
+                        bot.network.logger.debug { "unknown PbGetMsg type ${msg.msgHead.msgType}" }
+                        return@mapNotNull null
+                    }
+                }
+            }
+
+        val list: List<Packet> = messages.toList()
+        if (resp.syncFlag == MsgSvc.SyncFlag.STOP) {
+            return GetMsgSuccess(
+                list)
+        }
+        return Response(
+            resp.syncFlag,
+            list)
+    }
+
+    override suspend fun QQAndroidBot.handle(packet: Response) {
+        when (packet.syncFlagFromServer) {
+            MsgSvc.SyncFlag.STOP -> return
+            MsgSvc.SyncFlag.START -> {
+                network.run {
+                    MessageSvcPbGetMsg(
+                        client,
+                        MsgSvc.SyncFlag.CONTINUE,
+                        currentTimeSeconds).sendAndExpect<Packet>()
+                }
+                return
+            }
+
+            MsgSvc.SyncFlag.CONTINUE -> {
+                network.run {
+                    MessageSvcPbGetMsg(
+                        client,
+                        MsgSvc.SyncFlag.CONTINUE,
+                        currentTimeSeconds).sendAndExpect<Packet>()
+                }
+                return
+            }
+        }
+    }
+}
+
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt
new file mode 100644
index 000000000..cd1575829
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.toByteArray
+import net.mamoe.mirai.contact.Friend
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.contact.Member
+import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.message.data.PttMessage
+import net.mamoe.mirai.message.data.firstOrNull
+import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.contact.GroupImpl
+import net.mamoe.mirai.qqandroid.message.MessageSourceToFriendImpl
+import net.mamoe.mirai.qqandroid.message.MessageSourceToGroupImpl
+import net.mamoe.mirai.qqandroid.message.MessageSourceToTempImpl
+import net.mamoe.mirai.qqandroid.message.toRichTextElems
+import net.mamoe.mirai.qqandroid.network.Packet
+import net.mamoe.mirai.qqandroid.network.QQAndroidClient
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.*
+import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
+import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
+import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
+import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
+import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
+import net.mamoe.mirai.utils.currentTimeSeconds
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.Response>("MessageSvcPbSendMsg") {
+    sealed class Response : Packet {
+        object SUCCESS : Response() {
+            override fun toString(): String = "MessageSvcPbSendMsg.Response.SUCCESS"
+        }
+
+        /**
+         * 121: 被限制? 个别号才不能发
+         */
+        data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
+            override fun toString(): String =
+                "MessageSvcPbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
+        }
+    }
+
+    inline fun createToFriend(
+        client: QQAndroidClient,
+        qq: Friend,
+        message: MessageChain,
+        crossinline sourceCallback: (MessageSourceToFriendImpl) -> Unit
+    ): OutgoingPacket {
+        val rand = Random.nextInt().absoluteValue
+        val source = MessageSourceToFriendImpl(
+            internalId = rand,
+            sender = client.bot,
+            target = qq,
+            time = currentTimeSeconds.toInt(),
+            sequenceId = client.nextFriendSeq(),
+            originalMessage = message
+        )
+        sourceCallback(source)
+        return createToFriend(
+            client,
+            qq.id,
+            message,
+            source)
+    }
+
+    /**
+     * 发送好友消息
+     */
+    @Suppress("FunctionName")
+    private fun createToFriend(
+        client: QQAndroidClient,
+        toUin: Long,
+        message: MessageChain,
+        source: MessageSourceToFriendImpl
+    ): OutgoingPacket = buildOutgoingUniPacket(client) {
+        ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
+
+        ///return@buildOutgoingUniPacket
+        writeProtoBuf(
+            MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
+                routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = toUin)),
+                contentHead = MsgComm.ContentHead(pkgNum = 1),
+                msgBody = ImMsgBody.MsgBody(
+                    richText = ImMsgBody.RichText(
+                        elems = message.toRichTextElems(forGroup = false, withGeneralFlags = true)
+                    )
+                ),
+                msgSeq = source.sequenceId,
+                msgRand = source.internalId,
+                syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
+                // msgVia = 1
+            )
+        )
+    }
+
+
+    inline fun createToTemp(
+        client: QQAndroidClient,
+        member: Member,
+        message: MessageChain,
+        sourceCallback: (MessageSourceToTempImpl) -> Unit
+    ): OutgoingPacket {
+        val source = MessageSourceToTempImpl(
+            internalId = Random.nextInt().absoluteValue,
+            sender = client.bot,
+            target = member,
+            time = currentTimeSeconds.toInt(),
+            sequenceId = client.atomicNextMessageSequenceId(),
+            originalMessage = message
+        )
+        sourceCallback(source)
+        return createToTemp(
+            client,
+            (member.group as GroupImpl).uin,
+            member.id,
+            message,
+            source)
+    }
+
+    /**
+     * 发送临时消息
+     */
+    private fun createToTemp(
+        client: QQAndroidClient,
+        groupUin: Long,
+        toUin: Long,
+        message: MessageChain,
+        source: MessageSourceToTempImpl
+    ): OutgoingPacket = buildOutgoingUniPacket(client) {
+        writeProtoBuf(
+            MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
+                routingHead = MsgSvc.RoutingHead(
+                    grpTmp = MsgSvc.GrpTmp(groupUin, toUin)
+                ),
+                contentHead = MsgComm.ContentHead(pkgNum = 1),
+                msgBody = ImMsgBody.MsgBody(
+                    richText = ImMsgBody.RichText(
+                        elems = message.toRichTextElems(forGroup = false, withGeneralFlags = true)
+                    )
+                ),
+                msgSeq = source.sequenceId,
+                msgRand = source.internalId,
+                syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
+            )
+        )
+    }
+
+
+    inline fun createToGroup(
+        client: QQAndroidClient,
+        group: Group,
+        message: MessageChain,
+        isForward: Boolean,
+        sourceCallback: (MessageSourceToGroupImpl) -> Unit
+    ): OutgoingPacket {
+
+        val source = MessageSourceToGroupImpl(
+            group,
+            internalId = Random.nextInt().absoluteValue,
+            sender = client.bot,
+            target = group,
+            time = currentTimeSeconds.toInt(),
+            originalMessage = message//,
+            //   sourceMessage = message
+        )
+        sourceCallback(source)
+        return createToGroup(
+            client,
+            group.id,
+            message,
+            isForward,
+            source)
+    }
+
+    /**
+     * 发送群消息
+     */
+    @Suppress("FunctionName")
+    private fun createToGroup(
+        client: QQAndroidClient,
+        groupCode: Long,
+        message: MessageChain,
+        isForward: Boolean,
+        source: MessageSourceToGroupImpl
+    ): OutgoingPacket = buildOutgoingUniPacket(client) {
+        ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
+
+        // DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString())
+
+        ///return@buildOutgoingUniPacket
+        writeProtoBuf(
+            MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
+                routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = groupCode)),
+                contentHead = MsgComm.ContentHead(pkgNum = 1),
+                msgBody = ImMsgBody.MsgBody(
+                    richText = ImMsgBody.RichText(
+                        elems = message.toRichTextElems(forGroup = true, withGeneralFlags = true),
+                        ptt = message.firstOrNull(PttMessage)?.run {
+                            ImMsgBody.Ptt(fileName = fileName.toByteArray(), fileMd5 = md5)
+                        }
+                    )
+                ),
+                msgSeq = client.atomicNextMessageSequenceId(),
+                msgRand = source.internalId,
+                syncCookie = EMPTY_BYTE_ARRAY,
+                msgVia = 1,
+                msgCtrl = if (isForward) MsgCtrl.MsgCtrl(
+                    msgFlag = 4
+                ) else null
+            )
+        )
+    }
+
+    override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
+        val response = readProtoBuf(MsgSvc.PbSendMsgResp.serializer())
+        return if (response.result == 0) {
+            Response.SUCCESS
+        } else {
+            Response.Failed(
+                response.result,
+                response.errtype,
+                response.errmsg)
+        }
+    }
+}
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushForceOffline.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushForceOffline.kt
new file mode 100644
index 000000000..4443622cb
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushForceOffline.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
+
+import kotlinx.io.core.ByteReadPacket
+import net.mamoe.mirai.event.events.BotOfflineEvent
+import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
+import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket
+
+
+/**
+ * 被挤下线
+ */
+internal object MessageSvcPushForceOffline :
+    OutgoingPacketFactory<BotOfflineEvent.Force>("MessageSvcPushForceOffline") {
+    override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): BotOfflineEvent.Force {
+        val struct = this.readUniPacket(RequestPushForceOffline.serializer())
+        return BotOfflineEvent.Force(bot, title = struct.title ?: "", message = struct.tips ?: "")
+    }
+}
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt
new file mode 100644
index 000000000..04a7eff54
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
+import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
+import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket
+import net.mamoe.mirai.utils.currentTimeSeconds
+
+
+/**
+ * 告知要刷新好友消息
+ */
+internal object MessageSvcPushNotify : IncomingPacketFactory<RequestPushNotify>("MessageSvcPushNotify") {
+    override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): RequestPushNotify {
+        discardExact(4) // don't remove
+        return readUniPacket(RequestPushNotify.serializer())
+    }
+
+    override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? {
+
+        network.run {
+            return MessageSvcPbGetMsg(
+                client,
+                MsgSvc.SyncFlag.START,
+                packet.stMsgInfo?.uMsgTime ?: currentTimeSeconds
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
deleted file mode 100644
index 5337fba75..000000000
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
+++ /dev/null
@@ -1,600 +0,0 @@
-/*
- * Copyright 2020 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/master/LICENSE
- */
-
-@file: OptIn(LowLevelAPI::class)
-@file:Suppress("EXPERIMENTAL_API_USAGE")
-
-package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
-
-import kotlinx.atomicfu.loop
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.*
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.discardExact
-import kotlinx.io.core.toByteArray
-import net.mamoe.mirai.LowLevelAPI
-import net.mamoe.mirai.contact.Friend
-import net.mamoe.mirai.contact.Group
-import net.mamoe.mirai.contact.Member
-import net.mamoe.mirai.contact.MemberPermission
-import net.mamoe.mirai.data.MemberInfo
-import net.mamoe.mirai.event.AbstractEvent
-import net.mamoe.mirai.event.Event
-import net.mamoe.mirai.event.events.BotJoinGroupEvent
-import net.mamoe.mirai.event.events.BotOfflineEvent
-import net.mamoe.mirai.event.events.MemberJoinEvent
-import net.mamoe.mirai.getFriendOrNull
-import net.mamoe.mirai.message.FriendMessageEvent
-import net.mamoe.mirai.message.TempMessageEvent
-import net.mamoe.mirai.message.data.MessageChain
-import net.mamoe.mirai.message.data.PttMessage
-import net.mamoe.mirai.message.data.Voice
-import net.mamoe.mirai.message.data.firstOrNull
-import net.mamoe.mirai.qqandroid.QQAndroidBot
-import net.mamoe.mirai.qqandroid.contact.GroupImpl
-import net.mamoe.mirai.qqandroid.contact.checkIsFriendImpl
-import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
-import net.mamoe.mirai.qqandroid.message.*
-import net.mamoe.mirai.qqandroid.network.MultiPacket
-import net.mamoe.mirai.qqandroid.network.Packet
-import net.mamoe.mirai.qqandroid.network.QQAndroidClient
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgCtrl.MsgCtrl
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
-import net.mamoe.mirai.qqandroid.network.protocol.packet.*
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.NewContact
-import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
-import net.mamoe.mirai.qqandroid.utils._miraiContentToString
-import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
-import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket
-import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
-import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
-import net.mamoe.mirai.utils.*
-import kotlin.collections.firstOrNull
-import kotlin.math.absoluteValue
-import kotlin.random.Random
-
-internal class MessageSvc {
-    /**
-     * 告知要刷新好友消息
-     */
-    internal object PushNotify : IncomingPacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): RequestPushNotify {
-            discardExact(4) // don't remove
-            return readUniPacket(RequestPushNotify.serializer())
-        }
-
-        override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? {
-
-            network.run {
-                return PbGetMsg(
-                    client,
-                    MsgSvc.SyncFlag.START,
-                    packet.stMsgInfo?.uMsgTime ?: currentTimeSeconds
-                )
-            }
-        }
-    }
-
-
-    /**
-     * 获取好友消息和消息记录
-     */
-    @OptIn(MiraiInternalAPI::class)
-    internal object PbGetMsg : OutgoingPacketFactory<PbGetMsg.Response>("MessageSvc.PbGetMsg") {
-        @Suppress("SpellCheckingInspection")
-        operator fun invoke(
-            client: QQAndroidClient,
-            syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START,
-            msgTime: Long //PbPushMsg.msg.msgHead.msgTime
-        ): OutgoingPacket = buildOutgoingUniPacket(
-            client
-        ) {
-            //println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}")
-            writeProtoBuf(
-                MsgSvc.PbGetMsgReq.serializer(),
-                MsgSvc.PbGetMsgReq(
-                    msgReqType = 1, // from.ctype.toInt()
-                    contextFlag = 1,
-                    rambleFlag = 0,
-                    latestRambleNumber = 20,
-                    otherRambleNumber = 3,
-                    onlineSyncFlag = 1,
-                    whisperSessionId = 0,
-                    syncFlag = syncFlag,
-                    //  serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
-                    syncCookie = client.c2cMessageSync.syncCookie
-                        ?: SyncCookie(time = msgTime).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it },
-                    // syncFlag = client.c2cMessageSync.syncFlag,
-                    //msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
-                    //pubaccountCookie = client.c2cMessageSync.pubAccountCookie
-                )
-            )
-        }
-
-        @OptIn(MiraiInternalAPI::class)
-        open class GetMsgSuccess(delegate: List<Packet>) : Response(MsgSvc.SyncFlag.STOP, delegate), Event,
-            Packet.NoLog {
-            override fun toString(): String = "MessageSvc.PbGetMsg.GetMsgSuccess(messages=<Iterable>))"
-        }
-
-        /**
-         * 不要直接 expect 这个 class. 它可能还没同步完成
-         */
-        @MiraiInternalAPI
-        open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List<Packet>) :
-            AbstractEvent(),
-            MultiPacket<Packet>,
-            Iterable<Packet> by (delegate) {
-
-            override fun toString(): String =
-                "MessageSvc.PbGetMsg.Response(syncFlagFromServer=$syncFlagFromServer, messages=<Iterable>))"
-        }
-
-        object EmptyResponse : GetMsgSuccess(emptyList())
-
-        private suspend fun MsgComm.Msg.getNewGroup(bot: QQAndroidBot): Group? {
-            val troopNum = bot.network.run {
-                FriendList.GetTroopListSimplify(bot.client)
-                    .sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
-            }.groups.firstOrNull { it.groupUin == msgHead.fromUin } ?: return null
-
-            @Suppress("DuplicatedCode")
-            return GroupImpl(
-                bot = bot,
-                coroutineContext = bot.coroutineContext,
-                id = Group.calculateGroupCodeByGroupUin(msgHead.fromUin),
-                groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply {
-                    this as GroupInfoImpl
-
-                    if (this.delegate.groupName == null) {
-                        this.delegate.groupName = troopNum.groupName
-                    }
-
-                    if (this.delegate.groupMemo == null) {
-                        this.delegate.groupMemo = troopNum.groupMemo
-                    }
-
-                    if (this.delegate.groupUin == null) {
-                        this.delegate.groupUin = troopNum.groupUin
-                    }
-
-                    this.delegate.groupCode = troopNum.groupCode
-                },
-                members = bot._lowLevelQueryGroupMemberList(
-                    troopNum.groupUin,
-                    troopNum.groupCode,
-                    troopNum.dwGroupOwnerUin
-                )
-            )
-        }
-
-        private fun MsgComm.Msg.getNewMemberInfo(): MemberInfo {
-            return object : MemberInfo {
-                override val nameCard: String get() = ""
-                override val permission: MemberPermission get() = MemberPermission.MEMBER
-                override val specialTitle: String get() = ""
-                override val muteTimestamp: Int get() = 0
-                override val uin: Long get() = msgHead.authUin
-                override val nick: String = msgHead.authNick.takeIf { it.isNotEmpty() }
-                    ?: msgHead.fromNick
-            }
-        }
-
-        @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class, FlowPreview::class, LowLevelAPI::class)
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
-            // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00
-            val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer())
-
-            if (resp.result != 0) {
-                bot.network.logger
-                    .warning { "MessageSvc.PushNotify: result != 0, result = ${resp.result}, errorMsg=${resp.errmsg}" }
-                return EmptyResponse
-            }
-
-            bot.client.c2cMessageSync.syncCookie = resp.syncCookie
-            bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie
-            bot.client.c2cMessageSync.msgCtrlBuf = resp.msgCtrlBuf
-
-            if (resp.uinPairMsgs == null) {
-                return EmptyResponse
-            }
-
-            val messages = resp.uinPairMsgs.asFlow()
-                .filterNot { it.msg == null }
-                .flatMapConcat { it.msg!!.asFlow() }
-                .also { Del.delete(bot, it) } // 删除消息
-                .mapNotNull<MsgComm.Msg, Packet> { msg ->
-
-                    when (msg.msgHead.msgType) {
-                        33 -> { // 邀请入群
-
-                            val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
-                            if (msg.msgHead.authUin == bot.id) {
-                                if (group != null) {
-                                    return@mapNotNull null
-                                }
-                                // 新群
-
-                                val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null
-                                @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-                                bot.groups.delegate.addLast(newGroup)
-                                return@mapNotNull BotJoinGroupEvent(newGroup)
-                            } else {
-                                group ?: return@mapNotNull null
-
-                                if (group.members.contains(msg.msgHead.authUin)) {
-                                    return@mapNotNull null
-                                }
-
-                                @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-                                return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo())
-                                    .also { group.members.delegate.addLast(it) })
-                            }
-                        }
-                        34 -> { // 主动入群
-
-                            // 27 0B 60 E7 01 44 71 47 90 03 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 36 42 35 35 46 45 32 45 35 36 43 45 45 44 30 38 30 35 31 41 35 42 37 36 39 35 34 45 30 46 43 43 36 36 45 44 43 46 45 43 42 39 33 41 41 44 32 32
-                            val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
-                            group ?: return@mapNotNull null
-
-                            if (group.members.contains(msg.msgHead.authUin)) {
-                                return@mapNotNull null
-                            }
-                            @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-                            return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo())
-                                .also { group.members.delegate.addLast(it) })
-                        }
-                        166 -> {
-
-                            if (msg.msgHead.fromUin == bot.id) {
-                                loop@ while (true) {
-                                    val instance = bot.client.getFriendSeq()
-                                    if (instance < msg.msgHead.msgSeq) {
-                                        if (bot.client.setFriendSeq(instance, msg.msgHead.msgSeq)) {
-                                            break@loop
-                                        }
-                                    } else break@loop
-                                }
-                                return@mapNotNull null
-                            }
-                            val friend = bot.getFriendOrNull(msg.msgHead.fromUin) ?: return@mapNotNull null
-                            friend.checkIsFriendImpl()
-
-                            if (!bot.firstLoginSucceed) {
-                                return@mapNotNull null
-                            }
-
-                            friend.lastMessageSequence.loop { instant ->
-                                if (msg.msgHead.msgSeq > instant) {
-                                    if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
-                                        return@mapNotNull FriendMessageEvent(
-                                            friend,
-                                            msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true),
-                                            msg.msgHead.msgTime
-                                        )
-                                    }
-                                } else return@mapNotNull null
-                            }
-                        }
-                        208 -> {
-                            // friend ptt
-                            return@mapNotNull null
-                        }
-                        529 -> {
-                            // 好友文件
-                            return@mapNotNull null
-                        }
-                        141 -> {
-                            val tmpHead = msg.msgHead.c2cTmpMsgHead ?: return@mapNotNull null
-                            val member = bot.getGroupByUinOrNull(tmpHead.groupUin)?.getOrNull(msg.msgHead.fromUin)
-                                ?: return@mapNotNull null
-
-                            member.checkIsMemberImpl()
-
-                            if (msg.msgHead.fromUin == bot.id || !bot.firstLoginSucceed) {
-                                return@mapNotNull null
-                            }
-
-                            member.lastMessageSequence.loop { instant ->
-                                if (msg.msgHead.msgSeq > instant) {
-                                    if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
-                                        return@mapNotNull TempMessageEvent(
-                                            member,
-                                            msg.toMessageChain(
-                                                bot,
-                                                groupIdOrZero = 0,
-                                                onlineSource = true,
-                                                isTemp = true
-                                            ),
-                                            msg.msgHead.msgTime
-                                        )
-                                    }
-                                } else return@mapNotNull null
-                            }
-                        }
-                        84, 87 -> { // 请求入群验证 和 被要求入群
-                            bot.network.run {
-                                NewContact.SystemMsgNewGroup(bot.client).sendWithoutExpect()
-                            }
-                            return@mapNotNull null
-                        }
-                        187 -> { // 请求加好友验证
-                            bot.network.run {
-                                NewContact.SystemMsgNewFriend(bot.client).sendWithoutExpect()
-                            }
-                            return@mapNotNull null
-                        }
-                        // 732:  27 0B 60 E7 0C 01 3E 03 3F A2 5E 90 60 E2 00 01 44 71 47 90 00 00 02 58
-                        else -> {
-                            bot.network.logger.debug { "unknown PbGetMsg type ${msg.msgHead.msgType}" }
-                            return@mapNotNull null
-                        }
-                    }
-                }
-
-            val list: List<Packet> = messages.toList()
-            if (resp.syncFlag == MsgSvc.SyncFlag.STOP) {
-                return GetMsgSuccess(list)
-            }
-            return Response(resp.syncFlag, list)
-        }
-
-        override suspend fun QQAndroidBot.handle(packet: Response) {
-            when (packet.syncFlagFromServer) {
-                MsgSvc.SyncFlag.STOP -> return
-                MsgSvc.SyncFlag.START -> {
-                    network.run {
-                        PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendAndExpect<Packet>()
-                    }
-                    return
-                }
-
-                MsgSvc.SyncFlag.CONTINUE -> {
-                    network.run {
-                        PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendAndExpect<Packet>()
-                    }
-                    return
-                }
-            }
-        }
-    }
-
-
-    /**
-     * 被挤下线
-     */
-    internal object PushForceOffline : OutgoingPacketFactory<BotOfflineEvent.Force>("MessageSvc.PushForceOffline") {
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): BotOfflineEvent.Force {
-            val struct = this.readUniPacket(RequestPushForceOffline.serializer())
-            return BotOfflineEvent.Force(bot, title = struct.title ?: "", message = struct.tips ?: "")
-        }
-    }
-
-    internal object PbSendMsg : OutgoingPacketFactory<PbSendMsg.Response>("MessageSvc.PbSendMsg") {
-        sealed class Response : Packet {
-            object SUCCESS : Response() {
-                override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS"
-            }
-
-            /**
-             * 121: 被限制? 个别号才不能发
-             */
-            data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
-                override fun toString(): String =
-                    "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
-            }
-        }
-
-        inline fun createToFriend(
-            client: QQAndroidClient,
-            qq: Friend,
-            message: MessageChain,
-            crossinline sourceCallback: (MessageSourceToFriendImpl) -> Unit
-        ): OutgoingPacket {
-            val rand = Random.nextInt().absoluteValue
-            val source = MessageSourceToFriendImpl(
-                internalId = rand,
-                sender = client.bot,
-                target = qq,
-                time = currentTimeSeconds.toInt(),
-                sequenceId = client.nextFriendSeq(),
-                originalMessage = message
-            )
-            sourceCallback(source)
-            return createToFriend(client, qq.id, message, source)
-        }
-
-        /**
-         * 发送好友消息
-         */
-        @Suppress("FunctionName")
-        private fun createToFriend(
-            client: QQAndroidClient,
-            toUin: Long,
-            message: MessageChain,
-            source: MessageSourceToFriendImpl
-        ): OutgoingPacket = buildOutgoingUniPacket(client) {
-            ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
-
-            ///return@buildOutgoingUniPacket
-            writeProtoBuf(
-                MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
-                    routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = toUin)),
-                    contentHead = MsgComm.ContentHead(pkgNum = 1),
-                    msgBody = ImMsgBody.MsgBody(
-                        richText = ImMsgBody.RichText(
-                            elems = message.toRichTextElems(forGroup = false, withGeneralFlags = true)
-                        )
-                    ),
-                    msgSeq = source.sequenceId,
-                    msgRand = source.internalId,
-                    syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
-                    // msgVia = 1
-                )
-            )
-        }
-
-
-        inline fun createToTemp(
-            client: QQAndroidClient,
-            member: Member,
-            message: MessageChain,
-            sourceCallback: (MessageSourceToTempImpl) -> Unit
-        ): OutgoingPacket {
-            val source = MessageSourceToTempImpl(
-                internalId = Random.nextInt().absoluteValue,
-                sender = client.bot,
-                target = member,
-                time = currentTimeSeconds.toInt(),
-                sequenceId = client.atomicNextMessageSequenceId(),
-                originalMessage = message
-            )
-            sourceCallback(source)
-            return createToTemp(client, (member.group as GroupImpl).uin, member.id, message, source)
-        }
-
-        /**
-         * 发送临时消息
-         */
-        private fun createToTemp(
-            client: QQAndroidClient,
-            groupUin: Long,
-            toUin: Long,
-            message: MessageChain,
-            source: MessageSourceToTempImpl
-        ): OutgoingPacket = buildOutgoingUniPacket(client) {
-            writeProtoBuf(
-                MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
-                    routingHead = MsgSvc.RoutingHead(
-                        grpTmp = MsgSvc.GrpTmp(groupUin, toUin)
-                    ),
-                    contentHead = MsgComm.ContentHead(pkgNum = 1),
-                    msgBody = ImMsgBody.MsgBody(
-                        richText = ImMsgBody.RichText(
-                            elems = message.toRichTextElems(forGroup = false, withGeneralFlags = true)
-                        )
-                    ),
-                    msgSeq = source.sequenceId,
-                    msgRand = source.internalId,
-                    syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
-                )
-            )
-        }
-
-
-        inline fun createToGroup(
-            client: QQAndroidClient,
-            group: Group,
-            message: MessageChain,
-            isForward: Boolean,
-            sourceCallback: (MessageSourceToGroupImpl) -> Unit
-        ): OutgoingPacket {
-
-            val source = MessageSourceToGroupImpl(
-                group,
-                internalId = Random.nextInt().absoluteValue,
-                sender = client.bot,
-                target = group,
-                time = currentTimeSeconds.toInt(),
-                originalMessage = message//,
-                //   sourceMessage = message
-            )
-            sourceCallback(source)
-            return createToGroup(client, group.id, message, isForward, source)
-        }
-
-        /**
-         * 发送群消息
-         */
-        @Suppress("FunctionName")
-        private fun createToGroup(
-            client: QQAndroidClient,
-            groupCode: Long,
-            message: MessageChain,
-            isForward: Boolean,
-            source: MessageSourceToGroupImpl
-        ): OutgoingPacket = buildOutgoingUniPacket(client) {
-            ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
-
-            // DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString())
-
-            ///return@buildOutgoingUniPacket
-            writeProtoBuf(
-                MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
-                    routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = groupCode)),
-                    contentHead = MsgComm.ContentHead(pkgNum = 1),
-                    msgBody = ImMsgBody.MsgBody(
-                        richText = ImMsgBody.RichText(
-                            elems = message.toRichTextElems(forGroup = true, withGeneralFlags = true),
-                            ptt = message.firstOrNull(PttMessage)?.run {
-                                ImMsgBody.Ptt(fileName = fileName.toByteArray(), fileMd5 = md5)
-                            }
-                        )
-                    ),
-                    msgSeq = client.atomicNextMessageSequenceId(),
-                    msgRand = source.internalId,
-                    syncCookie = EMPTY_BYTE_ARRAY,
-                    msgVia = 1,
-                    msgCtrl = if (isForward) MsgCtrl(
-                        msgFlag = 4
-                    ) else null
-                )
-            )
-        }
-
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
-            val response = readProtoBuf(MsgSvc.PbSendMsgResp.serializer())
-            return if (response.result == 0) {
-                Response.SUCCESS
-            } else {
-                Response.Failed(response.result, response.errtype, response.errmsg)
-            }
-        }
-    }
-
-    internal object Del : OutgoingPacketFactory<Nothing?>("MessageSvc.PbDeleteMsg") {
-
-        internal operator fun invoke(client: QQAndroidClient, items: List<MsgSvc.PbDeleteMsgReq.MsgItem>) =
-            buildOutgoingUniPacket(client) {
-
-                writeProtoBuf(
-                    MsgSvc.PbDeleteMsgReq.serializer(),
-                    MsgSvc.PbDeleteMsgReq(
-                        msgItems = items
-                    )
-                )
-            }
-
-        internal suspend fun delete(bot: QQAndroidBot, messages: Flow<MsgComm.Msg>) =
-            bot.network.run {
-
-                val map = messages.map {
-                    MsgSvc.PbDeleteMsgReq.MsgItem(
-                        fromUin = it.msgHead.fromUin,
-                        toUin = it.msgHead.toUin,
-                        // 群为84、好友为187。群通过其他方法删除,但测试结果显示通过187也能删除群消息。
-                        msgType = 187,
-                        msgSeq = it.msgHead.msgSeq,
-                        msgUid = it.msgHead.msgUid
-                    )
-                }.toList()
-
-                Del(bot.client, map).sendWithoutExpect()
-            }
-
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot) = null
-    }
-}
-
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt
new file mode 100644
index 000000000..1ee0f65d0
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
+
+import kotlinx.io.core.ByteReadPacket
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.contact.nameCardOrNick
+import net.mamoe.mirai.event.AbstractEvent
+import net.mamoe.mirai.event.Event
+import net.mamoe.mirai.event.broadcast
+import net.mamoe.mirai.event.events.MemberCardChangeEvent
+import net.mamoe.mirai.getGroupOrNull
+import net.mamoe.mirai.message.GroupMessageEvent
+import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.contact.GroupImpl
+import net.mamoe.mirai.qqandroid.contact.MemberImpl
+import net.mamoe.mirai.qqandroid.message.toMessageChain
+import net.mamoe.mirai.qqandroid.network.Packet
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgOnlinePush
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Oidb0x8fc
+import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
+import net.mamoe.mirai.qqandroid.utils._miraiContentToString
+import net.mamoe.mirai.qqandroid.utils.encodeToString
+import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs
+import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
+
+/**
+ * 接受群消息
+ */
+internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("OnlinePush.PbPushGroupMsg") {
+    internal class SendGroupMessageReceipt(
+        val messageRandom: Int,
+        val sequenceId: Int
+    ) : Packet, Event, Packet.NoLog, AbstractEvent() {
+        override fun toString(): String {
+            return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)"
+        }
+    }
+
+    @OptIn(ExperimentalStdlibApi::class)
+    override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
+        // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
+        if (!bot.firstLoginSucceed) return null
+        val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
+
+        if (pbPushMsg.msg.msgHead.fromUin == bot.id) {
+            return SendGroupMessageReceipt(
+                pbPushMsg.msg.msgBody.richText.attr!!.random,
+                pbPushMsg.msg.msgHead.msgSeq
+            )
+        }
+
+        var extraInfo: ImMsgBody.ExtraInfo? = null
+        var anonymous: ImMsgBody.AnonymousGroupMsg? = null
+
+        for (elem in pbPushMsg.msg.msgBody.richText.elems) {
+            when {
+                elem.extraInfo != null -> extraInfo = elem.extraInfo
+                elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg
+            }
+        }
+
+        val group =
+            bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
+        val sender = if (anonymous != null) {
+            group.newAnonymous(anonymous.anonNick.encodeToString())
+        } else {
+            group[pbPushMsg.msg.msgHead.fromUin]
+        } as MemberImpl
+
+        val name = if (anonymous != null) {
+            sender.nameCard
+        } else {
+            extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.run {
+                kotlin.runCatching {
+                    if (this[0] == 0x0A.toByte())
+                        loadAs(Oidb0x8fc.CommCardNameBuf.serializer()).richCardName?.joinToString("") { it.text.encodeToString() }
+                    else return@runCatching null
+                }.getOrNull() ?: encodeToString()
+            } ?: pbPushMsg.msg.msgHead.groupInfo.groupCard.takeIf { it.isNotEmpty() }
+            ?: sender.nameCardOrNick // 没有 extraInfo 就从 head 里取
+        }
+
+        val flags = extraInfo?.flags ?: 0
+        return GroupMessageEvent(
+            senderName = name.also {
+                if (it != sender.nameCard) {
+                    val origin = sender._nameCard
+                    sender._nameCard = name
+                    MemberCardChangeEvent(origin, name, sender).broadcast()
+                }
+            },
+            sender = sender,
+            message = pbPushMsg.msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true),
+            permission = when {
+                flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
+                flags and 8 != 0 -> MemberPermission.OWNER
+                flags == 0 -> MemberPermission.MEMBER
+                else -> {
+                    bot.logger.warning("判断群 ${sender.group.id} 的群员 ${sender.id} 的权限失败: ${pbPushMsg.msg.msgHead._miraiContentToString()}. 请完整截图或复制此日志并确认其真实权限后发送给 mirai 维护者以帮助解决问题.")
+                    sender.permission
+                }
+            },
+            time = pbPushMsg.msg.msgHead.msgTime
+        )
+    }
+}
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushTransMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushTransMsg.kt
new file mode 100644
index 000000000..a1a20bc4f
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushTransMsg.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+@file:OptIn(MiraiInternalAPI::class,
+    MiraiExperimentalAPI::class,
+    JavaFriendlyAPI::class,
+    ExperimentalUnsignedTypes::class)
+
+package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import kotlinx.io.core.readUByte
+import kotlinx.io.core.readUInt
+import net.mamoe.mirai.JavaFriendlyAPI
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.event.events.BotGroupPermissionChangeEvent
+import net.mamoe.mirai.event.events.MemberLeaveEvent
+import net.mamoe.mirai.event.events.MemberPermissionChangeEvent
+import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.contact.GroupImpl
+import net.mamoe.mirai.qqandroid.contact.MemberImpl
+import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
+import net.mamoe.mirai.qqandroid.network.Packet
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.OnlinePushTrans
+import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
+import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
+import net.mamoe.mirai.qqandroid.utils.read
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
+import net.mamoe.mirai.utils.MiraiInternalAPI
+
+
+internal object OnlinePushPbPushTransMsg :
+    IncomingPacketFactory<Packet?>("OnlinePush.PbPushTransMsg", "OnlinePush.RespPush") {
+
+    @OptIn(MiraiInternalAPI::class)
+    override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
+        val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer())
+
+
+        if (!bot.client.pbPushTransMsgCacheList.ensureNoDuplication(content.msgSeq)) {
+            return null
+        }
+
+        content.msgData.read<Unit> {
+            when (content.msgType) {
+                44 -> {
+                    this.discardExact(5)
+                    val var4 = readByte().toInt()
+                    var var5 = 0L
+                    val target = readUInt().toLong()
+                    if (var4 != 0 && var4 != 1) {
+                        var5 = readUInt().toLong()
+                    }
+
+                    val group = bot.getGroupByUin(content.fromUin) as GroupImpl
+
+                    if (var5 == 0L && this.remaining == 1L) {//管理员变更
+                        val newPermission =
+                            if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR
+                            else MemberPermission.MEMBER
+
+                        if (target == bot.id) {
+                            if (group.botPermission == newPermission) {
+                                return null
+                            }
+
+                            return BotGroupPermissionChangeEvent(
+                                group,
+                                group.botPermission.also {
+                                    group.botAsMember.checkIsMemberImpl().permission = newPermission
+                                },
+                                newPermission
+                            )
+                        } else {
+                            val member = group[target] as MemberImpl
+                            if (member.permission == newPermission) {
+                                return null
+                            }
+
+                            return MemberPermissionChangeEvent(
+                                member,
+                                member.permission.also { member.permission = newPermission },
+                                newPermission
+                            )
+                        }
+                    }
+                }
+                34 -> {
+                    /* quit
+                    27 0B 60 E7
+                    01
+                    2F 55 7C B8
+                    82
+                    00 30 42 33 32 46 30 38 33 32 39 32 35 30 31 39 33 45 46 32 45 30 36 35 41 35 41 33 42 37 35 43 41 34 46 37 42 38 42 38 42 44 43 35 35 34 35 44 38 30
+                     */
+                    /* kick
+                    27 0B 60 E7
+                    01
+                    A8 32 51 A1
+                    83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 39 32 46 45 30 36 31 41 33 37 36 43 44 35 37 35 37 39 45 37 32 34 44 37 37 30 36 46 39 39 43 35 35 33 33 31 34 44 32 44 46 35 45 42 43 31 31 36
+                     */
+                    readUInt().toLong() // group, uin or code ?
+
+                    discardExact(1)
+                    val target = readUInt().toLong()
+                    val type = readUByte().toInt()
+                    val operator = readUInt().toLong()
+                    val groupUin = content.fromUin
+
+                    when (type) {
+                        0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group ->
+                            val member = group.getOrNull(target) as? MemberImpl ?: return null
+                            return MemberLeaveEvent.Quit(member.also {
+                                group.members.delegate.remove(member)
+                            })
+                        }
+                        0x83 -> bot.getGroupByUin(groupUin).let { group ->
+                            val member = group.getOrNull(target) as? MemberImpl ?: return null
+                            return MemberLeaveEvent.Kick(member.also {
+                                group.members.delegate.remove(member)
+                            }, group.members[operator])
+                        }
+                    }
+                }
+            }
+        }
+        return null
+    }
+
+    override suspend fun QQAndroidBot.handle(packet: Packet?, sequenceId: Int): OutgoingPacket? {
+        return buildResponseUniPacket(client, sequenceId = sequenceId) {}
+    }
+
+}
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt
new file mode 100644
index 000000000..26db88d6a
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+@file:OptIn(MiraiInternalAPI::class,
+    MiraiExperimentalAPI::class,
+    JavaFriendlyAPI::class,
+    ExperimentalUnsignedTypes::class)
+
+package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import kotlinx.io.core.readBytes
+import kotlinx.io.core.readUInt
+import kotlinx.serialization.Serializable
+import net.mamoe.mirai.*
+import net.mamoe.mirai.data.FriendInfo
+import net.mamoe.mirai.event.events.*
+import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.contact.GroupImpl
+import net.mamoe.mirai.qqandroid.contact.checkIsGroupImpl
+import net.mamoe.mirai.qqandroid.contact.checkIsInstance
+import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
+import net.mamoe.mirai.qqandroid.message.contextualBugReportException
+import net.mamoe.mirai.qqandroid.network.MultiPacketBySequence
+import net.mamoe.mirai.qqandroid.network.Packet
+import net.mamoe.mirai.qqandroid.network.QQAndroidClient
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgInfo
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgType0x210
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.OnlinePushPack
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.*
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x44
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0xb3
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.TroopTips0x857
+import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePushReqPush.ignoredLambda528
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePushReqPush.lambda528
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePushReqPush.lambda732
+import net.mamoe.mirai.qqandroid.utils._miraiContentToString
+import net.mamoe.mirai.qqandroid.utils.encodeToString
+import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf
+import net.mamoe.mirai.qqandroid.utils.io.readString
+import net.mamoe.mirai.qqandroid.utils.io.serialization.*
+import net.mamoe.mirai.qqandroid.utils.read
+import net.mamoe.mirai.qqandroid.utils.toUHexString
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
+import net.mamoe.mirai.utils.MiraiInternalAPI
+import net.mamoe.mirai.utils.currentTimeSeconds
+import net.mamoe.mirai.utils.debug
+
+
+//0C 01 B1 89 BE 09 5E 3D 72 A6 00 01 73 68 FC 06 00 00 00 3C
+internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.Response>(
+    "OnlinePush.ReqPush",
+    "OnlinePush.RespPush"
+) {
+    // to reduce nesting depth
+    private fun List<MsgInfo>.deco(
+        client: QQAndroidClient,
+        mapper: ByteReadPacket.(msgInfo: MsgInfo) -> Sequence<Packet>
+    ): Sequence<Packet> {
+        return asSequence().filter { msg ->
+            client.onlinePushCacheList.ensureNoDuplication(msg.shMsgSeq)
+        }.flatMap { it.vMsg.read { mapper(it) } }
+    }
+
+    @Suppress("unused") // bug
+    private fun lambda732(block: ByteReadPacket.(group: GroupImpl, bot: QQAndroidBot) -> Sequence<Packet>):
+            ByteReadPacket.(group: GroupImpl, bot: QQAndroidBot) -> Sequence<Packet> {
+        return block
+    }
+
+    private fun lambda528(block: MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet>):
+            MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet> {
+        return block
+    }
+
+    val ignoredLambda528: MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet> = lambda528 { emptySequence() }
+
+    @ExperimentalUnsignedTypes
+    @OptIn(ExperimentalStdlibApi::class)
+    override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Response {
+        val reqPushMsg = readUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req")
+
+        val packets: Sequence<Packet> = reqPushMsg.vMsgInfos.deco(bot.client) { msgInfo ->
+            when (msgInfo.shMsgType.toInt()) {
+                732 -> {
+                    val group = bot.getGroup(readUInt().toLong())
+                    GroupImpl.checkIsInstance(group)
+
+                    val internalType = readByte().toInt()
+                    discardExact(1)
+
+                    Transformers732[internalType]
+                        ?.let { it(this@deco, group, bot) }
+                        ?: kotlin.run {
+                            bot.network.logger.debug {
+                                "unknown group 732 type $internalType, data: " + readBytes().toUHexString()
+                            }
+                            return@deco emptySequence()
+                        }
+                }
+
+                // 00 27 1A 0C 1C 2C 3C 4C 5D 00 0C 6D 00 0C 7D 00 0C 8D 00 0C 9C AC BC CC DD 00 0C EC FC 0F 0B 2A 0C 1C 2C 3C 4C 5C 6C 0B 3A 0C 1C 2C 3C 4C 5C 6C 7C 8D 00 0C 9D 00 0C AC BD 00 0C CD 00 0C DC ED 00 0C FC 0F FC 10 0B 4A 0C 1C 2C 3C 4C 5C 6C 7C 8C 96 00 0B 5A 0C 1C 2C 3C 4C 5C 6C 7C 8C 9D 00 0C 0B 6A 0C 1A 0C 1C 26 00 0B 2A 0C 0B 3A 0C 16 00 0B 4A 09 0C 0B 5A 09 0C 0B 0B 7A 0C 1C 2C 36 00 0B 8A 0C 1C 2C 36 00 0B 9A 09 0C 0B AD 00 00 1E 0A 1C 10 28 4A 18 0A 16 08 00 10 A2 FF 8C F0 03 1A 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B
+                528 -> {
+                    val notifyMsgBody = readJceStruct(MsgType0x210.serializer())
+                    Transformers528[notifyMsgBody.uSubMsgType]
+                        ?.let { processor -> processor(notifyMsgBody, bot) }
+                        ?: kotlin.run {
+                            bot.network.logger.debug {
+                                // Network(1994701021) 16:03:54 : unknown group 528 type 0x0000000000000026, data: 08 01 12 40 0A 06 08 F4 EF BB 8F 04 10 E7 C1 AD B8 02 18 01 22 2C 10 01 1A 1A 18 B4 DC F8 9B 0C 20 E7 C1 AD B8 02 28 06 30 02 A2 01 04 08 93 D6 03 A8 01 08 20 00 28 00 32 08 18 01 20 FE AF AF F5 05 28 00
+                                // VIP 进群提示
+                                "unknown group 528 type 0x${notifyMsgBody.uSubMsgType.toUHexString("")}, data: " + notifyMsgBody.vProtobuf.toUHexString()
+                            }
+                            return@deco emptySequence()
+                        }
+                }
+                else -> {
+                    bot.network.logger.debug { "unknown sh type ${msgInfo.shMsgType.toInt()}" }
+                    bot.network.logger.debug { "data=${readBytes().toUHexString()}" }
+                    return@deco emptySequence()
+                }
+            }
+        }
+        return Response(reqPushMsg, packets)
+    }
+
+    @Suppress("SpellCheckingInspection")
+    internal data class Response(val request: OnlinePushPack.SvcReqPushMsg, val sequence: Sequence<Packet>) :
+        MultiPacketBySequence<Packet>(sequence) {
+        override fun toString(): String {
+            return "OnlinePush.ReqPush.Response"
+        }
+    }
+
+    override suspend fun QQAndroidBot.handle(packet: Response, sequenceId: Int): OutgoingPacket? {
+        return buildResponseUniPacket(client) {
+            writeJceStruct(
+                RequestPacket.serializer(),
+                RequestPacket(
+                    sServantName = "OnlinePush",
+                    sFuncName = "SvcRespPushMsg",
+                    iRequestId = sequenceId,
+                    sBuffer = jceRequestSBuffer(
+                        "resp",
+                        OnlinePushPack.SvcRespPushMsg.serializer(),
+                        OnlinePushPack.SvcRespPushMsg(
+                            packet.request.uin,
+                            packet.request.vMsgInfos.map { msg ->
+                                OnlinePushPack.DelMsgInfo(
+                                    fromUin = msg.lFromUin,
+                                    shMsgSeq = msg.shMsgSeq,
+                                    vMsgCookies = msg.vMsgCookies,
+                                    uMsgTime = msg.uMsgTime // captured 0
+                                )
+                            }
+                        )
+                    )
+                )
+            )
+        }
+    }
+}
+
+private object Transformers732 : Map<Int, ByteReadPacket.(GroupImpl, QQAndroidBot) -> Sequence<Packet>> by mapOf(
+    // mute
+    0x0c to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
+        val operatorUin = readUInt().toLong()
+        if (operatorUin == bot.id) {
+            return@lambda732 emptySequence()
+        }
+        val operator = group.getOrNull(operatorUin) ?: return@lambda732 emptySequence()
+        readUInt().toLong() // time
+        this.discardExact(2)
+        val target = readUInt().toLong()
+        val timeSeconds = readInt()
+
+        if (target == 0L) {
+            val new = timeSeconds != 0
+            if (group.settings.isMuteAll == new) {
+                return@lambda732 emptySequence()
+            }
+            group._muteAll = new
+            return@lambda732 sequenceOf(GroupMuteAllEvent(!new, new, group, operator))
+        }
+
+        if (target == bot.id) {
+            return@lambda732 when {
+                group.botMuteRemaining == timeSeconds -> emptySequence()
+                timeSeconds == 0 || timeSeconds == 0xFFFF_FFFF.toInt() -> {
+                    group.botAsMember.checkIsMemberImpl()._muteTimestamp = 0
+                    sequenceOf(BotUnmuteEvent(operator))
+                }
+                else -> {
+                    group.botAsMember.checkIsMemberImpl()._muteTimestamp =
+                        currentTimeSeconds.toInt() + timeSeconds
+                    sequenceOf(BotMuteEvent(timeSeconds, operator))
+                }
+            }
+        }
+
+        val member = group.getOrNull(target) ?: return@lambda732 emptySequence()
+        member.checkIsMemberImpl()
+
+        if (member._muteTimestamp == timeSeconds) {
+            return@lambda732 emptySequence()
+        }
+
+        member._muteTimestamp = timeSeconds
+        return@lambda732 if (timeSeconds == 0) sequenceOf(MemberUnmuteEvent(member, operator))
+        else sequenceOf(MemberMuteEvent(member, timeSeconds, operator))
+    },
+
+    // anonymous
+    0x0e to lambda732 { group: GroupImpl, _: QQAndroidBot ->
+        // 匿名
+        val operator = group.getOrNull(readUInt().toLong()) ?: return@lambda732 emptySequence()
+        val new = readInt() == 0
+        if (group.settings.isAnonymousChatEnabled == new) {
+            return@lambda732 emptySequence()
+        }
+
+        group._anonymousChat = new
+        return@lambda732 sequenceOf(GroupAllowAnonymousChatEvent(!new, new, group, operator))
+    },
+
+    // 传字符串信息
+    0x10 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
+        val dataBytes = readBytes(26)
+
+        when (dataBytes[0].toInt()) {
+            59 -> { // TODO 应该在 Transformers528 处理
+                val size = readByte().toInt() // orthodox, don't `readUByte`
+                if (size < 0) {
+                    // java.lang.IllegalStateException: negative array size: -100, remaining bytes=B0 E6 99 90 D8 E8 02 98 06 01
+                    // java.lang.IllegalStateException: negative array size: -121, remaining bytes=03 10 D9 F7 A2 93 0D 18 E0 DB E8 CA 0B 32 22 61 34 64 31 34 64 61 64 65 65 38 32 32 34 62 64 32 35 34 65 63 37 62 62 30 33 30 66 61 36 66 61 6D 6A 38 0E 48 00 58 01 70 C8 E8 9B 07 7A AD 02 3C 7B 22 69 63 6F 6E 22 3A 22 71 71 77 61 6C 6C 65 74 5F 63 75 73 74 6F 6D 5F 74 69 70 73 5F 69 64 69 6F 6D 5F 69 63 6F 6E 2E 70 6E 67 22 2C 22 61 6C 74 22 3A 22 22 7D 3E 3C 7B 22 63 6D 64 22 3A 31 2C 22 64 61 74 61 22 3A 22 6C 69 73 74 69 64 3D 31 30 30 30 30 34 35 32 30 31 32 30 30 34 30 38 31 32 30 30 31 30 39 36 31 32 33 31 34 35 30 30 26 67 72 6F 75 70 74 79 70 65 3D 31 22 2C 22 74 65 78 74 43 6F 6C 6F 72 22 3A 22 30 78 38 37 38 42 39 39 22 2C 22 74 65 78 74 22 3A 22 E6 8E A5 E9 BE 99 E7 BA A2 E5 8C 85 E4 B8 8B E4 B8 80 E4 B8 AA E6 8B BC E9 9F B3 EF BC 9A 22 7D 3E 3C 7B 22 63 6D 64 22 3A 31 2C 22 64 61 74 61 22 3A 22 6C 69 73 74 69 64 3D 31 30 30 30 30 34 35 32 30 31 32 30 30 34 30 38 31 32 30 30 31 30 39 36 31 32 33 31 34 35 30 30 26 67 72 6F 75 70 74 79 70 65 3D 31 22 2C 22 74 65 78 74 43 6F 6C 6F 72 22 3A 22 30 78 45 36 32 35 35 35 22 2C 22 74 65 78 74 22 3A 22 64 69 6E 67 22 7D 3E 82 01 0C E8 80 81 E5 83 A7 E5 85 A5 E5 AE 9A 88 01 03 92 01 04 64 69 6E 67 A0 01 00
+                    // negative array size: -40, remaining bytes=D6 94 C3 8C D8 E8 02 98 06 01
+                    error("negative array size: $size, remaining bytes=${readBytes().toUHexString()}")
+                }
+
+                // println(dataBytes.toUHexString())
+                //println(message + ":" + dataBytes.toUHexString())
+
+                val new = when (val message = readString(size)) {
+                    "管理员已关闭群聊坦白说" -> false
+                    "管理员已开启群聊坦白说" -> true
+                    else -> {
+                        bot.network.logger.debug { "Unknown server messages $message" }
+                        return@lambda732 emptySequence()
+                    }
+                }
+
+                if (group.settings.isConfessTalkEnabled == new) {
+                    return@lambda732 emptySequence()
+                }
+
+                return@lambda732 sequenceOf(
+                    GroupAllowConfessTalkEvent(
+                        new,
+                        false,
+                        group,
+                        false
+                    )
+                )
+            }
+
+            0x2D -> {
+                // 修改群名. 在 Transformers528 0x27L 处理
+                return@lambda732 emptySequence()
+            }
+            else -> {
+                /*
+                bot.network.logger.debug("unknown Transformer732 0xunknown type: ${dataBytes[0].toString(16)
+                    .toUpperCase()}")
+                bot.network.logger.debug("unknown Transformer732 0xdata= ${readBytes().toUHexString()}")
+                */
+                return@lambda732 emptySequence()
+
+                /*
+                if (group.name == message) {
+                    return@lambda732 emptySequence()
+                }
+
+                return@lambda732 sequenceOf(
+                    GroupNameChangeEvent(
+                        group.name.also { group._name = message },
+                        message, group, false
+                    )
+                )*/
+            }
+        }
+    },
+
+    // recall
+    0x11 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
+        discardExact(1)
+        val proto = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer())
+
+        val recallReminder = proto.optMsgRecall ?: return@lambda732 emptySequence()
+
+        val operator =
+            if (recallReminder.uin == bot.id) group.botAsMember
+            else group.getOrNull(recallReminder.uin) ?: return@lambda732 emptySequence()
+
+        return@lambda732 recallReminder.recalledMsgList.asSequence().mapNotNull { pkg ->
+            when {
+                pkg.authorUin == bot.id && operator.id == bot.id -> null
+                else -> {
+                    MessageRecallEvent.GroupRecall(
+                        bot,
+                        pkg.authorUin,
+                        pkg.seq,
+                        pkg.msgRandom,
+                        pkg.time,
+                        operator,
+                        group
+                    )
+                }
+            }
+        }
+    }
+)
+
+// uSubMsgType to vProtobuf
+// 138 or 139: top_package/akln.java:1568
+// 66: top_package/nhz.java:269
+/**
+ * @see MsgType0x210
+ */
+@OptIn(LowLevelAPI::class, MiraiInternalAPI::class)
+private object Transformers528 : Map<Long, MsgType0x210.(QQAndroidBot) -> Sequence<Packet>> by mapOf(
+    // 提示共同好友
+    0x111L to ignoredLambda528,
+    // 新好友
+    0xB3L to lambda528 { bot ->
+        // 08 01 12 52 08 A2 FF 8C F0 03 10 00 1D 15 3D 90 5E 22 2E E6 88 91 E4 BB AC E5 B7 B2 E7 BB 8F E6 98 AF E5 A5 BD E5 8F 8B E5 95 A6 EF BC 8C E4 B8 80 E8 B5 B7 E6 9D A5 E8 81 8A E5 A4 A9 E5 90 A7 21 2A 09 48 69 6D 31 38 38 6D 6F 65 30 07 38 03 48 DD F1 92 B7 07
+        val body = vProtobuf.loadAs(Submsgtype0xb3.SubMsgType0xb3.MsgBody.serializer())
+        val new = bot._lowLevelNewFriend(object : FriendInfo {
+            override val uin: Long get() = body.msgAddFrdNotify.fuin
+            override val nick: String get() = body.msgAddFrdNotify.fuinNick
+        })
+        bot.friends.delegate.addLast(new)
+        return@lambda528 sequenceOf(FriendAddEvent(new))
+    },
+    0xE2L to lambda528 {
+        // TODO: unknown. maybe messages.
+        // 0A 35 08 00 10 A2 FF 8C F0 03 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B 28 01
+        // vProtobuf.loadAs(Msgtype0x210.serializer())
+
+        return@lambda528 emptySequence()
+    },
+    0x44L to lambda528 { bot ->
+        val msg = vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer())
+        when {
+            msg.msgCleanCountMsg != null -> {
+
+            }
+            msg.msgFriendMsgSync != null -> {
+
+            }
+            else -> {
+                bot.network.logger.debug { "OnlinePush528 0x44L: " + msg._miraiContentToString() }
+            }
+        }
+        return@lambda528 emptySequence()
+    },
+    // bot 在其他客户端被踢或主动退出而同步情况
+    0xD4L to lambda528 { bot ->
+        @Serializable
+        data class SubD4(
+            // ok
+            val uin: Long
+        ) : ProtoBuf
+
+        val uin = vProtobuf.loadAs(SubD4.serializer()).uin
+        val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin)
+        return@lambda528 if (group != null && bot.groups.delegate.remove(group)) {
+            sequenceOf(BotLeaveEvent.Active(group))
+        } else emptySequence()
+    },
+    // 群相关,  ModFriendRemark, DelFriend, ModGroupProfile
+    0x27L to lambda528 { bot ->
+        fun ModFriendRemark.transform(bot: QQAndroidBot): Sequence<Packet> {
+            return this.msgFrdRmk?.asSequence()?.mapNotNull {
+                val friend = bot.getFriendOrNull(it.fuin) ?: return@mapNotNull null
+                // TODO: 2020/4/10 ADD REMARK QUERY
+                FriendRemarkChangeEvent(bot, friend, it.rmkName)
+            } ?: emptySequence()
+        }
+
+        fun DelFriend.transform(bot: QQAndroidBot): Sequence<Packet> {
+            return this.uint64Uins?.asSequence()?.mapNotNull {
+                val friend = bot.getFriendOrNull(it) ?: return@mapNotNull null
+                if (bot.friends.delegate.remove(friend)) {
+                    FriendDeleteEvent(friend)
+                } else null
+            } ?: emptySequence()
+        }
+
+        fun ModGroupProfile.transform(bot: QQAndroidBot): Sequence<Packet> {
+            return this.msgGroupProfileInfos?.asSequence()?.mapNotNull { info ->
+                when (info.field) {
+                    1 -> {
+                        // 群名
+                        val new = info.value.encodeToString()
+
+                        val group = bot.getGroupOrNull(this.groupCode) ?: return@mapNotNull null
+                        group.checkIsGroupImpl()
+                        val old = group.name
+
+                        if (new == old) return@mapNotNull null
+
+                        val operator = if (this.cmdUin == bot.id) null
+                        else group.getOrNull(this.cmdUin) ?: return@mapNotNull null
+
+                        group._name = new
+
+                        return@mapNotNull GroupNameChangeEvent(old, new, group, operator)
+                    }
+                    2 -> {
+                        // 头像
+                        // top_package/akkz.java:3446
+                        /*
+                        var4 = var82.byteAt(0);
+                           short var3 = (short) (var82.byteAt(1) | var4 << 8);
+                           var85 = var18.method_77927(var7 + "");
+                           var85.troopface = var3;
+                           var85.hasSetNewTroopHead = true;
+                         */
+                        bot.logger.debug(contextualBugReportException(
+                            "解析 Transformers528 0x27L ModGroupProfile 群头像修改",
+                            forDebug = "this=${this._miraiContentToString()}"
+                        ))
+                        null
+                    }
+                    3 -> { // troop.credit.data
+                        // top_package/akkz.java:3475
+                        // top_package/akkz.java:3498
+                        bot.logger.debug(contextualBugReportException(
+                            "解析 Transformers528 0x27L ModGroupProfile 群 troop.credit.data",
+                            forDebug = "this=${this._miraiContentToString()}"
+                        ))
+                        null
+                    }
+
+                    else -> null
+                }
+            } ?: emptySequence()
+        }
+
+        fun ModGroupMemberProfile.transform(bot: QQAndroidBot): Sequence<Packet> {
+            return this.msgGroupMemberProfileInfos?.asSequence()?.mapNotNull { info ->
+                when (info.field) {
+                    1 -> { // name card
+                        val new = info.value
+                        val group = bot.getGroupOrNull(this.groupCode) ?: return@mapNotNull null
+                        group.checkIsGroupImpl()
+                        val member = group.getOrNull(this.uin) ?: return@mapNotNull null
+                        member.checkIsMemberImpl()
+
+                        val old = member.nameCard
+
+                        if (new == old) return@mapNotNull null
+                        member._nameCard = new
+
+                        return@mapNotNull MemberCardChangeEvent(old, new, member)
+                    }
+                    2 -> {
+                        if (info.value.singleOrNull()?.toInt() != 0) {
+                            bot.logger.debug {
+                                "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}"
+                            }
+                        }
+                        return@mapNotNull null
+                    }
+                    else -> {
+                        bot.logger.debug {
+                            "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}"
+                        }
+                        return@mapNotNull null
+                    }
+                }
+            } ?: emptySequence()
+        }
+
+        fun ModCustomFace.transform(): Sequence<Packet> =
+            sequenceOf(BotFaceChangedEvent(Bot.getInstance(uin)))
+
+
+        return@lambda528 vProtobuf.loadAs(SubMsgType0x27MsgBody.serializer()).msgModInfos.asSequence()
+            .flatMap {
+                when {
+                    it.msgModFriendRemark != null -> it.msgModFriendRemark.transform(bot)
+                    it.msgDelFriend != null -> it.msgDelFriend.transform(bot)
+                    it.msgModGroupProfile != null -> it.msgModGroupProfile.transform(bot)
+                    it.msgModGroupMemberProfile != null -> it.msgModGroupMemberProfile.transform(bot)
+                    it.msgModCustomFace != null -> it.msgModCustomFace.transform()
+                    else -> {
+                        bot.network.logger.debug {
+                            "Transformers528 0x27L: new data: ${it._miraiContentToString()}"
+                        }
+                        emptySequence()
+                    }
+                }
+            }
+        // 0A 1C 10 28 4A 18 0A 16 08 00 10 A2 FF 8C F0 03 1A 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B
+    }
+)
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt
deleted file mode 100644
index e4caae7c5..000000000
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt
+++ /dev/null
@@ -1,698 +0,0 @@
-/*
- * Copyright 2020 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/master/LICENSE
- */
-
-@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-
-package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
-
-import kotlinx.io.core.*
-import kotlinx.serialization.Serializable
-import net.mamoe.mirai.Bot
-import net.mamoe.mirai.LowLevelAPI
-import net.mamoe.mirai.contact.MemberPermission
-import net.mamoe.mirai.contact.nameCardOrNick
-import net.mamoe.mirai.data.FriendInfo
-import net.mamoe.mirai.event.AbstractEvent
-import net.mamoe.mirai.event.Event
-import net.mamoe.mirai.event.broadcast
-import net.mamoe.mirai.event.events.*
-import net.mamoe.mirai.getFriendOrNull
-import net.mamoe.mirai.getGroupOrNull
-import net.mamoe.mirai.message.GroupMessageEvent
-import net.mamoe.mirai.qqandroid.QQAndroidBot
-import net.mamoe.mirai.qqandroid.contact.*
-import net.mamoe.mirai.qqandroid.message.contextualBugReportException
-import net.mamoe.mirai.qqandroid.message.toMessageChain
-import net.mamoe.mirai.qqandroid.network.MultiPacketBySequence
-import net.mamoe.mirai.qqandroid.network.Packet
-import net.mamoe.mirai.qqandroid.network.QQAndroidClient
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgInfo
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgType0x210
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.OnlinePushPack
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.*
-import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
-import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
-import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
-import net.mamoe.mirai.qqandroid.utils._miraiContentToString
-import net.mamoe.mirai.qqandroid.utils.encodeToString
-import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf
-import net.mamoe.mirai.qqandroid.utils.io.readString
-import net.mamoe.mirai.qqandroid.utils.io.serialization.*
-import net.mamoe.mirai.qqandroid.utils.read
-import net.mamoe.mirai.qqandroid.utils.toUHexString
-import net.mamoe.mirai.utils.MiraiInternalAPI
-import net.mamoe.mirai.utils.currentTimeSeconds
-import net.mamoe.mirai.utils.debug
-
-internal class OnlinePush {
-    /**
-     * 接受群消息
-     */
-    internal object PbPushGroupMsg : IncomingPacketFactory<Packet?>("OnlinePush.PbPushGroupMsg") {
-        internal class SendGroupMessageReceipt(
-            val messageRandom: Int,
-            val sequenceId: Int
-        ) : Packet, Event, Packet.NoLog, AbstractEvent() {
-            override fun toString(): String {
-                return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)"
-            }
-        }
-
-        @OptIn(ExperimentalStdlibApi::class)
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
-            // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
-            if (!bot.firstLoginSucceed) return null
-            val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
-
-            if (pbPushMsg.msg.msgHead.fromUin == bot.id) {
-                return SendGroupMessageReceipt(
-                    pbPushMsg.msg.msgBody.richText.attr!!.random,
-                    pbPushMsg.msg.msgHead.msgSeq
-                )
-            }
-
-            var extraInfo: ImMsgBody.ExtraInfo? = null
-            var anonymous: ImMsgBody.AnonymousGroupMsg? = null
-
-            for (elem in pbPushMsg.msg.msgBody.richText.elems) {
-                when {
-                    elem.extraInfo != null -> extraInfo = elem.extraInfo
-                    elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg
-                }
-            }
-
-            val group =
-                bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
-            val sender = if (anonymous != null) {
-                group.newAnonymous(anonymous.anonNick.encodeToString())
-            } else {
-                group[pbPushMsg.msg.msgHead.fromUin]
-            } as MemberImpl
-
-            val name = if (anonymous != null) {
-                sender.nameCard
-            } else {
-                extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.run {
-                    kotlin.runCatching {
-                        if (this[0] == 0x0A.toByte())
-                            loadAs(Oidb0x8fc.CommCardNameBuf.serializer()).richCardName?.joinToString("") { it.text.encodeToString() }
-                        else return@runCatching null
-                    }.getOrNull() ?: encodeToString()
-                } ?: pbPushMsg.msg.msgHead.groupInfo.groupCard.takeIf { it.isNotEmpty() }
-                ?: sender.nameCardOrNick // 没有 extraInfo 就从 head 里取
-            }
-
-            val flags = extraInfo?.flags ?: 0
-            return GroupMessageEvent(
-                senderName = name.also {
-                    if (it != sender.nameCard) {
-                        val origin = sender._nameCard
-                        sender._nameCard = name
-                        MemberCardChangeEvent(origin, name, sender).broadcast()
-                    }
-                },
-                sender = sender,
-                message = pbPushMsg.msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true),
-                permission = when {
-                    flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
-                    flags and 8 != 0 -> MemberPermission.OWNER
-                    flags == 0 -> MemberPermission.MEMBER
-                    else -> {
-                        bot.logger.warning("判断群 ${sender.group.id} 的群员 ${sender.id} 的权限失败: ${pbPushMsg.msg.msgHead._miraiContentToString()}. 请完整截图或复制此日志并确认其真实权限后发送给 mirai 维护者以帮助解决问题.")
-                        sender.permission
-                    }
-                },
-                time = pbPushMsg.msg.msgHead.msgTime
-            )
-        }
-    }
-
-    internal object PbPushTransMsg :
-        IncomingPacketFactory<Packet?>("OnlinePush.PbPushTransMsg", "OnlinePush.RespPush") {
-
-        @OptIn(MiraiInternalAPI::class)
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
-            val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer())
-
-
-            if (!bot.client.pbPushTransMsgCacheList.ensureNoDuplication(content.msgSeq)) {
-                return null
-            }
-
-            content.msgData.read<Unit> {
-                when (content.msgType) {
-                    44 -> {
-                        this.discardExact(5)
-                        val var4 = readByte().toInt()
-                        var var5 = 0L
-                        val target = readUInt().toLong()
-                        if (var4 != 0 && var4 != 1) {
-                            var5 = readUInt().toLong()
-                        }
-
-                        val group = bot.getGroupByUin(content.fromUin) as GroupImpl
-
-                        if (var5 == 0L && this.remaining == 1L) {//管理员变更
-                            val newPermission =
-                                if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR
-                                else MemberPermission.MEMBER
-
-                            if (target == bot.id) {
-                                if (group.botPermission == newPermission) {
-                                    return null
-                                }
-
-                                return BotGroupPermissionChangeEvent(
-                                    group,
-                                    group.botPermission.also {
-                                        group.botAsMember.checkIsMemberImpl().permission = newPermission
-                                    },
-                                    newPermission
-                                )
-                            } else {
-                                val member = group[target] as MemberImpl
-                                if (member.permission == newPermission) {
-                                    return null
-                                }
-
-                                return MemberPermissionChangeEvent(
-                                    member,
-                                    member.permission.also { member.permission = newPermission },
-                                    newPermission
-                                )
-                            }
-                        }
-                    }
-                    34 -> {
-                        /* quit
-                        27 0B 60 E7
-                        01
-                        2F 55 7C B8
-                        82
-                        00 30 42 33 32 46 30 38 33 32 39 32 35 30 31 39 33 45 46 32 45 30 36 35 41 35 41 33 42 37 35 43 41 34 46 37 42 38 42 38 42 44 43 35 35 34 35 44 38 30
-                         */
-                        /* kick
-                        27 0B 60 E7
-                        01
-                        A8 32 51 A1
-                        83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 39 32 46 45 30 36 31 41 33 37 36 43 44 35 37 35 37 39 45 37 32 34 44 37 37 30 36 46 39 39 43 35 35 33 33 31 34 44 32 44 46 35 45 42 43 31 31 36
-                         */
-                        readUInt().toLong() // group, uin or code ?
-
-                        discardExact(1)
-                        val target = readUInt().toLong()
-                        val type = readUByte().toInt()
-                        val operator = readUInt().toLong()
-                        val groupUin = content.fromUin
-
-                        when (type) {
-                            0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group ->
-                                val member = group.getOrNull(target) as? MemberImpl ?: return null
-                                return MemberLeaveEvent.Quit(member.also {
-                                    group.members.delegate.remove(member)
-                                })
-                            }
-                            0x83 -> bot.getGroupByUin(groupUin).let { group ->
-                                val member = group.getOrNull(target) as? MemberImpl ?: return null
-                                return MemberLeaveEvent.Kick(member.also {
-                                    group.members.delegate.remove(member)
-                                }, group.members[operator])
-                            }
-                        }
-                    }
-                }
-            }
-            return null
-        }
-
-        override suspend fun QQAndroidBot.handle(packet: Packet?, sequenceId: Int): OutgoingPacket? {
-            return buildResponseUniPacket(client, sequenceId = sequenceId) {}
-        }
-
-    }
-
-    //0C 01 B1 89 BE 09 5E 3D 72 A6 00 01 73 68 FC 06 00 00 00 3C
-    internal object ReqPush : IncomingPacketFactory<ReqPush.Response>(
-        "OnlinePush.ReqPush",
-        "OnlinePush.RespPush"
-    ) {
-        // to reduce nesting depth
-        private fun List<MsgInfo>.deco(
-            client: QQAndroidClient,
-            mapper: ByteReadPacket.(msgInfo: MsgInfo) -> Sequence<Packet>
-        ): Sequence<Packet> {
-            return asSequence().filter { msg ->
-                client.onlinePushCacheList.ensureNoDuplication(msg.shMsgSeq)
-            }.flatMap { it.vMsg.read { mapper(it) } }
-        }
-
-        private fun lambda732(block: ByteReadPacket.(group: GroupImpl, bot: QQAndroidBot) -> Sequence<Packet>):
-                ByteReadPacket.(group: GroupImpl, bot: QQAndroidBot) -> Sequence<Packet> {
-            return block
-        }
-
-        object Transformers732 : Map<Int, ByteReadPacket.(GroupImpl, QQAndroidBot) -> Sequence<Packet>> by mapOf(
-            // mute
-            0x0c to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
-                val operatorUin = readUInt().toLong()
-                if (operatorUin == bot.id) {
-                    return@lambda732 emptySequence()
-                }
-                val operator = group.getOrNull(operatorUin) ?: return@lambda732 emptySequence()
-                readUInt().toLong() // time
-                this.discardExact(2)
-                val target = readUInt().toLong()
-                val timeSeconds = readInt()
-
-                if (target == 0L) {
-                    val new = timeSeconds != 0
-                    if (group.settings.isMuteAll == new) {
-                        return@lambda732 emptySequence()
-                    }
-                    group._muteAll = new
-                    return@lambda732 sequenceOf(GroupMuteAllEvent(!new, new, group, operator))
-                }
-
-                if (target == bot.id) {
-                    return@lambda732 when {
-                        group.botMuteRemaining == timeSeconds -> emptySequence()
-                        timeSeconds == 0 || timeSeconds == 0xFFFF_FFFF.toInt() -> {
-                            group.botAsMember.checkIsMemberImpl()._muteTimestamp = 0
-                            sequenceOf(BotUnmuteEvent(operator))
-                        }
-                        else -> {
-                            group.botAsMember.checkIsMemberImpl()._muteTimestamp =
-                                currentTimeSeconds.toInt() + timeSeconds
-                            sequenceOf(BotMuteEvent(timeSeconds, operator))
-                        }
-                    }
-                }
-
-                val member = group.getOrNull(target) ?: return@lambda732 emptySequence()
-                member.checkIsMemberImpl()
-
-                if (member._muteTimestamp == timeSeconds) {
-                    return@lambda732 emptySequence()
-                }
-
-                member._muteTimestamp = timeSeconds
-                return@lambda732 if (timeSeconds == 0) sequenceOf(MemberUnmuteEvent(member, operator))
-                else sequenceOf(MemberMuteEvent(member, timeSeconds, operator))
-            },
-
-            // anonymous
-            0x0e to lambda732 { group: GroupImpl, _: QQAndroidBot ->
-                // 匿名
-                val operator = group.getOrNull(readUInt().toLong()) ?: return@lambda732 emptySequence()
-                val new = readInt() == 0
-                if (group.settings.isAnonymousChatEnabled == new) {
-                    return@lambda732 emptySequence()
-                }
-
-                group._anonymousChat = new
-                return@lambda732 sequenceOf(GroupAllowAnonymousChatEvent(!new, new, group, operator))
-            },
-
-            // 传字符串信息
-            0x10 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
-                val dataBytes = readBytes(26)
-
-                when (dataBytes[0].toInt()) {
-                    59 -> { // TODO 应该在 Transformers528 处理
-                        val size = readByte().toInt() // orthodox, don't `readUByte`
-                        if (size < 0) {
-                            // java.lang.IllegalStateException: negative array size: -100, remaining bytes=B0 E6 99 90 D8 E8 02 98 06 01
-                            // java.lang.IllegalStateException: negative array size: -121, remaining bytes=03 10 D9 F7 A2 93 0D 18 E0 DB E8 CA 0B 32 22 61 34 64 31 34 64 61 64 65 65 38 32 32 34 62 64 32 35 34 65 63 37 62 62 30 33 30 66 61 36 66 61 6D 6A 38 0E 48 00 58 01 70 C8 E8 9B 07 7A AD 02 3C 7B 22 69 63 6F 6E 22 3A 22 71 71 77 61 6C 6C 65 74 5F 63 75 73 74 6F 6D 5F 74 69 70 73 5F 69 64 69 6F 6D 5F 69 63 6F 6E 2E 70 6E 67 22 2C 22 61 6C 74 22 3A 22 22 7D 3E 3C 7B 22 63 6D 64 22 3A 31 2C 22 64 61 74 61 22 3A 22 6C 69 73 74 69 64 3D 31 30 30 30 30 34 35 32 30 31 32 30 30 34 30 38 31 32 30 30 31 30 39 36 31 32 33 31 34 35 30 30 26 67 72 6F 75 70 74 79 70 65 3D 31 22 2C 22 74 65 78 74 43 6F 6C 6F 72 22 3A 22 30 78 38 37 38 42 39 39 22 2C 22 74 65 78 74 22 3A 22 E6 8E A5 E9 BE 99 E7 BA A2 E5 8C 85 E4 B8 8B E4 B8 80 E4 B8 AA E6 8B BC E9 9F B3 EF BC 9A 22 7D 3E 3C 7B 22 63 6D 64 22 3A 31 2C 22 64 61 74 61 22 3A 22 6C 69 73 74 69 64 3D 31 30 30 30 30 34 35 32 30 31 32 30 30 34 30 38 31 32 30 30 31 30 39 36 31 32 33 31 34 35 30 30 26 67 72 6F 75 70 74 79 70 65 3D 31 22 2C 22 74 65 78 74 43 6F 6C 6F 72 22 3A 22 30 78 45 36 32 35 35 35 22 2C 22 74 65 78 74 22 3A 22 64 69 6E 67 22 7D 3E 82 01 0C E8 80 81 E5 83 A7 E5 85 A5 E5 AE 9A 88 01 03 92 01 04 64 69 6E 67 A0 01 00
-                            // negative array size: -40, remaining bytes=D6 94 C3 8C D8 E8 02 98 06 01
-                            error("negative array size: $size, remaining bytes=${readBytes().toUHexString()}")
-                        }
-
-                        // println(dataBytes.toUHexString())
-                        //println(message + ":" + dataBytes.toUHexString())
-
-                        val new = when (val message = readString(size)) {
-                            "管理员已关闭群聊坦白说" -> false
-                            "管理员已开启群聊坦白说" -> true
-                            else -> {
-                                bot.network.logger.debug { "Unknown server messages $message" }
-                                return@lambda732 emptySequence()
-                            }
-                        }
-
-                        if (group.settings.isConfessTalkEnabled == new) {
-                            return@lambda732 emptySequence()
-                        }
-
-                        return@lambda732 sequenceOf(
-                            GroupAllowConfessTalkEvent(
-                                new,
-                                false,
-                                group,
-                                false
-                            )
-                        )
-                    }
-
-                    0x2D -> {
-                        // 修改群名. 在 Transformers528 0x27L 处理
-                        return@lambda732 emptySequence()
-                    }
-                    else -> {
-                        /*
-                        bot.network.logger.debug("unknown Transformer732 0xunknown type: ${dataBytes[0].toString(16)
-                            .toUpperCase()}")
-                        bot.network.logger.debug("unknown Transformer732 0xdata= ${readBytes().toUHexString()}")
-                        */
-                        return@lambda732 emptySequence()
-
-                        /*
-                        if (group.name == message) {
-                            return@lambda732 emptySequence()
-                        }
-
-                        return@lambda732 sequenceOf(
-                            GroupNameChangeEvent(
-                                group.name.also { group._name = message },
-                                message, group, false
-                            )
-                        )*/
-                    }
-                }
-            },
-
-            // recall
-            0x11 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
-                discardExact(1)
-                val proto = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer())
-
-                val recallReminder = proto.optMsgRecall ?: return@lambda732 emptySequence()
-
-                val operator =
-                    if (recallReminder.uin == bot.id) group.botAsMember
-                    else group.getOrNull(recallReminder.uin) ?: return@lambda732 emptySequence()
-
-                return@lambda732 recallReminder.recalledMsgList.asSequence().mapNotNull { pkg ->
-                    when {
-                        pkg.authorUin == bot.id && operator.id == bot.id -> null
-                        else -> {
-                            MessageRecallEvent.GroupRecall(
-                                bot,
-                                pkg.authorUin,
-                                pkg.seq,
-                                pkg.msgRandom,
-                                pkg.time,
-                                operator,
-                                group
-                            )
-                        }
-                    }
-                }
-            }
-        )
-
-        private fun lambda528(block: MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet>):
-                MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet> {
-            return block
-        }
-
-        val ignoredLambda528: MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet> = lambda528 { emptySequence() }
-
-        // uSubMsgType to vProtobuf
-        // 138 or 139: top_package/akln.java:1568
-        // 66: top_package/nhz.java:269
-        /**
-         * @see MsgType0x210
-         */
-        @OptIn(LowLevelAPI::class, MiraiInternalAPI::class)
-        object Transformers528 : Map<Long, MsgType0x210.(QQAndroidBot) -> Sequence<Packet>> by mapOf(
-            // 提示共同好友
-            0x111L to ignoredLambda528,
-            // 新好友
-            0xB3L to lambda528 { bot ->
-                // 08 01 12 52 08 A2 FF 8C F0 03 10 00 1D 15 3D 90 5E 22 2E E6 88 91 E4 BB AC E5 B7 B2 E7 BB 8F E6 98 AF E5 A5 BD E5 8F 8B E5 95 A6 EF BC 8C E4 B8 80 E8 B5 B7 E6 9D A5 E8 81 8A E5 A4 A9 E5 90 A7 21 2A 09 48 69 6D 31 38 38 6D 6F 65 30 07 38 03 48 DD F1 92 B7 07
-                val body = vProtobuf.loadAs(Submsgtype0xb3.SubMsgType0xb3.MsgBody.serializer())
-                val new = bot._lowLevelNewFriend(object : FriendInfo {
-                    override val uin: Long get() = body.msgAddFrdNotify.fuin
-                    override val nick: String get() = body.msgAddFrdNotify.fuinNick
-                })
-                bot.friends.delegate.addLast(new)
-                return@lambda528 sequenceOf(FriendAddEvent(new))
-            },
-            0xE2L to lambda528 {
-                // TODO: unknown. maybe messages.
-                // 0A 35 08 00 10 A2 FF 8C F0 03 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B 28 01
-                // vProtobuf.loadAs(Msgtype0x210.serializer())
-
-                return@lambda528 emptySequence()
-            },
-            0x44L to lambda528 { bot ->
-                val msg = vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer())
-                when {
-                    msg.msgCleanCountMsg != null -> {
-
-                    }
-                    msg.msgFriendMsgSync != null -> {
-
-                    }
-                    else -> {
-                        bot.network.logger.debug { "OnlinePush528 0x44L: " + msg._miraiContentToString() }
-                    }
-                }
-                return@lambda528 emptySequence()
-            },
-            // bot 在其他客户端被踢或主动退出而同步情况
-            0xD4L to lambda528 { bot ->
-                @Serializable
-                data class SubD4(
-                    // ok
-                    val uin: Long
-                ) : ProtoBuf
-
-                val uin = vProtobuf.loadAs(SubD4.serializer()).uin
-                val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin)
-                return@lambda528 if (group != null && bot.groups.delegate.remove(group)) {
-                    sequenceOf(BotLeaveEvent.Active(group))
-                } else emptySequence()
-            },
-            // 群相关,  ModFriendRemark, DelFriend, ModGroupProfile
-            0x27L to lambda528 { bot ->
-                fun Submsgtype0x27.SubMsgType0x27.ModFriendRemark.transform(bot: QQAndroidBot): Sequence<Packet> {
-                    return this.msgFrdRmk?.asSequence()?.mapNotNull {
-                        val friend = bot.getFriendOrNull(it.fuin) ?: return@mapNotNull null
-                        // TODO: 2020/4/10 ADD REMARK QUERY
-                        FriendRemarkChangeEvent(bot, friend, it.rmkName)
-                    } ?: emptySequence()
-                }
-
-                fun Submsgtype0x27.SubMsgType0x27.DelFriend.transform(bot: QQAndroidBot): Sequence<Packet> {
-                    return this.uint64Uins?.asSequence()?.mapNotNull {
-                        val friend = bot.getFriendOrNull(it) ?: return@mapNotNull null
-                        if (bot.friends.delegate.remove(friend)) {
-                            FriendDeleteEvent(friend)
-                        } else null
-                    } ?: emptySequence()
-                }
-
-                fun Submsgtype0x27.SubMsgType0x27.ModGroupProfile.transform(bot: QQAndroidBot): Sequence<Packet> {
-                    return this.msgGroupProfileInfos?.asSequence()?.mapNotNull { info ->
-                        when (info.field) {
-                            1 -> {
-                                // 群名
-                                val new = info.value.encodeToString()
-
-                                val group = bot.getGroupOrNull(this.groupCode) ?: return@mapNotNull null
-                                group.checkIsGroupImpl()
-                                val old = group.name
-
-                                if (new == old) return@mapNotNull null
-
-                                val operator = if (this.cmdUin == bot.id) null
-                                else group.getOrNull(this.cmdUin) ?: return@mapNotNull null
-
-                                group._name = new
-
-                                return@mapNotNull GroupNameChangeEvent(old, new, group, operator)
-                            }
-                            2 -> {
-                                // 头像
-                                // top_package/akkz.java:3446
-                                /*
-                                var4 = var82.byteAt(0);
-                                   short var3 = (short) (var82.byteAt(1) | var4 << 8);
-                                   var85 = var18.method_77927(var7 + "");
-                                   var85.troopface = var3;
-                                   var85.hasSetNewTroopHead = true;
-                                 */
-                                bot.logger.debug(contextualBugReportException(
-                                    "解析 Transformers528 0x27L ModGroupProfile 群头像修改",
-                                    forDebug = "this=${this._miraiContentToString()}"
-                                ))
-                                null
-                            }
-                            3 -> { // troop.credit.data
-                                // top_package/akkz.java:3475
-                                // top_package/akkz.java:3498
-                                bot.logger.debug(contextualBugReportException(
-                                    "解析 Transformers528 0x27L ModGroupProfile 群 troop.credit.data",
-                                    forDebug = "this=${this._miraiContentToString()}"
-                                ))
-                                null
-                            }
-
-                            else -> null
-                        }
-                    } ?: emptySequence()
-                }
-
-                fun Submsgtype0x27.SubMsgType0x27.ModGroupMemberProfile.transform(bot: QQAndroidBot): Sequence<Packet> {
-                    return this.msgGroupMemberProfileInfos?.asSequence()?.mapNotNull { info ->
-                        when (info.field) {
-                            1 -> { // name card
-                                val new = info.value
-                                val group = bot.getGroupOrNull(this.groupCode) ?: return@mapNotNull null
-                                group.checkIsGroupImpl()
-                                val member = group.getOrNull(this.uin) ?: return@mapNotNull null
-                                member.checkIsMemberImpl()
-
-                                val old = member.nameCard
-
-                                if (new == old) return@mapNotNull null
-                                member._nameCard = new
-
-                                return@mapNotNull MemberCardChangeEvent(old, new, member)
-                            }
-                            2 -> {
-                                if (info.value.singleOrNull()?.toInt() != 0) {
-                                    bot.logger.debug {
-                                        "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}"
-                                    }
-                                }
-                                return@mapNotNull null
-                            }
-                            else -> {
-                                bot.logger.debug {
-                                    "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}"
-                                }
-                                return@mapNotNull null
-                            }
-                        }
-                    } ?: emptySequence()
-                }
-
-                fun Submsgtype0x27.SubMsgType0x27.ModCustomFace.transform(bot: QQAndroidBot): Sequence<Packet> =
-                    sequenceOf(BotFaceChangedEvent(Bot.getInstance(uin)))
-
-
-                return@lambda528 vProtobuf.loadAs(Submsgtype0x27.SubMsgType0x27.MsgBody.serializer()).msgModInfos.asSequence()
-                    .flatMap {
-                        when {
-                            it.msgModFriendRemark != null -> it.msgModFriendRemark.transform(bot)
-                            it.msgDelFriend != null -> it.msgDelFriend.transform(bot)
-                            it.msgModGroupProfile != null -> it.msgModGroupProfile.transform(bot)
-                            it.msgModGroupMemberProfile != null -> it.msgModGroupMemberProfile.transform(bot)
-                            it.msgModCustomFace != null -> it.msgModCustomFace.transform(bot)
-                            else -> {
-                                bot.network.logger.debug {
-                                    "Transformers528 0x27L: new data: ${it._miraiContentToString()}"
-                                }
-                                emptySequence()
-                            }
-                        }
-                    }
-                // 0A 1C 10 28 4A 18 0A 16 08 00 10 A2 FF 8C F0 03 1A 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B
-            }
-        )
-
-        @ExperimentalUnsignedTypes
-        @OptIn(ExperimentalStdlibApi::class)
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Response {
-            val reqPushMsg = readUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req")
-
-            val packets: Sequence<Packet> = reqPushMsg.vMsgInfos.deco(bot.client) { msgInfo ->
-                when (msgInfo.shMsgType.toInt()) {
-                    732 -> {
-                        val group = bot.getGroup(readUInt().toLong())
-                        GroupImpl.checkIsInstance(group)
-
-                        val internalType = readByte().toInt()
-                        discardExact(1)
-
-                        Transformers732[internalType]
-                            ?.let { it(this@deco, group, bot) }
-                            ?: kotlin.run {
-                                bot.network.logger.debug {
-                                    "unknown group 732 type $internalType, data: " + readBytes().toUHexString()
-                                }
-                                return@deco emptySequence()
-                            }
-                    }
-
-                    // 00 27 1A 0C 1C 2C 3C 4C 5D 00 0C 6D 00 0C 7D 00 0C 8D 00 0C 9C AC BC CC DD 00 0C EC FC 0F 0B 2A 0C 1C 2C 3C 4C 5C 6C 0B 3A 0C 1C 2C 3C 4C 5C 6C 7C 8D 00 0C 9D 00 0C AC BD 00 0C CD 00 0C DC ED 00 0C FC 0F FC 10 0B 4A 0C 1C 2C 3C 4C 5C 6C 7C 8C 96 00 0B 5A 0C 1C 2C 3C 4C 5C 6C 7C 8C 9D 00 0C 0B 6A 0C 1A 0C 1C 26 00 0B 2A 0C 0B 3A 0C 16 00 0B 4A 09 0C 0B 5A 09 0C 0B 0B 7A 0C 1C 2C 36 00 0B 8A 0C 1C 2C 36 00 0B 9A 09 0C 0B AD 00 00 1E 0A 1C 10 28 4A 18 0A 16 08 00 10 A2 FF 8C F0 03 1A 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B
-                    528 -> {
-                        val notifyMsgBody = readJceStruct(MsgType0x210.serializer())
-                        Transformers528[notifyMsgBody.uSubMsgType]
-                            ?.let { processor -> processor(notifyMsgBody, bot) }
-                            ?: kotlin.run {
-                                bot.network.logger.debug {
-                                    // Network(1994701021) 16:03:54 : unknown group 528 type 0x0000000000000026, data: 08 01 12 40 0A 06 08 F4 EF BB 8F 04 10 E7 C1 AD B8 02 18 01 22 2C 10 01 1A 1A 18 B4 DC F8 9B 0C 20 E7 C1 AD B8 02 28 06 30 02 A2 01 04 08 93 D6 03 A8 01 08 20 00 28 00 32 08 18 01 20 FE AF AF F5 05 28 00
-                                    // VIP 进群提示
-                                    "unknown group 528 type 0x${notifyMsgBody.uSubMsgType.toUHexString("")}, data: " + notifyMsgBody.vProtobuf.toUHexString()
-                                }
-                                return@deco emptySequence()
-                            }
-                    }
-                    else -> {
-                        bot.network.logger.debug { "unknown sh type ${msgInfo.shMsgType.toInt()}" }
-                        bot.network.logger.debug { "data=${readBytes().toUHexString()}" }
-                        return@deco emptySequence()
-                    }
-                }
-            }
-            return Response(reqPushMsg, packets)
-        }
-
-        @Suppress("SpellCheckingInspection")
-        internal data class Response(val request: OnlinePushPack.SvcReqPushMsg, val sequence: Sequence<Packet>) :
-            MultiPacketBySequence<Packet>(sequence) {
-            override fun toString(): String {
-                return "OnlinePush.ReqPush.Response"
-            }
-        }
-
-        override suspend fun QQAndroidBot.handle(packet: Response, sequenceId: Int): OutgoingPacket? {
-            return buildResponseUniPacket(client) {
-                writeJceStruct(
-                    RequestPacket.serializer(),
-                    RequestPacket(
-                        sServantName = "OnlinePush",
-                        sFuncName = "SvcRespPushMsg",
-                        iRequestId = sequenceId,
-                        sBuffer = jceRequestSBuffer(
-                            "resp",
-                            OnlinePushPack.SvcRespPushMsg.serializer(),
-                            OnlinePushPack.SvcRespPushMsg(
-                                packet.request.uin,
-                                packet.request.vMsgInfos.map { msg ->
-                                    OnlinePushPack.DelMsgInfo(
-                                        fromUin = msg.lFromUin,
-                                        shMsgSeq = msg.shMsgSeq,
-                                        vMsgCookies = msg.vMsgCookies,
-                                        uMsgTime = msg.uMsgTime // captured 0
-                                    )
-                                }
-                            )
-                        )
-                    )
-                )
-            }
-        }
-    }
-}
-
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
index e41107f9a..cbac2aa5b 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
@@ -155,7 +155,7 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
         override val messageInternalId: Int,
         override val messageTime: Int,
         /**
-         * 撤回操作人, 可能为 [Bot.uin] 或好友的 [User.id]
+         * 撤回操作人, 可能为 [Bot.id] 或好友的 [User.id]
          */
         val operator: Long
     ) : MessageRecallEvent(), Packet {