diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt
index 873725f57..d2b80a21b 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt
@@ -22,7 +22,6 @@ import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
 import net.mamoe.mirai.qqandroid.network.highway.postImage
 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
@@ -505,25 +504,12 @@ internal class GroupImpl(
             }
         }
 
+    @MiraiExperimentalAPI
     override suspend fun quit(): Boolean {
         check(botPermission != MemberPermission.OWNER) { "An owner cannot quit from a owning group" }
         TODO("not implemented")
     }
 
-    override suspend fun recall(source: MessageSource) {
-        if (source.senderId != bot.uin) {
-            checkBotPermissionOperator()
-        }
-
-        source.ensureSequenceIdAvailable()
-
-        bot.network.run {
-            val response = PbMessageSvc.PbMsgWithDraw.Group(bot.client, this@GroupImpl.id, source.sequenceId, source.messageUid.toInt())
-                .sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
-            check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" }
-        }
-    }
-
     @UseExperimental(MiraiExperimentalAPI::class)
     override fun Member(memberInfo: MemberInfo): Member {
         return MemberImpl(
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
index 5c6c89699..7b9c2326a 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
@@ -12,18 +12,17 @@ package net.mamoe.mirai.qqandroid
 import kotlinx.io.core.ByteReadPacket
 import net.mamoe.mirai.BotAccount
 import net.mamoe.mirai.BotImpl
-import net.mamoe.mirai.contact.ContactList
-import net.mamoe.mirai.contact.Group
-import net.mamoe.mirai.contact.QQ
-import net.mamoe.mirai.contact.filteringGetOrNull
+import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.data.AddFriendResult
 import net.mamoe.mirai.data.FriendInfo
 import net.mamoe.mirai.data.GroupInfo
 import net.mamoe.mirai.data.MemberInfo
 import net.mamoe.mirai.message.data.Image
+import net.mamoe.mirai.message.data.MessageSource
 import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
 import net.mamoe.mirai.qqandroid.network.QQAndroidClient
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
 import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
 import net.mamoe.mirai.utils.*
@@ -119,6 +118,21 @@ internal abstract class QQAndroidBotBase constructor(
         TODO("not implemented")
     }
 
+    override suspend fun recall(source: MessageSource) {
+        if (source.senderId != uin) {
+            getGroup(source.groupId).checkBotPermissionOperator()
+        }
+
+        source.ensureSequenceIdAvailable()
+
+        network.run {
+            val response: PbMessageSvc.PbMsgWithDraw.Response =
+                PbMessageSvc.PbMsgWithDraw.Group(bot.client, source.groupId, source.sequenceId, source.messageUid)
+                    .sendAndExpect()
+            check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" }
+        }
+    }
+
     override suspend fun Image.download(): ByteReadPacket {
         TODO("not implemented")
     }
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
index 11b5b9460..24ea3516e 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
@@ -11,8 +11,10 @@
 
 package net.mamoe.mirai
 
+import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
 import kotlinx.io.OutputStream
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.IoBuffer
@@ -23,10 +25,14 @@ import net.mamoe.mirai.data.FriendInfo
 import net.mamoe.mirai.data.GroupInfo
 import net.mamoe.mirai.data.MemberInfo
 import net.mamoe.mirai.message.data.Image
+import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.message.data.MessageSource
 import net.mamoe.mirai.network.BotNetworkHandler
 import net.mamoe.mirai.network.LoginFailedException
 import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.io.transferTo
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.jvm.JvmStatic
 
 /**
@@ -208,12 +214,37 @@ abstract class Bot : CoroutineScope {
 
     // region actions
 
+    /**
+     * 撤回这条消息.
+     *
+     * [Bot] 撤回自己的消息不需要权限.
+     * [Bot] 撤回群员的消息需要管理员权限.
+     *
+     * @throws PermissionDeniedException 当 [Bot] 无权限操作时
+     * @see Bot.recall (扩展函数) 接受参数 [MessageChain]
+     */// source.groupId, source.sequenceId, source.messageUid
+    abstract suspend fun recall(source: MessageSource)
+
+    /**
+     * 撤回这条消息.
+     *
+     * [Bot] 撤回自己的消息不需要权限.
+     * [Bot] 撤回群员的消息需要管理员权限.
+     *
+     * @throws PermissionDeniedException 当 [Bot] 无权限操作时
+     * @see Bot.recall (扩展函数) 接受参数 [MessageChain]
+     * @see 更推荐说
+     */
+    abstract suspend fun recall(groupId: Long, messageSequenceId: Int, messageUid: Int)
+
     @Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING)
+    @MiraiExperimentalAPI("未支持")
     abstract suspend fun Image.downloadAsByteArray(): ByteArray
 
     /**
      * 将图片下载到内存中 (使用 [IoBuffer.Pool])
      */
+    @MiraiExperimentalAPI("未支持")
     abstract suspend fun Image.download(): ByteReadPacket
 
     /**
@@ -222,11 +253,13 @@ abstract class Bot : CoroutineScope {
      * @param message 若需要验证请求时的验证消息.
      * @param remark 好友备注
      */
+    @MiraiExperimentalAPI("未支持")
     abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult
 
     /**
      * 同意来自陌生人的加好友请求
      */
+    @MiraiExperimentalAPI("未支持")
     abstract suspend fun approveFriendAddRequest(id: Long, remark: String?)
 
     // endregion
@@ -252,12 +285,60 @@ abstract class Bot : CoroutineScope {
     /**
      * 需要调用者自行 close [output]
      */
+    @MiraiExperimentalAPI("未支持")
     suspend inline fun Image.downloadTo(output: OutputStream) =
         download().use { input -> input.transferTo(output) }
 
     // endregion
 }
 
+/**
+ * 撤回这条消息.
+ * 根据 [message] 内的 [MessageSource] 进行相关判断.
+ *
+ * [Bot] 撤回自己的消息不需要权限.
+ * [Bot] 撤回群员的消息需要管理员权限.
+ *
+ * @throws PermissionDeniedException 当 [Bot] 无权限操作时
+ * @see Bot.recall
+ */
+@MiraiExperimentalAPI
+suspend inline fun Bot.recall(message: MessageChain) = this.recall(message[MessageSource])
+
+/**
+ * 在一段时间后撤回这条消息.
+ * 将根据 [MessageSource.groupId] 判断消息是群消息还是好友消息.
+ *
+ * @param millis 延迟的时间, 单位为毫秒
+ * @param coroutineContext 额外的 [CoroutineContext]
+ * @see recall
+ */
+fun Bot.recallIn(
+    source: MessageSource,
+    millis: Long,
+    coroutineContext: CoroutineContext = EmptyCoroutineContext
+): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
+    kotlinx.coroutines.delay(millis)
+    recall(source)
+}
+
+/**
+ * 在一段时间后撤回这条消息.
+ *
+ * @param millis 延迟的时间, 单位为毫秒
+ * @param coroutineContext 额外的 [CoroutineContext]
+ * @see recall
+ */
+@MiraiExperimentalAPI
+fun Bot.recallIn(
+    message: MessageChain,
+    millis: Long,
+    coroutineContext: CoroutineContext = EmptyCoroutineContext
+): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
+    kotlinx.coroutines.delay(millis)
+    recall(message)
+}
+
 /**
  * 关闭这个 [Bot], 停止一切相关活动. 所有引用都会被释放.
  *
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt
index 8b1c1ed42..9b36e5cae 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt
@@ -11,10 +11,7 @@
 
 package net.mamoe.mirai.contact
 
-import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.data.MemberInfo
 import net.mamoe.mirai.event.events.*
@@ -22,10 +19,7 @@ import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
 import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
 import net.mamoe.mirai.message.MessageReceipt
 import net.mamoe.mirai.message.data.MessageChain
-import net.mamoe.mirai.message.data.MessageSource
 import net.mamoe.mirai.utils.MiraiExperimentalAPI
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.jvm.JvmName
 
 /**
@@ -151,17 +145,6 @@ interface Group : Contact, CoroutineScope {
     @MiraiExperimentalAPI("还未支持")
     suspend fun quit(): Boolean
 
-    /**
-     * 撤回这条消息.
-     *
-     * [Bot] 撤回自己的消息不需要权限.
-     * [Bot] 撤回群员的消息需要管理员权限.
-     *
-     * @throws PermissionDeniedException 当 [Bot] 无权限操作时
-     * @see Group.recall (扩展函数) 接受参数 [MessageChain]
-     */
-    suspend fun recall(source: MessageSource)
-
     /**
      * 构造一个 [Member].
      * 非特殊情况请不要使用这个函数. 优先使用 [get].
@@ -226,49 +209,6 @@ interface Group : Contact, CoroutineScope {
     fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
 }
 
-/**
- * 撤回这条消息.
- *
- * [Bot] 撤回自己的消息不需要权限.
- * [Bot] 撤回群员的消息需要管理员权限.
- *
- * @throws PermissionDeniedException 当 [Bot] 无权限操作时
- * @see Group.recall
- */
-suspend inline fun Group.recall(message: MessageChain) = this.recall(message[MessageSource])
-
-/**
- * 在一段时间后撤回这条消息.
- *
- * @param millis 延迟的时间, 单位为毫秒
- * @param coroutineContext 额外的 [CoroutineContext]
- * @see recall
- */
-fun Group.recallIn(
-    message: MessageSource,
-    millis: Long,
-    coroutineContext: CoroutineContext = EmptyCoroutineContext
-): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
-    kotlinx.coroutines.delay(millis)
-    recall(message)
-}
-
-/**
- * 在一段时间后撤回这条消息.
- *
- * @param millis 延迟的时间, 单位为毫秒
- * @param coroutineContext 额外的 [CoroutineContext]
- * @see recall
- */
-fun Group.recallIn(
-    message: MessageChain,
-    millis: Long,
-    coroutineContext: CoroutineContext = EmptyCoroutineContext
-): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
-    kotlinx.coroutines.delay(millis)
-    recall(message)
-}
-
 /**
  * 返回机器人是否正在被禁言
  *