From 7bebb23ca3eff10d30fa8260a1aa4dcd1b5b90d8 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Wed, 25 May 2022 20:36:58 +0100
Subject: [PATCH] Remove legacy `sendAndExpect` to reduce code complexity to
 avoid compiler bugs, fix #2049

---
 mirai-core/src/commonMain/kotlin/MiraiImpl.kt | 240 ++++++++++--------
 .../commonMain/kotlin/contact/AbstractUser.kt |  29 ++-
 .../commonMain/kotlin/contact/FriendImpl.kt   |   9 +-
 .../commonMain/kotlin/contact/GroupImpl.kt    | 135 +++++-----
 .../kotlin/contact/GroupSettingsImpl.kt       |  15 +-
 .../kotlin/contact/NormalMemberImpl.kt        |  74 +++---
 .../kotlin/contact/SendMessageHandler.kt      |   2 +-
 .../commonMain/kotlin/contact/StrangerImpl.kt |  15 +-
 .../kotlin/contact/file/AbsoluteFileImpl.kt   |  54 ++--
 .../kotlin/contact/file/AbsoluteFolderImpl.kt |  55 ++--
 .../file/AbstractAbsoluteFileFolder.kt        |  19 +-
 .../contact/roaming/RoamingMessagesImpl.kt    |  25 +-
 .../kotlin/message/FileMessageImpl.kt         |   5 +-
 .../message/InternalImageProtocolImpl.kt      |  89 ++++---
 .../kotlin/message/MultiMsgUploader.kt        |   8 +-
 .../network/components/ContactUpdater.kt      |  33 ++-
 .../network/components/HeartbeatProcessor.kt  |  17 +-
 .../network/components/KeyRefreshProcessor.kt |  11 +-
 .../network/components/MessageSvcSyncer.kt    |   3 +-
 .../kotlin/network/components/SsoProcessor.kt |  10 +-
 .../kotlin/network/handler/NetworkHandler.kt  |  35 +--
 .../network/handler/NetworkHandlerSupport.kt  |  12 +-
 .../selector/SelectorNetworkHandler.kt        |  18 +-
 .../network/notice/NewContactSupport.kt       |  16 +-
 .../notice/UnconsumedNoticesAlerter.kt        |  16 +-
 .../notice/priv/FriendNoticeProcessor.kt      |  18 +-
 .../network/protocol/packet/OutgoingPacket.kt |  32 ---
 .../protocol/packet/chat/NudgePacket.kt       |  13 +-
 .../protocol/packet/chat/TroopManagement.kt   |  15 +-
 .../protocol/packet/chat/image/LongConn.kt    |  14 +-
 .../chat/receive/MessageSvc.PbDeleteMsg.kt    |  30 +--
 .../chat/receive/MessageSvc.PbGetMsg.kt       |  24 +-
 .../chat/receive/OnlinePush.SidExpired.kt     |  13 +-
 .../commonMain/kotlin/utils/ImagePatcher.kt   |  33 +--
 .../commonMain/kotlin/utils/RemoteFileImpl.kt | 113 +++++----
 35 files changed, 632 insertions(+), 618 deletions(-)

diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt
index a6d662d87..01cc629fa 100644
--- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt
@@ -52,7 +52,6 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.PbMessageSvc
 import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
 import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
 import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
 import net.mamoe.mirai.internal.network.psKey
 import net.mamoe.mirai.internal.network.sKey
@@ -257,7 +256,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
     override suspend fun getOnlineOtherClientsList(bot: Bot, mayIncludeSelf: Boolean): List<OtherClientInfo> {
         bot.asQQAndroidBot()
         val response = bot.network.run {
-            StatSvc.GetDevLoginInfo(bot.client).sendAndExpect()
+            bot.network.sendAndExpect(StatSvc.GetDevLoginInfo(bot.client))
         }
 
         fun SvcDevLoginInfo.toOtherClientInfo() = OtherClientInfo(
@@ -355,7 +354,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
     override suspend fun getRawGroupList(bot: Bot): Sequence<Long> {
         bot.asQQAndroidBot()
         return bot.network.run {
-            FriendList.GetTroopListSimplify(bot.client).sendAndExpect(retry = 2)
+            bot.network.sendAndExpect(FriendList.GetTroopListSimplify(bot.client))
         }.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode }
     }
 
@@ -365,44 +364,45 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         groupUin: Long,
         groupCode: Long,
         ownerId: Long
-    ): Sequence<MemberInfo> =
-        bot.asQQAndroidBot().network.run {
-            var nextUin = 0L
-            var sequence = sequenceOf<MemberInfoImpl>()
-            while (true) {
-                val data = FriendList.GetTroopMemberList(
+    ): Sequence<MemberInfo> {
+        var nextUin = 0L
+        var sequence = sequenceOf<MemberInfoImpl>()
+        while (true) {
+            val data = bot.asQQAndroidBot().network.sendAndExpect(
+                FriendList.GetTroopMemberList(
                     client = bot.client,
                     targetGroupUin = groupUin,
                     targetGroupCode = groupCode,
                     nextUin = nextUin
-                ).sendAndExpect(retry = 3)
-                sequence += data.members.asSequence().map { troopMemberInfo ->
-                    MemberInfoImpl(bot.client, troopMemberInfo, ownerId)
-                }
-                nextUin = data.nextUin
-                if (nextUin == 0L) {
-                    break
-                }
+                ), 5000, 3
+            )
+            sequence += data.members.asSequence().map { troopMemberInfo ->
+                MemberInfoImpl(bot.client, troopMemberInfo, ownerId)
+            }
+            nextUin = data.nextUin
+            if (nextUin == 0L) {
+                break
             }
-            return sequence
         }
+        return sequence
+    }
 
     override suspend fun recallGroupMessageRaw(
         bot: Bot,
         groupCode: Long,
         messageIds: IntArray,
         messageInternalIds: IntArray,
-    ): Boolean = bot.asQQAndroidBot().run {
-        val response = network.run {
+    ): Boolean {
+        val response = bot.asQQAndroidBot().network.sendAndExpect(
             PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
-                client,
+                bot.client,
                 groupCode,
                 messageIds,
                 messageInternalIds
-            ).sendAndExpect()
-        }
+            ), 5000, 2
+        )
 
-        response is PbMessageSvc.PbMsgWithDraw.Response.Success
+        return response is PbMessageSvc.PbMsgWithDraw.Response.Success
     }
 
     override suspend fun recallFriendMessageRaw(
@@ -411,18 +411,18 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         messageIds: IntArray,
         messageInternalIds: IntArray,
         time: Int,
-    ): Boolean = bot.asQQAndroidBot().run {
-        val response = network.run {
+    ): Boolean {
+        val response = bot.asQQAndroidBot().network.sendAndExpect(
             PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
-                client,
+                bot.client,
                 targetId,
                 messageIds,
                 messageInternalIds,
                 time,
-            ).sendAndExpect()
-        }
+            ), 5000, 2
+        )
 
-        response is PbMessageSvc.PbMsgWithDraw.Response.Success
+        return response is PbMessageSvc.PbMsgWithDraw.Response.Success
     }
 
     override suspend fun recallGroupTempMessageRaw(
@@ -432,19 +432,19 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         messageIds: IntArray,
         messageInternalIds: IntArray,
         time: Int
-    ): Boolean = bot.asQQAndroidBot().run {
-        val response = network.run {
+    ): Boolean {
+        val response = bot.asQQAndroidBot().network.sendAndExpect(
             PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage(
-                client,
+                bot.client,
                 groupUin,
                 targetId,
                 messageIds,
                 messageInternalIds,
                 time,
-            ).sendAndExpect()
-        }
+            ), 5000, 2
+        )
 
-        response is PbMessageSvc.PbMsgWithDraw.Response.Success
+        return response is PbMessageSvc.PbMsgWithDraw.Response.Success
     }
 
     @Suppress("RemoveExplicitTypeArguments") // false positive
@@ -477,81 +477,92 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
                     }
                 }
 
-                network.run {
+
+                bot.asQQAndroidBot().network.sendAndExpect(
                     PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
                         bot.asQQAndroidBot().client,
                         group.id,
                         source.sequenceIds,
                         source.internalIds
-                    ).sendAndExpect()
-                }
+                    ), 5000, 2
+                )
             }
             is OnlineMessageSourceFromFriendImpl,
             is OnlineMessageSourceToFriendImpl,
             is OnlineMessageSourceFromStrangerImpl,
             is OnlineMessageSourceToStrangerImpl,
-            -> network.run {
+            -> {
                 check(source.fromId == bot.id) {
                     "can only recall a message sent by bot"
                 }
-                PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
-                    bot.client,
-                    source.targetId,
-                    source.sequenceIds,
-                    source.internalIds,
-                    source.time
-                ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
+                bot.asQQAndroidBot().network.sendAndExpect(
+                    PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
+                        bot.client,
+                        source.targetId,
+                        source.sequenceIds,
+                        source.internalIds,
+                        source.time
+                    ), 5000, 2
+                )
             }
             is OnlineMessageSourceFromTempImpl,
             is OnlineMessageSourceToTempImpl
-            -> network.run {
+            -> {
                 check(source.fromId == bot.id) {
                     "can only recall a message sent by bot"
                 }
                 source as OnlineMessageSourceToTempImpl
-                PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage(
-                    bot.client,
-                    (source.target.group as GroupImpl).uin,
-                    source.targetId,
-                    source.sequenceIds,
-                    source.internalIds,
-                    source.time
-                ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
+                bot.asQQAndroidBot().network.sendAndExpect(
+                    PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage(
+                        bot.client,
+                        (source.target.group as GroupImpl).uin,
+                        source.targetId,
+                        source.sequenceIds,
+                        source.internalIds,
+                        source.time
+                    ), 5000, 2
+                )
             }
-            is OfflineMessageSource -> network.run {
+            is OfflineMessageSource -> {
                 when (source.kind) {
                     MessageSourceKind.FRIEND, MessageSourceKind.STRANGER -> {
                         check(source.fromId == bot.id) {
                             "can only recall a message sent by bot"
                         }
-                        PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
-                            bot.client,
-                            source.targetId,
-                            source.sequenceIds,
-                            source.internalIds,
-                            source.time
-                        ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
+                        bot.asQQAndroidBot().network.sendAndExpect(
+                            PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
+                                bot.client,
+                                source.targetId,
+                                source.sequenceIds,
+                                source.internalIds,
+                                source.time
+                            ), 5000, 2
+                        )
                     }
                     MessageSourceKind.TEMP -> {
                         check(source.fromId == bot.id) {
                             "can only recall a message sent by bot"
                         }
-                        PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage(
-                            bot.client,
-                            source.targetId, // groupUin
-                            source.targetId, // memberUin
-                            source.sequenceIds,
-                            source.internalIds,
-                            source.time
-                        ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
+                        bot.asQQAndroidBot().network.sendAndExpect(
+                            PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage(
+                                bot.client,
+                                source.targetId, // groupUin
+                                source.targetId, // memberUin
+                                source.sequenceIds,
+                                source.internalIds,
+                                source.time
+                            ), 5000, 2
+                        )
                     }
                     MessageSourceKind.GROUP -> {
-                        PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
-                            bot.client,
-                            source.targetId,
-                            source.sequenceIds,
-                            source.internalIds
-                        ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
+                        bot.asQQAndroidBot().network.sendAndExpect(
+                            PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
+                                bot.client,
+                                source.targetId,
+                                source.sequenceIds,
+                                source.internalIds
+                            ), 5000, 2
+                        )
                     }
                 }
             }
@@ -651,20 +662,20 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         accept: Boolean,
         blackList: Boolean
     ): Unit = bot.asQQAndroidBot().run {
-        network.apply {
+        network.sendWithoutExpect(
             NewContact.SystemMsgNewFriend.Action(
                 bot.client,
                 eventId = eventId,
                 fromId = fromId,
                 accept = accept,
                 blackList = blackList
-            ).sendWithoutExpect()
+            )
+        )
 
-            if (!accept) return@apply
+        if (!accept) return
 
-            @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-            bot.friends.delegate.add(newFriend(bot, FriendInfoImpl(fromId, fromNick, "")))
-        }
+        @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+        bot.friends.delegate.add(newFriend(bot, FriendInfoImpl(fromId, fromNick, "")))
     }
 
     override suspend fun solveBotInvitedJoinGroupRequestEvent(
@@ -673,8 +684,8 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         invitorId: Long,
         groupId: Long,
         accept: Boolean
-    ) = bot.asQQAndroidBot().run {
-        network.run {
+    ) {
+        bot.asQQAndroidBot().network.sendWithoutExpect(
             NewContact.SystemMsgNewGroup.Action(
                 bot.client,
                 eventId = eventId,
@@ -682,8 +693,8 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
                 groupId = groupId,
                 isInvited = true,
                 accept = accept
-            ).sendWithoutExpect()
-        }
+            )
+        )
     }
 
     override suspend fun solveMemberJoinRequestEvent(
@@ -695,8 +706,8 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         accept: Boolean?,
         blackList: Boolean,
         message: String
-    ) = bot.asQQAndroidBot().run {
-        network.run {
+    ) {
+        bot.asQQAndroidBot().network.sendWithoutExpect(
             NewContact.SystemMsgNewGroup.Action(
                 bot.client,
                 eventId = eventId,
@@ -706,8 +717,8 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
                 accept = accept,
                 blackList = blackList,
                 message = message
-            ).sendWithoutExpect()
-        }
+            )
+        )
         // Add member in MsgOnlinePush.PbPushMsg
     }
 
@@ -718,10 +729,12 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         groupId: Long,
         dstUin: Long
     ): String {
-        bot.asQQAndroidBot().network.run {
-            val response = PttStore.GroupPttDown(bot.client, groupId, dstUin, md5).sendAndExpect()
-            return "http://${response.strDomain}${response.downPara.decodeToString()}"
-        }
+        val response = bot.asQQAndroidBot().network.sendAndExpect(
+            PttStore.GroupPttDown(bot.client, groupId, dstUin, md5),
+            5000,
+            2
+        )
+        return "http://${response.strDomain}${response.downPara.decodeToString()}"
     }
 
     override suspend fun muteAnonymousMember(
@@ -772,10 +785,11 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
     }
 
     override suspend fun queryProfile(bot: Bot, targetId: Long): UserProfile {
-        bot.asQQAndroidBot().network.apply {
-            return SummaryCard.ReqSummaryCard(bot.client, targetId)
-                .sendAndExpect()
-        }
+
+        return bot.asQQAndroidBot().network.sendAndExpect(
+            SummaryCard.ReqSummaryCard(bot.client, targetId),
+            5000, 2
+        )
     }
 
     override suspend fun sendNudge(bot: Bot, nudge: Nudge, receiver: Contact): Boolean {
@@ -787,17 +801,21 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         bot.network.run {
             return if (receiver is Group) {
                 receiver.checkIsGroupImpl()
-                NudgePacket.troopInvoke(
-                    client = bot.client,
-                    messageReceiverGroupCode = receiver.id,
-                    nudgeTargetId = nudge.target.id,
-                ).sendAndExpect<NudgePacket.Response>().success
+                bot.network.sendAndExpect(
+                    NudgePacket.troopInvoke(
+                        client = bot.client,
+                        messageReceiverGroupCode = receiver.id,
+                        nudgeTargetId = nudge.target.id,
+                    )
+                ).success
             } else {
-                NudgePacket.friendInvoke(
-                    client = bot.client,
-                    messageReceiverUin = receiver.id,
-                    nudgeTargetId = nudge.target.id,
-                ).sendAndExpect<NudgePacket.Response>().success
+                bot.network.sendAndExpect(
+                    NudgePacket.friendInvoke(
+                        client = bot.client,
+                        messageReceiverUin = receiver.id,
+                        nudgeTargetId = nudge.target.id,
+                    )
+                ).success
             }
         }
     }
@@ -879,7 +897,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         resourceKind: ResourceKind,
     ): MsgTransmit.PbMultiMsgTransmit {
         bot.asQQAndroidBot()
-        when (val resp = MultiMsg.ApplyDown(bot.client, 2, resourceId, 1).sendAndExpect(bot)) {
+        when (val resp = bot.network.sendAndExpect(MultiMsg.ApplyDown(bot.client, 2, resourceId, 1))) {
             is MultiMsg.ApplyDown.Response.RequireDownload -> {
                 @Suppress("DEPRECATION", "DEPRECATION_ERROR")
                 val http = Mirai.Http
diff --git a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt
index c5d5cfcac..ddff7060e 100644
--- a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt
@@ -28,7 +28,6 @@ import net.mamoe.mirai.internal.network.highway.tryServersUpload
 import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352
 import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
 import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.utils.AtomicIntSeq
 import net.mamoe.mirai.internal.utils.C2CPkgMsgParsingCache
 import net.mamoe.mirai.internal.utils.structureToString
@@ -76,7 +75,7 @@ internal sealed class AbstractUser(
             throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
         }
         val imageInfo = runBIO { resource.calculateImageInfo() }
-        val resp = bot.network.run {
+        val resp = bot.network.sendAndExpect(
             LongConn.OffPicUp(
                 bot.client,
                 Cmd0x352.TryUpImgReq(
@@ -92,8 +91,8 @@ internal sealed class AbstractUser(
                     imgOriginal = true,
                     buildVer = bot.client.buildVer,
                 ),
-            ).sendAndExpect<LongConn.OffPicUp.Response>()
-        }
+            ), 5000, 2
+        )
 
         return when (resp) {
             is LongConn.OffPicUp.Response.FileExists -> {
@@ -141,16 +140,18 @@ internal sealed class AbstractUser(
                     )
                 }.recoverCatchingSuppressed {
                     // try upload as group image
-                    val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
-                        bot.client,
-                        uin = bot.id,
-                        groupCode = id,
-                        md5 = resource.md5,
-                        size = resource.size,
-                        picWidth = imageInfo.width,
-                        picHeight = imageInfo.height,
-                        picType = getIdByImageType(imageInfo.imageType),
-                    ).sendAndExpect(bot)
+                    val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect(
+                        ImgStore.GroupPicUp(
+                            bot.client,
+                            uin = bot.id,
+                            groupCode = id,
+                            md5 = resource.md5,
+                            size = resource.size,
+                            picWidth = imageInfo.width,
+                            picHeight = imageInfo.height,
+                            picType = getIdByImageType(imageInfo.imageType),
+                        )
+                    )
 
                     when (response) {
                         is ImgStore.GroupPicUp.Response.Failed -> {
diff --git a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt
index d2a2859fa..f5465e800 100644
--- a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt
@@ -30,7 +30,6 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
 import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.audioCodec
 import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.utils.io.serialization.loadAs
 import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
 import net.mamoe.mirai.message.MessageReceipt
@@ -69,10 +68,8 @@ internal class FriendImpl(
         check(bot.friends[id] != null) {
             "Friend $id had already been deleted"
         }
-        bot.network.run {
-            FriendList.DelFriend.invoke(bot.client, this@FriendImpl).sendAndExpect().also {
-                check(it.isSuccess) { "delete friend failed: ${it.resultCode}" }
-            }
+        bot.network.sendAndExpect(FriendList.DelFriend.invoke(bot.client, this@FriendImpl), 5000, 2).let {
+            check(it.isSuccess) { "delete friend failed: ${it.resultCode}" }
         }
     }
 
@@ -120,7 +117,7 @@ internal class FriendImpl(
                 )
             )
         }.recoverCatchingSuppressed {
-            when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, res).sendAndExpect(bot)) {
+            when (val resp = bot.network.sendAndExpect(PttStore.GroupPttUp(bot.client, bot.id, id, res))) {
                 is PttStore.GroupPttUp.Response.RequireUpload -> {
                     tryServersUpload(
                         bot,
diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt
index 501fcd317..8120a99bb 100644
--- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt
@@ -28,7 +28,6 @@ import net.mamoe.mirai.internal.contact.file.RemoteFilesImpl
 import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
 import net.mamoe.mirai.internal.message.*
 import net.mamoe.mirai.internal.network.components.BdhSession
-import net.mamoe.mirai.internal.network.handler.NetworkHandler
 import net.mamoe.mirai.internal.network.handler.logger
 import net.mamoe.mirai.internal.network.highway.ChannelKind
 import net.mamoe.mirai.internal.network.highway.Highway
@@ -43,7 +42,6 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
 import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.audioCodec
 import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec
 import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache
 import net.mamoe.mirai.internal.utils.ImagePatcher
 import net.mamoe.mirai.internal.utils.RemoteFileImpl
@@ -151,15 +149,13 @@ internal class GroupImpl constructor(
         if (!bot.groups.delegate.remove(this)) {
             return false
         }
-        bot.network.run {
-            val response: ProfileService.GroupMngReq.GroupMngReqResponse = ProfileService.GroupMngReq(
-                bot.client,
-                this@GroupImpl.id
-            ).sendAndExpect()
-            check(response.errorCode == 0) {
-                "Group.quit failed: $response".also {
-                    bot.groups.delegate.add(this@GroupImpl)
-                }
+
+        val response: ProfileService.GroupMngReq.GroupMngReqResponse = bot.network.sendAndExpect(
+            ProfileService.GroupMngReq(bot.client, this@GroupImpl.id), 5000, 2
+        )
+        check(response.errorCode == 0) {
+            "Group.quit failed: $response".also {
+                bot.groups.delegate.add(this@GroupImpl)
             }
         }
         BotLeaveEvent.Active(this).broadcast()
@@ -210,8 +206,9 @@ internal class GroupImpl constructor(
         }
 
         val imageInfo = runBIO { resource.calculateImageInfo() }
-        bot.network.run<NetworkHandler, Image> {
-            val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
+
+        val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect(
+            ImgStore.GroupPicUp(
                 bot.client,
                 uin = bot.id,
                 groupCode = id,
@@ -222,60 +219,60 @@ internal class GroupImpl constructor(
                 picHeight = imageInfo.height,
                 picType = getIdByImageType(imageInfo.imageType),
                 originalPic = 1
-            ).sendAndExpect()
+            ), 5000, 2
+        )
 
-            when (response) {
-                is ImgStore.GroupPicUp.Response.Failed -> {
-                    ImageUploadEvent.Failed(this@GroupImpl, resource, response.resultCode, response.message).broadcast()
-                    if (response.message == "over file size max") throw OverFileSizeMaxException()
-                    error("upload group image failed with reason ${response.message}")
-                }
-                is ImgStore.GroupPicUp.Response.FileExists -> {
-                    val resourceId = resource.calculateResourceId()
-                    return response.fileInfo.run {
-                        OfflineGroupImage(
-                            imageId = resourceId,
-                            height = fileHeight,
-                            width = fileWidth,
-                            imageType = getImageTypeById(fileType),
-                            size = resource.size
-                        )
-                    }
-                        .also {
-                            it.fileId = response.fileId.toInt()
-                        }
-                        .also { it.putIntoCache() }
-                        .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
-                }
-                is ImgStore.GroupPicUp.Response.RequireUpload -> {
-                    // val servers = response.uploadIpList.zip(response.uploadPortList)
-                    Highway.uploadResourceBdh(
-                        bot = bot,
-                        resource = resource,
-                        kind = GROUP_IMAGE,
-                        commandId = 2,
-                        initialTicket = response.uKey,
-                        noBdhAwait = true,
-                        fallbackSession = {
-                            BdhSession(
-                                EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY,
-                                ssoAddresses = response.uploadIpList.zip(response.uploadPortList).toMutableSet(),
-                            )
-                        },
+        when (response) {
+            is ImgStore.GroupPicUp.Response.Failed -> {
+                ImageUploadEvent.Failed(this@GroupImpl, resource, response.resultCode, response.message).broadcast()
+                if (response.message == "over file size max") throw OverFileSizeMaxException()
+                error("upload group image failed with reason ${response.message}")
+            }
+            is ImgStore.GroupPicUp.Response.FileExists -> {
+                val resourceId = resource.calculateResourceId()
+                return response.fileInfo.run {
+                    OfflineGroupImage(
+                        imageId = resourceId,
+                        height = fileHeight,
+                        width = fileWidth,
+                        imageType = getImageTypeById(fileType),
+                        size = resource.size
                     )
-
-                    return imageInfo.run {
-                        OfflineGroupImage(
-                            imageId = resource.calculateResourceId(),
-                            width = width,
-                            height = height,
-                            imageType = imageType,
-                            size = resource.size
-                        )
-                    }.also { it.fileId = response.fileId.toInt() }
-                        .also { it.putIntoCache() }
-                        .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
                 }
+                    .also {
+                        it.fileId = response.fileId.toInt()
+                    }
+                    .also { it.putIntoCache() }
+                    .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
+            }
+            is ImgStore.GroupPicUp.Response.RequireUpload -> {
+                // val servers = response.uploadIpList.zip(response.uploadPortList)
+                Highway.uploadResourceBdh(
+                    bot = bot,
+                    resource = resource,
+                    kind = GROUP_IMAGE,
+                    commandId = 2,
+                    initialTicket = response.uKey,
+                    noBdhAwait = true,
+                    fallbackSession = {
+                        BdhSession(
+                            EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY,
+                            ssoAddresses = response.uploadIpList.zip(response.uploadPortList).toMutableSet(),
+                        )
+                    },
+                )
+
+                return imageInfo.run {
+                    OfflineGroupImage(
+                        imageId = resource.calculateResourceId(),
+                        width = width,
+                        height = height,
+                        imageType = imageType,
+                        size = resource.size
+                    )
+                }.also { it.fileId = response.fileId.toInt() }
+                    .also { it.putIntoCache() }
+                    .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
             }
         }
     }
@@ -300,8 +297,8 @@ internal class GroupImpl constructor(
                     res.voiceCodec,
                     ""
                 )
+            }
         }
-    }
 
     private suspend fun uploadAudioResource(resource: ExternalResource) {
         kotlin.runCatching {
@@ -314,7 +311,7 @@ internal class GroupImpl constructor(
                     .toByteArray(Cmd0x388.ReqBody.serializer()),
             )
         }.recoverCatchingSuppressed {
-            when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect(bot)) {
+            when (val resp = bot.network.sendAndExpect(PttStore.GroupPttUp(bot.client, bot.id, id, resource))) {
                 is PttStore.GroupPttUp.Response.RequireUpload -> {
                     tryServersUpload(
                         bot,
@@ -354,14 +351,14 @@ internal class GroupImpl constructor(
 
     override suspend fun setEssenceMessage(source: MessageSource): Boolean {
         checkBotPermission(MemberPermission.ADMINISTRATOR)
-        val result = bot.network.run {
+        val result = bot.network.sendAndExpect(
             TroopEssenceMsgManager.SetEssence(
                 bot.client,
                 this@GroupImpl.uin,
                 source.internalIds.first(),
                 source.ids.first()
-            ).sendAndExpect()
-        }
+            ), 5000, 2
+        )
         return result.success
     }
 
diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupSettingsImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupSettingsImpl.kt
index 870872e07..49bf1fd80 100644
--- a/mirai-core/src/commonMain/kotlin/contact/GroupSettingsImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/GroupSettingsImpl.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.contact
@@ -24,7 +24,6 @@ import net.mamoe.mirai.internal.network.QQAndroidClient
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement.GroupOperation
 import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement.SwitchAnonymousChat
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 
 @Suppress("SetterBackingFieldAssignment")
 internal class GroupSettingsImpl(
@@ -43,9 +42,7 @@ internal class GroupSettingsImpl(
         val oldValue = getter()
         setter(newValue)
         launch {
-            bot.network.run {
-                packetConstructor(bot.client, id, newValue).sendWithoutExpect()
-            }
+            bot.network.sendWithoutExpect(packetConstructor(bot.client, id, newValue))
             eventConstructor(oldValue).broadcast()
         }
     }
@@ -98,7 +95,7 @@ internal class GroupSettingsImpl(
                 checkBotPermission(MemberPermission.ADMINISTRATOR)
                 launch {
                     //Handle it in NoticePipelineContext#processAllowAnonymousChat
-                    SwitchAnonymousChat(bot.client, id, newValue).sendAndExpect(bot.network)
+                    bot.network.sendAndExpect(SwitchAnonymousChat(bot.client, id, newValue))
                 }
             }
         }
diff --git a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt
index 821137ae5..05177e0bb 100644
--- a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 @file:Suppress("EXPERIMENTAL_API_USAGE")
@@ -93,13 +93,13 @@ internal class NormalMemberImpl constructor(
                 val oldValue = _nameCard
                 _nameCard = newValue
                 launch {
-                    bot.network.run {
+                    bot.network.sendWithoutExpect(
                         TroopManagement.EditGroupNametag(
                             bot.client,
                             this@NormalMemberImpl,
                             newValue,
-                        ).sendWithoutExpect()
-                    }
+                        )
+                    )
                     MemberCardChangeEvent(oldValue, newValue, this@NormalMemberImpl).broadcast()
                 }
             }
@@ -113,13 +113,13 @@ internal class NormalMemberImpl constructor(
                 val oldValue = _specialTitle
                 _specialTitle = newValue
                 launch {
-                    bot.network.run {
+                    bot.network.sendWithoutExpect(
                         TroopManagement.EditSpecialTitle(
                             bot.client,
                             this@NormalMemberImpl,
                             newValue,
-                        ).sendWithoutExpect()
-                    }
+                        )
+                    )
                     MemberSpecialTitleChangeEvent(oldValue, newValue, this@NormalMemberImpl, null).broadcast()
                 }
             }
@@ -133,14 +133,14 @@ internal class NormalMemberImpl constructor(
             "durationSeconds must greater than zero"
         }
         checkBotPermissionHigherThanThis("mute")
-        bot.network.run {
+        bot.network.sendAndExpect(
             TroopManagement.Mute(
                 client = bot.client,
                 groupCode = group.id,
                 memberUin = this@NormalMemberImpl.id,
                 timeInSecond = durationSeconds,
-            ).sendAndExpect<TroopManagement.Mute.Response>()
-        }
+            ), 5000, 2
+        )
 
         @Suppress("RemoveRedundantQualifierName") // or unresolved reference
         (net.mamoe.mirai.event.events.MemberMuteEvent(this@NormalMemberImpl, durationSeconds, null).broadcast())
@@ -149,14 +149,14 @@ internal class NormalMemberImpl constructor(
 
     override suspend fun unmute() {
         checkBotPermissionHigherThanThis("unmute")
-        bot.network.run {
+        bot.network.sendAndExpect(
             TroopManagement.Mute(
                 client = bot.client,
                 groupCode = group.id,
                 memberUin = this@NormalMemberImpl.id,
                 timeInSecond = 0,
-            ).sendAndExpect<TroopManagement.Mute.Response>()
-        }
+            ), 5000, 2
+        )
 
         @Suppress("RemoveRedundantQualifierName") // or unresolved reference
         (net.mamoe.mirai.event.events.MemberUnmuteEvent(this@NormalMemberImpl, null).broadcast())
@@ -168,25 +168,25 @@ internal class NormalMemberImpl constructor(
         check(group.members[this.id] != null) {
             "Member ${this.id} had already been kicked from group ${group.id}"
         }
-        bot.network.run {
-            val response: TroopManagement.Kick.Response = TroopManagement.Kick(
+        val response: TroopManagement.Kick.Response = bot.network.sendAndExpect(
+            TroopManagement.Kick(
                 client = bot.client,
                 groupCode = group.groupCode,
                 memberId = id,
                 message = message,
                 ban = block
-            ).sendAndExpect()
+            ), 5000, 2
+        )
 
-            // Note: when member not found, result is still true.
+        // Note: when member not found, result is still true.
 
-            if (response.ret == 255) error("Operation too fast") // https://github.com/mamoe/mirai/issues/1503
-            check(response.success) { "kick failed: ${response.ret}" }
+        if (response.ret == 255) error("Operation too fast") // https://github.com/mamoe/mirai/issues/1503
+        check(response.success) { "kick failed: ${response.ret}" }
 
-            @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-            group.members.delegate.removeIf { it.id == this@NormalMemberImpl.id }
-            this@NormalMemberImpl.cancel(CancellationException("Kicked by bot"))
-            MemberLeaveEvent.Kick(this@NormalMemberImpl, null).broadcast()
-        }
+        @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+        group.members.delegate.removeIf { it.id == this@NormalMemberImpl.id }
+        this@NormalMemberImpl.cancel(CancellationException("Kicked by bot"))
+        MemberLeaveEvent.Kick(this@NormalMemberImpl, null).broadcast()
     }
 
     override suspend fun modifyAdmin(operation: Boolean) {
@@ -201,21 +201,21 @@ internal class NormalMemberImpl constructor(
 
         if (origin == new) return
 
-        bot.network.run {
-            val resp: TroopManagement.ModifyAdmin.Response = TroopManagement.ModifyAdmin(
+        val resp: TroopManagement.ModifyAdmin.Response = bot.network.sendAndExpect(
+            TroopManagement.ModifyAdmin(
                 client = bot.client,
                 member = this@NormalMemberImpl,
                 operation = operation,
-            ).sendAndExpect()
+            ), 5000, 2
+        ) as TroopManagement.ModifyAdmin.Response
 
-            check(resp.success) {
-                "Failed to modify admin, cause: ${resp.msg}"
-            }
-
-            this@NormalMemberImpl.permission = new
-
-            MemberPermissionChangeEvent(this@NormalMemberImpl, origin, new).broadcast()
+        check(resp.success) {
+            "Failed to modify admin, cause: ${resp.msg}"
         }
+
+        this@NormalMemberImpl.permission = new
+
+        MemberPermissionChangeEvent(this@NormalMemberImpl, origin, new).broadcast()
     }
 }
 
diff --git a/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
index 0cea159b7..f26b387ae 100644
--- a/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
@@ -138,7 +138,7 @@ internal abstract class SendMessageHandler<C : Contact> {
                 fragmented = step == SendMessageStep.FRAGMENTED
             ) { source = it }.forEach { packet ->
 
-                when (val resp = packet.sendAndExpect<Packet>()) {
+                when (val resp = bot.network.sendAndExpect<Packet>(packet, 5000, 2)) {
                     is MessageSvcPbSendMsg.Response -> {
                         if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) {
                             return when (step) {
diff --git a/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt b/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt
index 7459eb91a..26213da79 100644
--- a/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 @file:OptIn(LowLevelApi::class)
 @file:Suppress(
@@ -54,11 +54,8 @@ internal class StrangerImpl(
         check(bot.strangers[this.id] != null) {
             "Stranger ${this.id} had already been deleted"
         }
-        bot.network.run {
-            StrangerList.DelStranger(bot.client, this@StrangerImpl)
-                .sendAndExpect().also {
-                    check(it.isSuccess) { "delete Stranger failed: ${it.result}" }
-                }
+        bot.network.sendAndExpect(StrangerList.DelStranger(bot.client, this@StrangerImpl), 5000, 2).also {
+            check(it.isSuccess) { "delete Stranger failed: ${it.result}" }
         }
     }
 
diff --git a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFileImpl.kt b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFileImpl.kt
index e044ba8be..adeda2bf1 100644
--- a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFileImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFileImpl.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
@@ -15,7 +15,6 @@ import net.mamoe.mirai.contact.file.AbsoluteFolder
 import net.mamoe.mirai.internal.message.FileMessageImpl
 import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
 import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.message.data.FileMessage
 import net.mamoe.mirai.utils.toUHexString
 
@@ -59,13 +58,14 @@ internal class AbsoluteFileImpl(
         }
 
     override suspend fun exists(): Boolean {
-        return FileManagement.GetFileInfo(
-            client,
-            groupCode = contact.id,
-            busId = busId,
-            fileId = id
-        ).sendAndExpect(bot)
-            .toResult("AbsoluteFileImpl.exists", checkResp = false)
+        return bot.network.sendAndExpect(
+            FileManagement.GetFileInfo(
+                client,
+                groupCode = contact.id,
+                busId = busId,
+                fileId = id
+            )
+        ).toResult("AbsoluteFileImpl.exists", checkResp = false)
             .getOrThrow()
             .fileInfo != null
     }
@@ -78,9 +78,20 @@ internal class AbsoluteFileImpl(
         if (folder.absolutePath == this.parentOrRoot.absolutePath) return true
         checkPermission("moveTo")
 
-        val result = FileManagement.MoveFile(client, contact.id, busId, id, parent.idOrRoot, folder.idOrRoot)
-            .sendAndExpect(bot).toResult("AbsoluteFileImpl.moveTo", checkResp = false)
-            .getOrThrow()
+
+        val result =
+            bot.network.sendAndExpect(
+                FileManagement.MoveFile(
+                    client,
+                    contact.id,
+                    busId,
+                    id,
+                    parent.idOrRoot,
+                    folder.idOrRoot
+                )
+            )
+                .toResult("AbsoluteFileImpl.moveTo", checkResp = false)
+                .getOrThrow()
 
         return when (result.int32RetCode) {
             -36 -> throwPermissionDeniedException("moveTo")
@@ -103,14 +114,14 @@ internal class AbsoluteFileImpl(
         // java.lang.IllegalStateException: Failed AbsoluteFileImpl.getUrl, result=-303, msg=param error: bus_id
         // java.lang.IllegalStateException: Failed AbsoluteFileImpl.getUrl, result=-103, msg=GetFileAttrAction file not exist
 
-        val resp = FileManagement.RequestDownload(
-            client,
-            groupCode = contact.id,
-            busId = busId,
-            fileId = id
-        ).sendAndExpect(bot)
-            .toResult("AbsoluteFileImpl.getUrl")
-            .getOrElse { return null }
+        val resp = bot.network.sendAndExpect(
+            FileManagement.RequestDownload(
+                client,
+                groupCode = contact.id,
+                busId = busId,
+                fileId = id
+            )
+        ).toResult("AbsoluteFileImpl.getUrl").getOrElse { return null }
 
 
         return "http://${resp.downloadIp}/ftn_handler/${resp.downloadUrl.toUHexString("")}/?fname=" +
@@ -133,8 +144,7 @@ internal class AbsoluteFileImpl(
     override fun toString(): String = "AbsoluteFile(name=$name, absolutePath=$absolutePath, id=$id)"
 
     override suspend fun refreshed(): AbsoluteFile? {
-        val result = FileManagement.GetFileInfo(client, contact.id, id, busId)
-            .sendAndExpect(bot)
+        val result = bot.network.sendAndExpect(FileManagement.GetFileInfo(client, contact.id, id, busId))
             .toResult("AbsoluteFile.refreshed")
             .getOrNull()?.fileInfo
             ?: return null
diff --git a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt
index d68864553..6b4bffa0a 100644
--- a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
@@ -27,7 +27,6 @@ import net.mamoe.mirai.internal.network.protocol
 import net.mamoe.mirai.internal.network.protocol.data.proto.*
 import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
 import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.utils.FileSystem
 import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
 import net.mamoe.mirai.utils.*
@@ -113,12 +112,15 @@ internal class AbsoluteFolderImpl(
             return flow {
                 var index = 0
                 while (true) {
-                    val list = FileManagement.GetFileList(
-                        client,
-                        groupCode = contact.id,
-                        folderId = folderId,
-                        startIndex = index
-                    ).sendAndExpect(client.bot).toResult("AbsoluteFolderImpl.getFilesFlow").getOrThrow()
+                    val list =
+                        client.bot.network.sendAndExpect(
+                            FileManagement.GetFileList(
+                                client,
+                                groupCode = contact.id,
+                                folderId = folderId,
+                                startIndex = index
+                            )
+                        ).toResult("AbsoluteFolderImpl.getFilesFlow").getOrThrow()
                     index += list.itemList.size
 
                     if (list.int32RetCode != 0) return@flow
@@ -139,13 +141,16 @@ internal class AbsoluteFolderImpl(
             // TODO: 12/10/2021 checkPermission for AbsoluteFolderImpl.upload
 
             content.withAutoClose {
-                val resp = FileManagement.RequestUpload(
-                    folder.client,
-                    groupCode = folder.contact.id,
-                    folderId = folder.id,
-                    resource = content,
-                    filename = filepath
-                ).sendAndExpect(folder.bot).toResult("AbsoluteFolderImpl.upload").getOrThrow()
+                val resp =
+                    folder.bot.network.sendAndExpect(
+                        FileManagement.RequestUpload(
+                            folder.client,
+                            groupCode = folder.contact.id,
+                            folderId = folder.id,
+                            resource = content,
+                            filename = filepath
+                        )
+                    ).toResult("AbsoluteFolderImpl.upload").getOrThrow()
 
                 when (resp.int32RetCode) {
                     -36 -> folder.throwPermissionDeniedException("uploadNewFile")
@@ -254,12 +259,14 @@ internal class AbsoluteFolderImpl(
             var index = 0
             while (true) {
                 val list = runBlocking {
-                    FileManagement.GetFileList(
-                        client,
-                        groupCode = contact.id,
-                        folderId = id,
-                        startIndex = index
-                    ).sendAndExpect(bot)
+                    bot.network.sendAndExpect(
+                        FileManagement.GetFileList(
+                            client,
+                            groupCode = contact.id,
+                            folderId = id,
+                            startIndex = index
+                        )
+                    )
                 }.toResult("AbsoluteFolderImpl.getFilesFlow").getOrThrow()
                 index += list.itemList.size
 
@@ -307,8 +314,8 @@ internal class AbsoluteFolderImpl(
 
         // server only support nesting depth level of 1 so we don't need to check the name
 
-        val result = FileManagement.CreateFolder(client, contact.id, this.id, name)
-            .sendAndExpect(bot).toResult("AbsoluteFolderImpl.mkdir", checkResp = false)
+        val result = bot.network.sendAndExpect(FileManagement.CreateFolder(client, contact.id, this.id, name))
+            .toResult("AbsoluteFolderImpl.mkdir", checkResp = false)
             .getOrThrow() // throw protocol errors
 
         /*
@@ -389,7 +396,7 @@ internal class AbsoluteFolderImpl(
 
         if (!deep) return null
 
-        return folders().map { it.resolveFileById(id, deep) }.firstOrNull{ it != null }
+        return folders().map { it.resolveFileById(id, deep) }.firstOrNull { it != null }
     }
 
     override suspend fun resolveFiles(path: String): Flow<AbsoluteFile> {
diff --git a/mirai-core/src/commonMain/kotlin/contact/file/AbstractAbsoluteFileFolder.kt b/mirai-core/src/commonMain/kotlin/contact/file/AbstractAbsoluteFileFolder.kt
index 1eba1ca73..328e01615 100644
--- a/mirai-core/src/commonMain/kotlin/contact/file/AbstractAbsoluteFileFolder.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/file/AbstractAbsoluteFileFolder.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
@@ -19,7 +19,6 @@ import net.mamoe.mirai.contact.file.AbsoluteFolder
 import net.mamoe.mirai.internal.asQQAndroidBot
 import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
 import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.utils.FileSystem
 import net.mamoe.mirai.utils.cast
 
@@ -72,11 +71,13 @@ internal abstract class AbstractAbsoluteFileFolder(
         parentOrFail()
         checkPermission("renameTo")
 
-        val result = if (isFile) {
-            FileManagement.RenameFile(client, contact.id, busId, id, parent.idOrRoot, newName)
-        } else {
-            FileManagement.RenameFolder(client, contact.id, id, newName)
-        }.sendAndExpect(bot)
+        val result = bot.network.sendAndExpect(
+            if (isFile) {
+                FileManagement.RenameFile(client, contact.id, busId, id, parent.idOrRoot, newName)
+            } else {
+                FileManagement.RenameFolder(client, contact.id, id, newName)
+            }
+        )
 
         result.toResult("AbstractAbsoluteFileFolder.renameTo") {
             when (it) {
@@ -95,10 +96,10 @@ internal abstract class AbstractAbsoluteFileFolder(
     suspend fun delete(): Boolean {
         checkPermission("delete")
         val result = if (isFile) {
-            FileManagement.DeleteFile(client, contact.id, busId, id, parent.idOrRoot).sendAndExpect(bot)
+            bot.network.sendAndExpect(FileManagement.DeleteFile(client, contact.id, busId, id, parent.idOrRoot))
         } else {
             // natively 'recursive'
-            FileManagement.DeleteFolder(client, contact.id, id).sendAndExpect(bot)
+            bot.network.sendAndExpect(FileManagement.DeleteFolder(client, contact.id, id))
         }.toResult("AbstractAbsoluteFileFolder.delete", checkResp = false).getOrThrow()
 
         return when (result.int32RetCode) {
diff --git a/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt b/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt
index 2ee2e6487..e85faf5a4 100644
--- a/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
@@ -25,7 +25,6 @@ import net.mamoe.mirai.internal.contact.FriendImpl
 import net.mamoe.mirai.internal.message.toMessageChainOnline
 import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
 import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetRoamMsgReq
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.message.data.MessageChain
 import net.mamoe.mirai.utils.*
 import java.util.stream.Stream
@@ -123,15 +122,17 @@ internal class RoamingMessagesImplFriend(
         lastMessageTime: Long,
         random: Long
     ): MessageSvcPbGetRoamMsgReq.Response {
-        return MessageSvcPbGetRoamMsgReq.createForFriend(
-            client = contact.bot.client,
-            uin = contact.id,
-            timeStart = timeStart,
-            lastMsgTime = lastMessageTime,
-            random = random,
-            maxCount = 1000,
-            sig = byteArrayOf(),
-            pwd = byteArrayOf()
-        ).sendAndExpect(contact.bot).value.check()
+        return contact.bot.network.sendAndExpect(
+            MessageSvcPbGetRoamMsgReq.createForFriend(
+                client = contact.bot.client,
+                uin = contact.id,
+                timeStart = timeStart,
+                lastMsgTime = lastMessageTime,
+                random = random,
+                maxCount = 1000,
+                sig = byteArrayOf(),
+                pwd = byteArrayOf()
+            )
+        ).value.check()
     }
 }
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/message/FileMessageImpl.kt b/mirai-core/src/commonMain/kotlin/message/FileMessageImpl.kt
index bbadd5e35..8e1b3e76c 100644
--- a/mirai-core/src/commonMain/kotlin/message/FileMessageImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/message/FileMessageImpl.kt
@@ -28,7 +28,6 @@ import net.mamoe.mirai.internal.contact.file.resolved
 import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x6d8.GetFileListRspBody
 import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
 import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.message.data.FileMessage
 import net.mamoe.mirai.utils.cast
 import kotlin.contracts.contract
@@ -51,8 +50,8 @@ internal data class FileMessageImpl(
         get() = busId
 
     override suspend fun toAbsoluteFile(contact: FileSupported): AbsoluteFile? {
-        val result = FileManagement.GetFileInfo(contact.bot.asQQAndroidBot().client, contact.id, id, busId)
-            .sendAndExpect(contact.bot.asQQAndroidBot())
+        val result = contact.bot.asQQAndroidBot().network
+            .sendAndExpect(FileManagement.GetFileInfo(contact.bot.asQQAndroidBot().client, contact.id, id, busId))
             .toResult("FileMessage.toAbsoluteFile").getOrThrow()
         if (result.fileInfo == null) return null
 
diff --git a/mirai-core/src/commonMain/kotlin/message/InternalImageProtocolImpl.kt b/mirai-core/src/commonMain/kotlin/message/InternalImageProtocolImpl.kt
index 9ae0acf0d..e97db1384 100644
--- a/mirai-core/src/commonMain/kotlin/message/InternalImageProtocolImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/message/InternalImageProtocolImpl.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
@@ -18,7 +18,6 @@ import net.mamoe.mirai.internal.asQQAndroidBot
 import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352
 import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
 import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.utils.ImagePatcher
 import net.mamoe.mirai.internal.utils.ImagePatcher.Companion.withCache
 import net.mamoe.mirai.message.data.Image
@@ -66,18 +65,20 @@ internal class InternalImageProtocolImpl : InternalImageProtocol {
             width: Int,
             height: Int
         ): Boolean {
-            val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
-                bot.client,
-                uin = bot.id,
-                groupCode = context.id,
-                md5 = md5,
-                size = size,
-                filename = "${md5.toUHexString("")}.${type.formatName}",
-                picWidth = width,
-                picHeight = height,
-                picType = getIdByImageType(type),
-                originalPic = 1
-            ).sendAndExpect(bot)
+            val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect(
+                ImgStore.GroupPicUp(
+                    bot.client,
+                    uin = bot.id,
+                    groupCode = context.id,
+                    md5 = md5,
+                    size = size,
+                    filename = "${md5.toUHexString("")}.${type.formatName}",
+                    picWidth = width,
+                    picHeight = height,
+                    picType = getIdByImageType(type),
+                    originalPic = 1
+                )
+            )
 
             return response is ImgStore.GroupPicUp.Response.FileExists
         }
@@ -93,22 +94,24 @@ internal class InternalImageProtocolImpl : InternalImageProtocol {
             width: Int,
             height: Int
         ): Boolean {
-            val resp = LongConn.OffPicUp(
-                bot.client,
-                Cmd0x352.TryUpImgReq(
-                    buType = 1,
-                    srcUin = bot.id,
-                    dstUin = context.id,
-                    fileMd5 = md5,
-                    fileSize = size,
-                    imgWidth = width,
-                    imgHeight = height,
-                    imgType = getIdByImageType(type),
-                    fileName = "${md5.toUHexString("")}.${type.formatName}",
-                    imgOriginal = true,
-                    buildVer = bot.client.buildVer,
-                ),
-            ).sendAndExpect<LongConn.OffPicUp.Response>(bot)
+            val resp = bot.network.sendAndExpect(
+                LongConn.OffPicUp(
+                    bot.client,
+                    Cmd0x352.TryUpImgReq(
+                        buType = 1,
+                        srcUin = bot.id,
+                        dstUin = context.id,
+                        fileMd5 = md5,
+                        fileSize = size,
+                        imgWidth = width,
+                        imgHeight = height,
+                        imgType = getIdByImageType(type),
+                        fileName = "${md5.toUHexString("")}.${type.formatName}",
+                        imgOriginal = true,
+                        buildVer = bot.client.buildVer,
+                    ),
+                )
+            )
 
             return resp is LongConn.OffPicUp.Response.FileExists
         }
@@ -124,18 +127,20 @@ internal class InternalImageProtocolImpl : InternalImageProtocol {
             width: Int,
             height: Int
         ): Boolean {
-            val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
-                bot.client,
-                uin = bot.id,
-                groupCode = 1,
-                md5 = md5,
-                size = size,
-                filename = "${md5.toUHexString("")}.${type.formatName}",
-                picWidth = width,
-                picHeight = height,
-                picType = getIdByImageType(type),
-                originalPic = 1
-            ).sendAndExpect(bot)
+            val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect(
+                ImgStore.GroupPicUp(
+                    bot.client,
+                    uin = bot.id,
+                    groupCode = 1,
+                    md5 = md5,
+                    size = size,
+                    filename = "${md5.toUHexString("")}.${type.formatName}",
+                    picWidth = width,
+                    picHeight = height,
+                    picType = getIdByImageType(type),
+                    originalPic = 1
+                )
+            )
 
             return response is ImgStore.GroupPicUp.Response.FileExists
         }
diff --git a/mirai-core/src/commonMain/kotlin/message/MultiMsgUploader.kt b/mirai-core/src/commonMain/kotlin/message/MultiMsgUploader.kt
index 25c837337..c90075b8c 100644
--- a/mirai-core/src/commonMain/kotlin/message/MultiMsgUploader.kt
+++ b/mirai-core/src/commonMain/kotlin/message/MultiMsgUploader.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
@@ -194,14 +194,14 @@ internal open class MultiMsgUploader(
     open suspend fun uploadAndReturnResId(): String {
         val data = toMessageValidationData()
 
-        val response = client.bot.network.run {
+        val response = client.bot.network.sendAndExpect(
             MultiMsg.ApplyUp.createForGroup(
                 buType = if (isLong) 1 else 2,
                 client = client,
                 messageData = data,
                 dstUin = handler.targetUin
-            ).sendAndExpect()
-        }
+            ), 5000, 2
+        )
 
         val resId: String
         when (response) {
diff --git a/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt b/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt
index 0c1bee55a..47514a260 100644
--- a/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt
+++ b/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt
@@ -28,7 +28,6 @@ import net.mamoe.mirai.internal.contact.info.GroupInfoImpl
 import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
 import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
 import net.mamoe.mirai.internal.contact.toMiraiFriendInfo
-import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.component.ComponentKey
 import net.mamoe.mirai.internal.network.component.ComponentStorage
 import net.mamoe.mirai.internal.network.isValid
@@ -39,7 +38,6 @@ import net.mamoe.mirai.internal.network.protocol.data.jce.isValid
 import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement
 import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
 import net.mamoe.mirai.internal.network.protocol.packet.list.StrangerList
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.utils.MiraiLogger
 import net.mamoe.mirai.utils.info
 import net.mamoe.mirai.utils.retryCatching
@@ -140,9 +138,11 @@ internal class ContactUpdaterImpl(
             var count = 0
             var total: Short
             while (true) {
-                val data = FriendList.GetFriendGroupList(
-                    bot.client, count, 150, 0, 0
-                ).sendAndExpect(bot, timeoutMillis = 5000, retry = 2)
+                val data = bot.network.sendAndExpect(
+                    FriendList.GetFriendGroupList(bot.client, count, 150, 0, 0),
+                    timeout = 5000,
+                    attempts = 2
+                )
 
                 total = data.totalFriendCount
 
@@ -163,9 +163,11 @@ internal class ContactUpdaterImpl(
             logger.info { "Loaded ${list.size} friends from local cache." }
 
             // For sync bot nick
-            FriendList.GetFriendGroupList(
-                bot.client, 0, 1, 0, 0
-            ).sendAndExpect<Packet>(bot)
+            bot.network.sendAndExpect(
+                FriendList.GetFriendGroupList(
+                    bot.client, 0, 1, 0, 0
+                )
+            )
 
             list
         } else {
@@ -225,8 +227,12 @@ internal class ContactUpdaterImpl(
         }
         var currentCount = 0
         logger.info { "Start loading stranger list..." }
-        val response = StrangerList.GetStrangerList(bot.client)
-            .sendAndExpect(bot, timeoutMillis = 5000, retry = 2)
+
+        val response = bot.network.sendAndExpect(
+            StrangerList.GetStrangerList(bot.client),
+            timeout = 5000,
+            attempts = 2
+        )
 
         if (response.result == 0) {
             response.strangerList.forEach {
@@ -245,11 +251,12 @@ internal class ContactUpdaterImpl(
         if (initGroupOk) {
             return
         }
-        TroopManagement.GetTroopConfig(bot.client).sendAndExpect(bot)
+
+        bot.network.sendAndExpect(TroopManagement.GetTroopConfig(bot.client))
 
         logger.info { "Start loading group list..." }
-        val troopListData = FriendList.GetTroopListSimplify(bot.client)
-            .sendAndExpect(bot, retry = 5)
+
+        val troopListData = bot.network.sendAndExpect(FriendList.GetTroopListSimplify(bot.client), attempts = 5)
 
         val semaphore = Semaphore(30)
 
diff --git a/mirai-core/src/commonMain/kotlin/network/components/HeartbeatProcessor.kt b/mirai-core/src/commonMain/kotlin/network/components/HeartbeatProcessor.kt
index 811d8fdac..0d1280c38 100644
--- a/mirai-core/src/commonMain/kotlin/network/components/HeartbeatProcessor.kt
+++ b/mirai-core/src/commonMain/kotlin/network/components/HeartbeatProcessor.kt
@@ -13,7 +13,6 @@ import net.mamoe.mirai.internal.network.component.ComponentKey
 import net.mamoe.mirai.internal.network.handler.NetworkHandler
 import net.mamoe.mirai.internal.network.protocol.packet.login.Heartbeat
 import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 
 internal interface HeartbeatProcessor {
 
@@ -32,19 +31,19 @@ internal interface HeartbeatProcessor {
 internal class HeartbeatProcessorImpl : HeartbeatProcessor {
     @Throws(Exception::class)
     override suspend fun doStatHeartbeatNow(networkHandler: NetworkHandler) {
-        StatSvc.SimpleGet(networkHandler.context.bot.client).sendAndExpect(
-            networkHandler,
-            timeoutMillis = networkHandler.context[SsoProcessorContext].configuration.heartbeatTimeoutMillis,
-            retry = 2
+        networkHandler.sendAndExpect(
+            StatSvc.SimpleGet(networkHandler.context.bot.client),
+            timeout = networkHandler.context[SsoProcessorContext].configuration.heartbeatTimeoutMillis,
+            attempts = 2
         )
     }
 
     @Throws(Exception::class)
     override suspend fun doAliveHeartbeatNow(networkHandler: NetworkHandler) {
-        Heartbeat.Alive(networkHandler.context.bot.client).sendAndExpect(
-            networkHandler,
-            timeoutMillis = networkHandler.context[SsoProcessorContext].configuration.heartbeatTimeoutMillis,
-            retry = 2
+        networkHandler.sendAndExpect(
+            Heartbeat.Alive(networkHandler.context.bot.client),
+            timeout = networkHandler.context[SsoProcessorContext].configuration.heartbeatTimeoutMillis,
+            attempts = 2
         )
     }
 
diff --git a/mirai-core/src/commonMain/kotlin/network/components/KeyRefreshProcessor.kt b/mirai-core/src/commonMain/kotlin/network/components/KeyRefreshProcessor.kt
index d1370e0ec..09166176f 100644
--- a/mirai-core/src/commonMain/kotlin/network/components/KeyRefreshProcessor.kt
+++ b/mirai-core/src/commonMain/kotlin/network/components/KeyRefreshProcessor.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.network.components
@@ -13,7 +13,6 @@ import kotlinx.coroutines.*
 import net.mamoe.mirai.internal.network.component.ComponentKey
 import net.mamoe.mirai.internal.network.handler.NetworkHandler
 import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin15
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.network.LoginFailedException
 import net.mamoe.mirai.utils.MiraiLogger
 import net.mamoe.mirai.utils.info
@@ -66,6 +65,6 @@ internal class KeyRefreshProcessorImpl(
     }
 
     override suspend fun refreshKeysNow(handler: NetworkHandler) {
-        WtLogin15(handler.context[SsoProcessor].client).sendAndExpect(handler)
+        handler.sendAndExpect(WtLogin15(handler.context[SsoProcessor].client))
     }
 }
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/network/components/MessageSvcSyncer.kt b/mirai-core/src/commonMain/kotlin/network/components/MessageSvcSyncer.kt
index 6712ec697..c9094c051 100644
--- a/mirai-core/src/commonMain/kotlin/network/components/MessageSvcSyncer.kt
+++ b/mirai-core/src/commonMain/kotlin/network/components/MessageSvcSyncer.kt
@@ -18,7 +18,6 @@ import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.network.component.ComponentKey
 import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc
 import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.utils.MiraiLogger
 import net.mamoe.mirai.utils.addNameHierarchically
 import net.mamoe.mirai.utils.childScope
@@ -64,7 +63,7 @@ internal class MessageSvcSyncerImpl(
                     it.bot == this@MessageSvcSyncerImpl.bot
                 }
             }
-            MessageSvcPbGetMsg(bot.client, MsgSvc.SyncFlag.START, null).sendAndExpect(bot)
+            bot.network.sendAndExpect(MessageSvcPbGetMsg(bot.client, MsgSvc.SyncFlag.START, null))
         } ?: error("timeout syncing friend message history.")
         logger.info { "Syncing friend message history: Success." }
     }
diff --git a/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt b/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt
index d240b1b33..9ae6b18f1 100644
--- a/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt
+++ b/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt
@@ -26,7 +26,6 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin10
 import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin2
 import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin20
 import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin9
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.network.*
 import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
 import net.mamoe.mirai.utils.LoginSolver
@@ -148,7 +147,9 @@ internal class SsoProcessorImpl(
     }
 
     private suspend fun registerClientOnline(handler: NetworkHandler): StatSvc.Register.Response {
-        return StatSvc.Register.online(client).sendAndExpect(handler).also { registerResp = it }
+        return handler.sendAndExpect(StatSvc.Register.online(client)).also {
+            registerResp = it
+        }
     }
 
     override suspend fun logout(handler: NetworkHandler) {
@@ -179,7 +180,8 @@ internal class SsoProcessorImpl(
         protected val bot get() = context.bot
         protected val logger get() = bot.logger
 
-        protected suspend fun <R : Packet?> OutgoingPacketWithRespType<R>.sendAndExpect(): R = sendAndExpect(handler)
+        protected suspend fun <R : Packet?> OutgoingPacketWithRespType<R>.sendAndExpect(): R =
+            handler.sendAndExpect(this)
 
         abstract suspend fun doLogin()
     }
@@ -300,7 +302,7 @@ internal class SsoProcessorImpl(
 
     private inner class FastLoginImpl(handler: NetworkHandler) : LoginStrategy(handler) {
         override suspend fun doLogin() {
-            val login10 = WtLogin10(client).sendAndExpect(handler)
+            val login10 = handler.sendAndExpect(WtLogin10(client))
             check(login10 is LoginPacketResponse.Success) { "Fast login failed: $login10" }
         }
     }
diff --git a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandler.kt
index 95e0358c4..4263845dd 100644
--- a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandler.kt
@@ -125,6 +125,12 @@ internal interface NetworkHandler : CoroutineScope {
     suspend fun resumeConnection()
 
 
+    suspend fun <P : Packet?> sendAndExpect(
+        packet: OutgoingPacketWithRespType<P>,
+        timeout: Long = 5000,
+        attempts: Int = 2
+    ): P
+
     /**
      * Sends [packet], suspends and expects to receive a response from the server.
      *
@@ -133,7 +139,8 @@ internal interface NetworkHandler : CoroutineScope {
      *
      * @param attempts ranges `1..INFINITY`
      */
-    suspend fun sendAndExpect(packet: OutgoingPacket, timeout: Long = 5000, attempts: Int = 2): Packet?
+    suspend fun <P : Packet?> sendAndExpect(packet: OutgoingPacket, timeout: Long = 5000, attempts: Int = 2): P
+
 
     /**
      * Sends [packet] and does not expect any response.
@@ -154,32 +161,6 @@ internal interface NetworkHandler : CoroutineScope {
     ///////////////////////////////////////////////////////////////////////////
     // compatibility
     ///////////////////////////////////////////////////////////////////////////
-
-    /**
-     * @suppress This is for compatibility with old code. Use [sendWithoutExpect] without extension receiver instead.
-     */
-    suspend fun OutgoingPacket.sendWithoutExpect(
-        antiCollisionParam: Any? = null,
-    ) = this@NetworkHandler.sendWithoutExpect(this)
-
-    /**
-     * @suppress This is for compatibility with old code. Use [sendAndExpect] without extension receiver instead.
-     */
-    @Suppress("UNCHECKED_CAST")
-    suspend fun <R> OutgoingPacket.sendAndExpect(
-        timeoutMillis: Long = 5000,
-        retry: Int = 2,
-        antiCollisionParam: Any? = null, // signature collision
-    ): R = sendAndExpect(this, timeoutMillis, retry) as R
-
-    /**
-     * @suppress This is for compatibility with old code. Use [sendAndExpect] without extension receiver instead.
-     */
-    @Suppress("UNCHECKED_CAST")
-    suspend fun <R : Packet?> OutgoingPacketWithRespType<R>.sendAndExpect(
-        timeoutMillis: Long = 5000,
-        retry: Int = 2,
-    ): R = sendAndExpect(this, timeoutMillis, retry) as R
 }
 
 internal val NetworkHandler.logger: MiraiLogger get() = context.logger
diff --git a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt
index ce04e7319..838ee05d5 100644
--- a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt
+++ b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt
@@ -21,6 +21,7 @@ import net.mamoe.mirai.internal.network.handler.selector.NetworkHandlerSelector
 import net.mamoe.mirai.internal.network.handler.state.StateObserver
 import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
+import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
 import net.mamoe.mirai.internal.utils.SingleEntrantLock
 import net.mamoe.mirai.internal.utils.fromMiraiLogger
 import net.mamoe.mirai.internal.utils.subLogger
@@ -88,7 +89,7 @@ internal abstract class NetworkHandlerSupport(
         }
     }
 
-    final override suspend fun sendAndExpect(packet: OutgoingPacket, timeout: Long, attempts: Int): Packet? {
+    final override suspend fun <P : Packet?> sendAndExpect(packet: OutgoingPacket, timeout: Long, attempts: Int): P {
         require(attempts >= 1) { "attempts must be at least 1." }
         val listener = PacketListener(packet.commandName, packet.sequenceId)
         packetListeners.add(listener)
@@ -98,9 +99,10 @@ internal abstract class NetworkHandlerSupport(
                     context[PacketLoggingStrategy].logSent(logger, packet)
                     sendPacketImpl(packet)
                     try {
+                        @Suppress("UNCHECKED_CAST")
                         return withTimeout(timeout) {
                             listener.result.await()
-                        }
+                        } as P
                     } catch (e: TimeoutCancellationException) {
                         collectException(e)
                     }
@@ -117,6 +119,12 @@ internal abstract class NetworkHandlerSupport(
         }
     }
 
+    final override suspend fun <P : Packet?> sendAndExpect(
+        packet: OutgoingPacketWithRespType<P>,
+        timeout: Long,
+        attempts: Int
+    ): P = sendAndExpect(packet as OutgoingPacket, timeout, attempts)
+
     final override suspend fun sendWithoutExpect(packet: OutgoingPacket) {
         context[PacketLoggingStrategy].logSent(logger, packet)
         sendPacketImpl(packet)
diff --git a/mirai-core/src/commonMain/kotlin/network/handler/selector/SelectorNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/handler/selector/SelectorNetworkHandler.kt
index 0ecf3fab2..aebc490ab 100644
--- a/mirai-core/src/commonMain/kotlin/network/handler/selector/SelectorNetworkHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/network/handler/selector/SelectorNetworkHandler.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.network.handler.selector
@@ -13,10 +13,12 @@ import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.channels.ReceiveChannel
 import kotlinx.coroutines.isActive
+import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.handler.NetworkHandler
 import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
 import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
+import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
 import net.mamoe.mirai.utils.addNameHierarchically
 import net.mamoe.mirai.utils.childScope
 import kotlin.coroutines.CoroutineContext
@@ -70,9 +72,15 @@ internal open class SelectorNetworkHandler<out H : NetworkHandler>(
         instance() // the selector will resume connection for us.
     }
 
-    override suspend fun sendAndExpect(packet: OutgoingPacket, timeout: Long, attempts: Int) =
+    override suspend fun <P : Packet?> sendAndExpect(packet: OutgoingPacket, timeout: Long, attempts: Int): P =
         instance().sendAndExpect(packet, timeout, attempts)
 
+    override suspend fun <P : Packet?> sendAndExpect(
+        packet: OutgoingPacketWithRespType<P>,
+        timeout: Long,
+        attempts: Int
+    ): P = instance().sendAndExpect(packet, timeout, attempts)
+
     override suspend fun sendWithoutExpect(packet: OutgoingPacket) = instance().sendWithoutExpect(packet)
     override fun close(cause: Throwable?) {
         if (cause is NetworkException && cause.recoverable) {
diff --git a/mirai-core/src/commonMain/kotlin/network/notice/NewContactSupport.kt b/mirai-core/src/commonMain/kotlin/network/notice/NewContactSupport.kt
index c8f86c23d..6e4d3ccb7 100644
--- a/mirai-core/src/commonMain/kotlin/network/notice/NewContactSupport.kt
+++ b/mirai-core/src/commonMain/kotlin/network/notice/NewContactSupport.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.network.notice
@@ -25,7 +25,6 @@ import net.mamoe.mirai.internal.getGroupByUin
 import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum
 import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
 import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 
 internal interface NewContactSupport { // can be a marker interface when context receivers are available.
 
@@ -89,9 +88,10 @@ internal interface NewContactSupport { // can be a marker interface when context
     }
 
     private suspend fun QQAndroidBot.getNewGroup(groupCode: Long): GroupImpl? {
-        val troopNum = FriendList.GetTroopListSimplify(client)
-            .sendAndExpect(network, timeoutMillis = 10_000, retry = 5)
-            .groups.firstOrNull { it.groupCode == groupCode } ?: return null
+        val troopNum = network.sendAndExpect(
+            FriendList.GetTroopListSimplify(client),
+            timeout = 10_000, attempts = 5
+        ).groups.firstOrNull { it.groupCode == groupCode } ?: return null
 
         return getNewGroup(troopNum)
     }
diff --git a/mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt b/mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt
index fe07419f0..d746a11b4 100644
--- a/mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt
+++ b/mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.network.notice
@@ -106,14 +106,10 @@ internal class UnconsumedNoticesAlerter(
                 // 前 4 byte 是群号
             }
             84, 87 -> { // 请求入群验证 和 被邀请入群
-                bot.network.run {
-                    NewContact.SystemMsgNewGroup(bot.client).sendWithoutExpect()
-                }
+                bot.network.sendWithoutExpect(NewContact.SystemMsgNewGroup(bot.client))
             }
             187 -> { // 请求加好友验证
-                bot.network.run {
-                    NewContact.SystemMsgNewFriend(bot.client).sendWithoutExpect()
-                }
+                bot.network.sendWithoutExpect(NewContact.SystemMsgNewFriend(bot.client))
             }
             else -> {
                 logger.debug { "unknown PbGetMsg type ${data.msgHead.msgType}, data=${data.msgBody.msgContent.toUHexString()}" }
diff --git a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt
index 3521ed24e..c06029109 100644
--- a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt
+++ b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.network.notice.priv
@@ -35,7 +35,6 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x44.Subms
 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0xb3.SubMsgType0xb3
 import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact
 import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList.GetFriendGroupList
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.utils.io.ProtoBuf
 import net.mamoe.mirai.internal.utils.io.serialization.loadAs
 import net.mamoe.mirai.internal.utils.structureToString
@@ -92,9 +91,7 @@ internal class FriendNoticeProcessor(
             val nick = fromNick.ifEmpty { authNick }.ifEmpty { pbNick }
             collect(StrangerAddEvent(bot.addNewStranger(StrangerInfoImpl(id, nick, fromGroup)) ?: return))
             //同时需要请求好友验证消息(有新请求需要同意)
-            bot.network.run {
-                NewContact.SystemMsgNewFriend(bot.client).sendWithoutExpect()
-            }
+            bot.network.sendWithoutExpect(NewContact.SystemMsgNewFriend(bot.client))
         }
 
     }
@@ -252,7 +249,8 @@ internal class FriendNoticeProcessor(
             3, 9, 10 -> {
                 if (bot.getFriend(fuin) != null) return
 
-                val response = GetFriendGroupList.forSingleFriend(bot.client, fuin).sendAndExpect(bot)
+
+                val response = bot.network.sendAndExpect(GetFriendGroupList.forSingleFriend(bot.client, fuin))
                 val info = response.friendList.firstOrNull() ?: return
                 collect(
                     FriendAddEvent(bot.addNewFriendAndRemoveStranger(info.toMiraiFriendInfo()) ?: return),
@@ -274,7 +272,7 @@ internal class FriendNoticeProcessor(
             val added = bot.addNewFriendAndRemoveStranger(info) ?: return
             collect(FriendAddEvent(added))
             if (removed != null) collect(StrangerRelationChangeEvent.Friended(removed, added))
-    }
+        }
 
     private fun NoticePipelineContext.handlePrivateNudge(body: Submsgtype0x122.Submsgtype0x122.MsgBody) {
         val action = body.msgTemplParam["action_str"].orEmpty()
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/OutgoingPacket.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/OutgoingPacket.kt
index 44fd5c0f7..5c0f096e6 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/OutgoingPacket.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/OutgoingPacket.kt
@@ -11,12 +11,10 @@ package net.mamoe.mirai.internal.network.protocol.packet
 
 
 import kotlinx.io.core.*
-import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.QQAndroidClient
 import net.mamoe.mirai.internal.network.appClientVersion
 import net.mamoe.mirai.internal.network.components.EcdhInitialPublicKeyUpdater
-import net.mamoe.mirai.internal.network.handler.NetworkHandler
 import net.mamoe.mirai.internal.utils.io.encryptAndWrite
 import net.mamoe.mirai.internal.utils.io.writeHex
 import net.mamoe.mirai.internal.utils.io.writeIntLVPacket
@@ -76,36 +74,6 @@ internal class IncomingPacket private constructor(
     }
 }
 
-@Suppress("UNCHECKED_CAST")
-internal suspend inline fun <E : Packet> OutgoingPacketWithRespType<E>.sendAndExpect(
-    network: NetworkHandler,
-    timeoutMillis: Long = 5000,
-    retry: Int = 2
-): E = network.sendAndExpect(this, timeoutMillis, retry) as E
-
-@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNCHECKED_CAST")
-@kotlin.internal.LowPriorityInOverloadResolution
-internal suspend inline fun <E : Packet> OutgoingPacket.sendAndExpect(
-    network: NetworkHandler,
-    timeoutMillis: Long = 5000,
-    retry: Int = 2
-): E = network.sendAndExpect(this, timeoutMillis, retry) as E
-
-internal suspend inline fun <E : Packet> OutgoingPacketWithRespType<E>.sendAndExpect(
-    bot: QQAndroidBot,
-    timeoutMillis: Long = 5000,
-    retry: Int = 2
-): E = (this@sendAndExpect as OutgoingPacket).sendAndExpect(bot.network, timeoutMillis, retry)
-
-@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNCHECKED_CAST")
-@kotlin.internal.LowPriorityInOverloadResolution
-internal suspend inline fun <E : Packet> OutgoingPacket.sendAndExpect(
-    bot: QQAndroidBot,
-    timeoutMillis: Long = 5000,
-    retry: Int = 2
-): E = bot.network.sendAndExpect(this, timeoutMillis, retry) as E
-
-
 @Suppress("DuplicatedCode")
 internal inline fun <R : Packet?> OutgoingPacketFactory<R>.buildOutgoingUniPacket(
     client: QQAndroidClient,
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NudgePacket.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NudgePacket.kt
index 621b3d7f5..97433b6f0 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NudgePacket.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NudgePacket.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.network.protocol.packet.chat
@@ -16,7 +16,6 @@ import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.QQAndroidClient
 import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0xed3
 import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso
-import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory
 import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
 import net.mamoe.mirai.internal.utils.io.serialization.loadAs
@@ -38,7 +37,7 @@ internal object NudgePacket : OutgoingPacketFactory<NudgePacket.Response>("OidbS
         client: QQAndroidClient,
         nudgeTargetId: Long,
         messageReceiverUin: Long,
-    ): OutgoingPacket = buildOutgoingUniPacket(client) {
+    ) = buildOutgoingUniPacket(client) {
         writeProtoBuf(
             OidbSso.OIDBSSOPkg.serializer(),
             OidbSso.OIDBSSOPkg(
@@ -57,7 +56,7 @@ internal object NudgePacket : OutgoingPacketFactory<NudgePacket.Response>("OidbS
         client: QQAndroidClient,
         messageReceiverGroupCode: Long,
         nudgeTargetId: Long,
-    ): OutgoingPacket = buildOutgoingUniPacket(client) {
+    ) = buildOutgoingUniPacket(client) {
         writeProtoBuf(
             OidbSso.OIDBSSOPkg.serializer(),
             OidbSso.OIDBSSOPkg(
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt
index 56f99592d..d72020d37 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.network.protocol.packet.chat
@@ -24,6 +24,7 @@ import net.mamoe.mirai.internal.network.protocol.data.jce.stUinInfo
 import net.mamoe.mirai.internal.network.protocol.data.proto.*
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory
+import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
 import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
 import net.mamoe.mirai.internal.network.subAppId
 import net.mamoe.mirai.internal.utils.io.serialization.*
@@ -41,7 +42,7 @@ internal class TroopManagement {
             groupCode: Long,
             memberUin: Long,
             timeInSecond: Int
-        ): OutgoingPacket {
+        ): OutgoingPacketWithRespType<Response> {
             require(timeInSecond in 0..30.daysToSeconds)
             return buildOutgoingUniPacket(client) {
                 writeProtoBuf(
@@ -73,7 +74,7 @@ internal class TroopManagement {
         operator fun invoke(
             client: QQAndroidClient,
             groupCode: Long
-        ): OutgoingPacket {
+        ): OutgoingPacketWithRespType<GroupInfoImpl> {
             return buildOutgoingUniPacket(client) {
                 writeProtoBuf(
                     OidbSso.OIDBSSOPkg.serializer(),
@@ -382,7 +383,7 @@ internal class TroopManagement {
             client: QQAndroidClient,
             member: Member,
             newName: String
-        ): OutgoingPacket {
+        ): OutgoingPacketWithRespType<Response> {
             return buildOutgoingUniPacket(client) {
                 writeJceStruct(
                     RequestPacket.serializer(),
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/image/LongConn.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/image/LongConn.kt
index 9d655476a..7b67806cd 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/image/LongConn.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/image/LongConn.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.network.protocol.packet.chat.image
@@ -14,7 +14,6 @@ import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.QQAndroidClient
 import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352
-import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory
 import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
 import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
@@ -24,8 +23,8 @@ internal class LongConn {
 
     internal object OffPicUp : OutgoingPacketFactory<OffPicUp.Response>("LongConn.OffPicUp") {
 
-        operator fun invoke(client: QQAndroidClient, req: Cmd0x352.TryUpImgReq): OutgoingPacket {
-            return buildOutgoingUniPacket(client) {
+        operator fun invoke(client: QQAndroidClient, req: Cmd0x352.TryUpImgReq) =
+            buildOutgoingUniPacket(client) {
                 writeProtoBuf(
                     Cmd0x352.ReqBody.serializer(),
                     Cmd0x352.ReqBody(
@@ -37,7 +36,6 @@ internal class LongConn {
                     )
                 )
             }
-        }
 
 
         //08 01 12 7D 08 00 10 AB E1 9D DF 07 18 00 28 01 32 1C 0A 10 8E C4 9D 72 26 AE 20 C0 5D A2 B6 78 4D 12 B7 3A 10 E9 07 18 86 1F 20 30 28 30 52 25 2F 61 30 30 39 32 64 61 39 2D 64 39 31 38 2D 34 38 31 62 2D 38 34 30 63 2D 33 32 33 64 64 33 39 33 34 35 37 63 5A 25 2F 61 30 30 39 32 64 61 39 2D 64 39 31 38 2D 34 38 31 62 2D 38 34 30 63 2D 33 32 33 64 64 33 39 33 34 35 37 63 60 00 68 80 40 20 01
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbDeleteMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbDeleteMsg.kt
index 21a7128c7..d8769a9b6 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbDeleteMsg.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbDeleteMsg.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
@@ -31,21 +31,21 @@ internal object MessageSvcPbDeleteMsg : OutgoingPacketFactory<Nothing?>("Message
             )
         }
 
-    internal suspend fun delete(bot: QQAndroidBot, messages: List<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,
-                )
-            }
-
-            MessageSvcPbDeleteMsg(bot.client, map).sendWithoutExpect()
+    internal suspend fun delete(bot: QQAndroidBot, messages: List<MsgComm.Msg>) {
+        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,
+            )
         }
 
+        bot.network.sendWithoutExpect(MessageSvcPbDeleteMsg(bot.client, map))
+    }
+
+
     override suspend fun ByteReadPacket.decode(bot: QQAndroidBot) = null
 }
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
index a47cf93eb..3b709032a 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@@ -113,9 +113,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
             //                .warning { "MessageSvcPushNotify: result != 0, result = ${resp.result}, errorMsg=${resp.errmsg}" }
             bot.network.launch(CoroutineName("MessageSvcPushNotify.retry")) {
                 delay(500 + Random.nextLong(0, 1000))
-                bot.network.run {
-                    MessageSvcPbGetMsg(bot.client, syncCookie = null).sendWithoutExpect()
-                }
+                bot.network.sendWithoutExpect(MessageSvcPbGetMsg(bot.client, syncCookie = null))
             }
             return EmptyResponse(bot)
         }
@@ -170,24 +168,24 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
             }
 
             MsgSvc.SyncFlag.START -> {
-                network.run {
+                network.sendAndExpect(
                     MessageSvcPbGetMsg(
                         client,
                         MsgSvc.SyncFlag.CONTINUE,
                         bot.syncController.syncCookie,
-                    ).sendAndExpect()
-                }
+                    ), 5000, 2
+                )
                 return
             }
 
             MsgSvc.SyncFlag.CONTINUE -> {
-                network.run {
+                network.sendAndExpect(
                     MessageSvcPbGetMsg(
                         client,
                         MsgSvc.SyncFlag.CONTINUE,
                         bot.syncController.syncCookie,
-                    ).sendAndExpect()
-                }
+                    ), 5000, 2
+                )
                 return
             }
         }
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.SidExpired.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.SidExpired.kt
index d13a3bd92..02215d62b 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.SidExpired.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.SidExpired.kt
@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 package net.mamoe.mirai.internal.network.protocol.packet.chat.receive
@@ -17,13 +17,12 @@ import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket
 import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
 import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin10
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 
 internal object OnlinePushSidExpired : IncomingPacketFactory<Packet?>("OnlinePush.SidTicketExpired") {
 
     override suspend fun QQAndroidBot.handle(packet: Packet?, sequenceId: Int): OutgoingPacket {
-        WtLogin10(client, mainSigMap = 3554528).sendAndExpect(bot)
-        StatSvc.Register.online(client).sendAndExpect(bot)
+        bot.network.sendAndExpect(WtLogin10(client, mainSigMap = 3554528))
+        bot.network.sendAndExpect(StatSvc.Register.online(client))
         return buildResponseUniPacket(client, sequenceId = sequenceId)
     }
 
diff --git a/mirai-core/src/commonMain/kotlin/utils/ImagePatcher.kt b/mirai-core/src/commonMain/kotlin/utils/ImagePatcher.kt
index 5a4c532ea..6ca66bc7a 100644
--- a/mirai-core/src/commonMain/kotlin/utils/ImagePatcher.kt
+++ b/mirai-core/src/commonMain/kotlin/utils/ImagePatcher.kt
@@ -14,7 +14,6 @@ import net.mamoe.mirai.internal.message.FriendImage
 import net.mamoe.mirai.internal.message.OfflineGroupImage
 import net.mamoe.mirai.internal.network.component.ComponentKey
 import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.utils.ResourceAccessLock
 import net.mamoe.mirai.utils.UnsafeMutableNonNullProperty
 import net.mamoe.mirai.utils.currentTimeMillis
@@ -117,13 +116,15 @@ internal open class ImagePatcher {
 
         val bot = group.bot
 
-        val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
-            bot.client,
-            uin = bot.id,
-            groupCode = group.id,
-            md5 = image.md5,
-            size = 1,
-        ).sendAndExpect(bot)
+        val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect(
+            ImgStore.GroupPicUp(
+                bot.client,
+                uin = bot.id,
+                groupCode = group.id,
+                md5 = image.md5,
+                size = 1,
+            )
+        )
 
         when (response) {
             is ImgStore.GroupPicUp.Response.Failed -> {
@@ -154,13 +155,15 @@ internal open class ImagePatcher {
 
         val bot = group.bot
 
-        val response = ImgStore.GroupPicUp(
-            bot.client,
-            uin = bot.id,
-            groupCode = group.id,
-            md5 = image.md5,
-            size = image.size
-        ).sendAndExpect(bot.network)
+        val response = bot.network.sendAndExpect(
+            ImgStore.GroupPicUp(
+                bot.client,
+                uin = bot.id,
+                groupCode = group.id,
+                md5 = image.md5,
+                size = image.size
+            )
+        )
 
         return OfflineGroupImage(
             imageId = image.imageId,
diff --git a/mirai-core/src/commonMain/kotlin/utils/RemoteFileImpl.kt b/mirai-core/src/commonMain/kotlin/utils/RemoteFileImpl.kt
index fe6949e27..87c5a5c33 100644
--- a/mirai-core/src/commonMain/kotlin/utils/RemoteFileImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/utils/RemoteFileImpl.kt
@@ -26,7 +26,6 @@ import net.mamoe.mirai.internal.network.protocol
 import net.mamoe.mirai.internal.network.protocol.data.proto.*
 import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
 import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult
-import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
 import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
 import net.mamoe.mirai.message.MessageReceipt
 import net.mamoe.mirai.message.data.FileMessage
@@ -222,12 +221,14 @@ internal class RemoteFileImpl(
         return flow {
             var index = 0
             while (true) {
-                val list = FileManagement.GetFileList(
-                    client,
-                    groupCode = contact.id,
-                    folderId = info.id,
-                    startIndex = index
-                ).sendAndExpect(bot).toResult("RemoteFile.listFiles").getOrThrow()
+                val list = bot.network.sendAndExpect(
+                    FileManagement.GetFileList(
+                        client,
+                        groupCode = contact.id,
+                        folderId = info.id,
+                        startIndex = index
+                    )
+                ).toResult("RemoteFile.listFiles").getOrThrow()
                 index += list.itemList.size
 
                 if (list.int32RetCode != 0) return@flow
@@ -275,12 +276,14 @@ internal class RemoteFileImpl(
             private var ended = false
 
             private suspend fun updateItems() {
-                val list = FileManagement.GetFileList(
-                    client,
-                    groupCode = contact.id,
-                    folderId = path,
-                    startIndex = index
-                ).sendAndExpect(bot).toResult("RemoteFile.listFiles").getOrThrow()
+                val list = bot.network.sendAndExpect(
+                    FileManagement.GetFileList(
+                        client,
+                        groupCode = contact.id,
+                        folderId = path,
+                        startIndex = index
+                    )
+                ).toResult("RemoteFile.listFiles").getOrThrow()
                 if (list.int32RetCode != 0 || list.itemList.isEmpty()) {
                     ended = true
                     return
@@ -356,13 +359,15 @@ internal class RemoteFileImpl(
         if (!info.isOperable()) return false
         return when {
             info.isFile -> {
-                FileManagement.DeleteFile(
-                    client,
-                    groupCode = contact.id,
-                    busId = info.busId,
-                    fileId = info.id,
-                    parentFolderId = info.parentFolderId,
-                ).sendAndExpect(bot).toResult("RemoteFile.delete", checkResp = false).getOrThrow().int32RetCode == 0
+                bot.network.sendAndExpect(
+                    FileManagement.DeleteFile(
+                        client,
+                        groupCode = contact.id,
+                        busId = info.busId,
+                        fileId = info.id,
+                        parentFolderId = info.parentFolderId,
+                    )
+                ).toResult("RemoteFile.delete", checkResp = false).getOrThrow().int32RetCode == 0
             }
             //            recursively -> {
             //                this.listFiles().collect { child ->
@@ -372,9 +377,11 @@ internal class RemoteFileImpl(
             //            }
             else -> {
                 // natively 'recursive'
-                FileManagement.DeleteFolder(
-                    client, contact.id, info.id
-                ).sendAndExpect(bot).toResult("RemoteFile.delete").getOrThrow().int32RetCode == 0
+                bot.network.sendAndExpect(
+                    FileManagement.DeleteFolder(
+                        client, contact.id, info.id
+                    )
+                ).toResult("RemoteFile.delete").getOrThrow().int32RetCode == 0
             }
         }
     }
@@ -387,11 +394,13 @@ internal class RemoteFileImpl(
 
         val info = getFileFolderInfo() ?: return false
         if (!info.isOperable()) return false
-        return if (info.isFile) {
-            FileManagement.RenameFile(client, contact.id, info.busId, info.id, info.parentFolderId, normalized)
-        } else {
-            FileManagement.RenameFolder(client, contact.id, info.id, normalized)
-        }.sendAndExpect(bot).toResult("RemoteFile.renameTo", checkResp = false).getOrThrow().int32RetCode == 0
+        return bot.network.sendAndExpect(
+            if (info.isFile) {
+                FileManagement.RenameFile(client, contact.id, info.busId, info.id, info.parentFolderId, normalized)
+            } else {
+                FileManagement.RenameFolder(client, contact.id, info.id, normalized)
+            }
+        ).toResult("RemoteFile.renameTo", checkResp = false).getOrThrow().int32RetCode == 0
     }
 
     /**
@@ -424,10 +433,18 @@ internal class RemoteFileImpl(
         if (!info.isOperable()) return false
         return if (info.isFile) {
             val newParentId = target.parent?.checkIsImpl()?.getIdSmart() ?: return false
-            FileManagement.MoveFile(client, contact.id, info.busId, info.id, info.parentFolderId, newParentId)
-                .sendAndExpect(bot).toResult("RemoteFile.moveTo", checkResp = false).getOrThrow().int32RetCode == 0
+            bot.network.sendAndExpect(
+                FileManagement.MoveFile(
+                    client,
+                    contact.id,
+                    info.busId,
+                    info.id,
+                    info.parentFolderId,
+                    newParentId
+                )
+            ).toResult("RemoteFile.moveTo", checkResp = false).getOrThrow().int32RetCode == 0
         } else {
-            return FileManagement.RenameFolder(client, contact.id, info.id, target.name).sendAndExpect(bot)
+            return bot.network.sendAndExpect(FileManagement.RenameFolder(client, contact.id, info.id, target.name))
                 .toResult("RemoteFile.moveTo", checkResp = false).getOrThrow().int32RetCode == 0
         }
     }
@@ -439,8 +456,8 @@ internal class RemoteFileImpl(
 
         val parentFolderId: String = parent?.getIdSmart() ?: return false
 
-        return FileManagement.CreateFolder(client, contact.id, parentFolderId, this.name)
-            .sendAndExpect(bot).toResult("RemoteFile.mkdir", checkResp = false).getOrThrow().int32RetCode == 0
+        return bot.network.sendAndExpect(FileManagement.CreateFolder(client, contact.id, parentFolderId, this.name))
+            .toResult("RemoteFile.mkdir", checkResp = false).getOrThrow().int32RetCode == 0
     }
 
     private suspend fun upload0(
@@ -449,13 +466,15 @@ internal class RemoteFileImpl(
     ): Oidb0x6d6.UploadFileRspBody? = resource.withAutoClose {
         val parent = parent ?: return null
         val parentInfo = parent.getFileFolderInfo() ?: return null
-        val resp = FileManagement.RequestUpload(
-            client,
-            groupCode = contact.id,
-            folderId = parentInfo.id,
-            resource = resource,
-            filename = this.name
-        ).sendAndExpect(bot).toResult("RemoteFile.upload").getOrThrow()
+        val resp = bot.network.sendAndExpect(
+            FileManagement.RequestUpload(
+                client,
+                groupCode = contact.id,
+                folderId = parentInfo.id,
+                resource = resource,
+                filename = this.name
+            )
+        ).toResult("RemoteFile.upload").getOrThrow()
         if (resp.boolFileExist) {
             return resp
         }
@@ -585,12 +604,14 @@ internal class RemoteFileImpl(
     override suspend fun getDownloadInfo(): RemoteFile.DownloadInfo? {
         val info = getFileFolderInfo() ?: return null
         if (!info.isFile) return null
-        val resp = FileManagement.RequestDownload(
-            client,
-            groupCode = contact.id,
-            busId = info.busId,
-            fileId = info.id
-        ).sendAndExpect(bot).toResult("RemoteFile.getDownloadInfo").getOrThrow()
+        val resp = bot.network.sendAndExpect(
+            FileManagement.RequestDownload(
+                client,
+                groupCode = contact.id,
+                busId = info.busId,
+                fileId = info.id
+            )
+        ).toResult("RemoteFile.getDownloadInfo").getOrThrow()
 
         return RemoteFile.DownloadInfo(
             filename = name,