From 0c58c511d0ed2d4d26e00ab034a7aa784007120c Mon Sep 17 00:00:00 2001
From: ryoii <ryoii@foxmail.com>
Date: Sun, 23 Feb 2020 18:22:58 +0800
Subject: [PATCH 01/17] Http api ignore unknown message type tdo

---
 .../net/mamoe/mirai/api/http/data/common/MessageDTO.kt      | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
index d13a97f17..b0906898a 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
@@ -17,7 +17,9 @@ import net.mamoe.mirai.message.FriendMessage
 import net.mamoe.mirai.message.GroupMessage
 import net.mamoe.mirai.message.MessagePacket
 import net.mamoe.mirai.message.data.*
+import net.mamoe.mirai.message.uploadImage
 import net.mamoe.mirai.utils.MiraiInternalAPI
+import java.net.URL
 
 /*
 *   DTO data class
@@ -97,7 +99,7 @@ fun MessageChain.toDTOChain() = mutableListOf(this[MessageSource].toDTO()).apply
 }
 
 fun MessageChainDTO.toMessageChain(contact: Contact) =
-    buildMessageChain { this@toMessageChain.forEach { add(it.toMessage(contact)) } }
+    buildMessageChain { this@toMessageChain.forEach { it.toMessage(contact)?.let(::add) } }
 
 @UseExperimental(ExperimentalUnsignedTypes::class)
 fun Message.toDTO() = when (this) {
@@ -119,6 +121,6 @@ fun MessageDTO.toMessage(contact: Contact) = when (this) {
     is PlainDTO -> PlainText(text)
     is ImageDTO -> Image(imageId)
     is XmlDTO -> XMLMessage(xml)
-    is MessageSourceDTO, is UnknownMessageDTO -> PlainText("assert cannot reach")
+    is MessageSourceDTO, is UnknownMessageDTO -> null
 }
 

From 94d859a4e6cf410d3e79d63f97ffa11a54771bbe Mon Sep 17 00:00:00 2001
From: ryoii <ryoii@foxmail.com>
Date: Sun, 23 Feb 2020 19:18:14 +0800
Subject: [PATCH 02/17] Http api better image with url

---
 .../mirai/api/http/data/common/BotEventDTO.kt |  2 +-
 .../mirai/api/http/data/common/MessageDTO.kt  | 42 +++++++++++--------
 .../mirai/api/http/queue/MessageQueue.kt      |  2 +-
 .../api/http/route/MessageRouteModule.kt      |  2 +
 4 files changed, 28 insertions(+), 20 deletions(-)

diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/BotEventDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/BotEventDTO.kt
index e94a0c5e4..28630c173 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/BotEventDTO.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/BotEventDTO.kt
@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI
 sealed class BotEventDTO : EventDTO()
 
 @UseExperimental(MiraiExperimentalAPI::class)
-fun BotEvent.toDTO() = when(this) {
+suspend fun BotEvent.toDTO() = when(this) {
     is MessagePacket<*, *> -> toDTO()
     else -> when(this) {
         is BotOnlineEvent -> BotOnlineEventDTO(bot.uin)
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
index b0906898a..187c49a0e 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
@@ -58,7 +58,7 @@ data class PlainDTO(val text: String) : MessageDTO()
 
 @Serializable
 @SerialName("Image")
-data class ImageDTO(val imageId: String) : MessageDTO()
+data class ImageDTO(val imageId: String? = null, val url: String? = null) : MessageDTO()
 
 @Serializable
 @SerialName("Xml")
@@ -85,41 +85,47 @@ sealed class MessageDTO : DTO
 /*
     Extend function
  */
-fun MessagePacket<*, *>.toDTO() = when (this) {
+suspend fun MessagePacket<*, *>.toDTO() = when (this) {
     is FriendMessage -> FriendMessagePacketDTO(QQDTO(sender))
     is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender))
     else -> IgnoreEventDTO
 }.apply {
-    if (this is MessagePacketDTO) { messageChain = message.toDTOChain() }
-    // else: `this` is bot event
+    if (this is MessagePacketDTO) {
+        // 将MessagePacket中的所有Message转为DTO对象,并添加到messageChain
+        // foreachContent会忽略MessageSource,一次主动获取
+        messageChain = mutableListOf(messageDTO(message[MessageSource])).apply {
+            message.foreachContent { content -> messageDTO(content).takeUnless { it == UnknownMessageDTO }?.let(::add) }
+        }
+        // else: `this` is bot event
+    }
 }
 
-fun MessageChain.toDTOChain() = mutableListOf(this[MessageSource].toDTO()).apply {
-    foreachContent { content -> content.toDTO().takeUnless { it == UnknownMessageDTO }?.let(::add) }
-}
-
-fun MessageChainDTO.toMessageChain(contact: Contact) =
+suspend fun MessageChainDTO.toMessageChain(contact: Contact) =
     buildMessageChain { this@toMessageChain.forEach { it.toMessage(contact)?.let(::add) } }
 
 @UseExperimental(ExperimentalUnsignedTypes::class)
-fun Message.toDTO() = when (this) {
-    is MessageSource -> MessageSourceDTO(id)
-    is At -> AtDTO(target, display)
+suspend fun MessagePacket<*, *>.messageDTO(message: Message) = when (message) {
+    is MessageSource -> MessageSourceDTO(message.id)
+    is At -> AtDTO(message.target, message.display)
     is AtAll -> AtAllDTO(0L)
-    is Face -> FaceDTO(id)
-    is PlainText -> PlainDTO(stringValue)
-    is Image -> ImageDTO(imageId)
-    is XMLMessage -> XmlDTO(stringValue)
+    is Face -> FaceDTO(message.id)
+    is PlainText -> PlainDTO(message.stringValue)
+    is Image -> ImageDTO(message.imageId, message.url())
+    is XMLMessage -> XmlDTO(message.stringValue)
     else -> UnknownMessageDTO
 }
 
 @UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
-fun MessageDTO.toMessage(contact: Contact) = when (this) {
+suspend fun MessageDTO.toMessage(contact: Contact) = when (this) {
     is AtDTO -> At((contact as Group)[target])
     is AtAllDTO -> AtAll
     is FaceDTO -> Face(faceId)
     is PlainDTO -> PlainText(text)
-    is ImageDTO -> Image(imageId)
+    is ImageDTO -> when {
+        !imageId.isNullOrBlank() -> Image(imageId)
+        !url.isNullOrBlank() -> contact.uploadImage(URL(url))
+        else -> null
+    }
     is XmlDTO -> XMLMessage(xml)
     is MessageSourceDTO, is UnknownMessageDTO -> null
 }
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
index e497d56e6..1cb1a54cb 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
@@ -23,7 +23,7 @@ class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
     val quoteCacheSize = 4096
     val quoteCache = LinkedHashMap<Long, GroupMessage>()
 
-    fun fetch(size: Int): List<EventDTO> {
+    suspend fun fetch(size: Int): List<EventDTO> {
         var count = size
 
         val ret = ArrayList<EventDTO>(count)
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
index 04631b2d1..e5b7b0bb7 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
@@ -46,12 +46,14 @@ fun Application.messageModule() {
             it.session.bot.getFriend(it.target).apply {
                 sendMessage(it.messageChain.toMessageChain(this)) // this aka QQ
             }
+            call.respondStateCode(StateCode.Success)
         }
 
         miraiVerify<SendDTO>("/sendGroupMessage") {
             it.session.bot.getGroup(it.target).apply {
                 sendMessage(it.messageChain.toMessageChain(this)) // this aka Group
             }
+            call.respondStateCode(StateCode.Success)
         }
 
         miraiVerify<SendDTO>("/sendQuoteMessage") {

From 0e5e2336ff9af79265dfe5b7b72acf0eb3704e74 Mon Sep 17 00:00:00 2001
From: ryoii <ryoii@foxmail.com>
Date: Sun, 23 Feb 2020 20:33:03 +0800
Subject: [PATCH 03/17] Http api return messageId after send a message

---
 mirai-api-http/README_CH.md                   | 20 ++++++++++------
 .../api/http/route/MessageRouteModule.kt      | 24 +++++++++++--------
 .../net.mamoe.mirai/message/MessageReceipt.kt |  2 +-
 3 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/mirai-api-http/README_CH.md b/mirai-api-http/README_CH.md
index 274d7cd84..7019abd7d 100644
--- a/mirai-api-http/README_CH.md
+++ b/mirai-api-http/README_CH.md
@@ -175,12 +175,13 @@ fun main() {
 | target       | Long   | false | 987654321   | 发送消息目标好友的QQ号           |
 | messageChain | Array  | false | []          | 消息链,是一个消息对象构成的数组 |
 
-#### 响应: 返回统一状态码
+#### 响应: 返回统一状态码(并携带messageId)
 
 ```json5
 {
     "code": 0,
-    "msg": "success"
+    "msg": "success",
+    "messageId": 1234567890 // 一个Long类型属性,标识本条消息,用于撤回和引用回复
 }
 ```
 
@@ -213,12 +214,13 @@ fun main() {
 | target       | Long   | false | 987654321   | 发送消息目标群的群号             |
 | messageChain | Array  | false | []          | 消息链,是一个消息对象构成的数组 |
 
-#### 响应: 返回统一状态码
+#### 响应: 返回统一状态码(并携带messageId)
 
 ```json5
 {
     "code": 0,
-    "msg": "success"
+    "msg": "success",
+    "messageId": 1234567890 // 一个Long类型属性,标识本条消息,用于撤回和引用回复
 }
 ```
 
@@ -251,12 +253,13 @@ fun main() {
 | target       | Long   | false | 987654321   | 引用消息的Message Source的Uid    |
 | messageChain | Array  | false | []          | 消息链,是一个消息对象构成的数组 |
 
-#### 响应: 返回统一状态码
+#### 响应: 返回统一状态码(并携带messageId)
 
 ```json5
 {
     "code": 0,
-    "msg": "success"
+    "msg": "success",
+    "messageId": 1234567890 // 一个Long类型属性,标识本条消息,用于撤回和引用回复
 }
 ```
 
@@ -370,7 +373,10 @@ Content-Type:multipart/form-data
     }
  },{
     "type": "FriendMessage",         // 消息类型:GroupMessage或FriendMessage或各类Event
-        "messageChain": [{           // 消息链,是一个消息对象构成的数组
+    "messageChain": [{             // 消息链,是一个消息对象构成的数组
+        "type": "Source",
+        "uid": 123456
+    },{
         "type": "Plain",
         "text": "Miral牛逼"
     }],
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
index e5b7b0bb7..872d40497 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
@@ -20,10 +20,10 @@ import io.ktor.response.respondText
 import io.ktor.routing.post
 import io.ktor.routing.routing
 import kotlinx.serialization.Serializable
-import kotlinx.serialization.Transient
 import net.mamoe.mirai.api.http.AuthedSession
 import net.mamoe.mirai.api.http.SessionManager
 import net.mamoe.mirai.api.http.data.*
+import net.mamoe.mirai.api.http.data.common.DTO
 import net.mamoe.mirai.api.http.data.common.MessageChainDTO
 import net.mamoe.mirai.api.http.data.common.VerifyDTO
 import net.mamoe.mirai.api.http.data.common.toMessageChain
@@ -44,23 +44,26 @@ fun Application.messageModule() {
 
         miraiVerify<SendDTO>("/sendFriendMessage") {
             it.session.bot.getFriend(it.target).apply {
-                sendMessage(it.messageChain.toMessageChain(this)) // this aka QQ
+                val receipt = sendMessage(it.messageChain.toMessageChain(this)) // this aka QQ
+                receipt.source.ensureSequenceIdAvailable()
+                call.respondDTO(SendRetDTO(messageId = receipt.source.id))
             }
-            call.respondStateCode(StateCode.Success)
         }
 
         miraiVerify<SendDTO>("/sendGroupMessage") {
             it.session.bot.getGroup(it.target).apply {
-                sendMessage(it.messageChain.toMessageChain(this)) // this aka Group
+                val receipt = sendMessage(it.messageChain.toMessageChain(this)) // this aka Group
+                receipt.source.ensureSequenceIdAvailable()
+                call.respondDTO(SendRetDTO(messageId = receipt.source.id))
             }
-            call.respondStateCode(StateCode.Success)
         }
 
         miraiVerify<SendDTO>("/sendQuoteMessage") {
             it.session.messageQueue.quoteCache[it.target]?.apply {
-                quoteReply(it.messageChain.toMessageChain(group))
+                val receipt = quoteReply(it.messageChain.toMessageChain(group))
+                receipt.source.ensureSequenceIdAvailable()
+                call.respondDTO(SendRetDTO(messageId = receipt.source.id))
             } ?: throw NoSuchElementException()
-            call.respondStateCode(StateCode.Success)
         }
 
         miraiVerify<SendImageDTO>("sendImageMessage") {
@@ -127,9 +130,10 @@ private data class SendImageDTO(
 
 @Serializable
 private class SendRetDTO(
-    val messageId: Long,
-    @Transient val stateCode: StateCode = Success
-) : StateCode(stateCode.code, stateCode.msg)
+    val code: Int = 0,
+    val msg: String = "success",
+    val messageId: Long
+) : DTO
 
 @Serializable
 private data class RecallDTO(
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
index 7d5c37657..31482e393 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
@@ -31,7 +31,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
  * @see QQ.sendMessage 发送群消息, 返回回执(此对象)
  */
 open class MessageReceipt<C : Contact>(
-    private val source: MessageSource,
+    val source: MessageSource,
     target: C
 ) {
     init {

From a52655e2abe32d6b31dd34eb153e754de852e1aa Mon Sep 17 00:00:00 2001
From: ryoii <ryoii@foxmail.com>
Date: Sun, 23 Feb 2020 22:57:06 +0800
Subject: [PATCH 04/17] Fix error in LockFreeLinkedListTest

---
 .../kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt  | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt
index de4c3b195..6af63aa89 100644
--- a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt
+++ b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt
@@ -161,7 +161,7 @@ internal class LockFreeLinkedListTest {
         println("Check value")
         value shouldBeEqualTo 6
         println("Check size")
-        println(list.getLinkStructure())
+//        println(list.getLinkStructure())
         list.size shouldBeEqualTo 6
     }
 
@@ -174,7 +174,7 @@ internal class LockFreeLinkedListTest {
         println("Check value")
         value shouldBeEqualTo 2
         println("Check size")
-        println(list.getLinkStructure())
+//        println(list.getLinkStructure())
         list.size shouldBeEqualTo 5
     }
 
@@ -198,7 +198,7 @@ internal class LockFreeLinkedListTest {
         println("Check value")
         value shouldBeEqualTo 2
         println("Check size")
-        println(list.getLinkStructure())
+//        println(list.getLinkStructure())
         list.size shouldBeEqualTo 1
     }
     /*

From cd73d7f53467407cc3c9b277a39decf391f02e45 Mon Sep 17 00:00:00 2001
From: ryoii <ryoii@foxmail.com>
Date: Sun, 23 Feb 2020 22:58:24 +0800
Subject: [PATCH 05/17] Http api friendly quote

---
 .../mirai/api/http/queue/MessageQueue.kt      | 11 +++--
 .../api/http/route/MessageRouteModule.kt      | 40 ++++++++++++++-----
 2 files changed, 36 insertions(+), 15 deletions(-)

diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
index 1cb1a54cb..83250127a 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
@@ -14,6 +14,7 @@ import net.mamoe.mirai.api.http.data.common.IgnoreEventDTO
 import net.mamoe.mirai.api.http.data.common.toDTO
 import net.mamoe.mirai.event.events.BotEvent
 import net.mamoe.mirai.message.GroupMessage
+import net.mamoe.mirai.message.MessagePacket
 import net.mamoe.mirai.message.data.MessageSource
 import net.mamoe.mirai.utils.firstKey
 import java.util.concurrent.ConcurrentLinkedDeque
@@ -21,7 +22,7 @@ import java.util.concurrent.ConcurrentLinkedDeque
 class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
 
     val quoteCacheSize = 4096
-    val quoteCache = LinkedHashMap<Long, GroupMessage>()
+    val quoteCache = LinkedHashMap<Long, MessagePacket<*, *>>()
 
     suspend fun fetch(size: Int): List<EventDTO> {
         var count = size
@@ -37,15 +38,17 @@ class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
                 }
             }
 
-            // TODO: 等FriendMessage支持quote
-            if (event is GroupMessage) {
+            if (event is MessagePacket<*, *>) {
                 addQuoteCache(event)
             }
         }
         return ret
     }
 
-    private fun addQuoteCache(msg: GroupMessage) {
+    fun cacheQuote(messageId: Long) =
+        quoteCache[messageId] ?: throw NoSuchElementException()
+
+    private fun addQuoteCache(msg: MessagePacket<*, *>) {
         quoteCache[msg.message[MessageSource].id] = msg
         if (quoteCache.size > quoteCacheSize) {
             quoteCache.remove(quoteCache.firstKey())
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
index 872d40497..7e59e6e43 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
@@ -28,7 +28,9 @@ import net.mamoe.mirai.api.http.data.common.MessageChainDTO
 import net.mamoe.mirai.api.http.data.common.VerifyDTO
 import net.mamoe.mirai.api.http.data.common.toMessageChain
 import net.mamoe.mirai.api.http.util.toJson
-import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.contact.Contact
+import net.mamoe.mirai.message.MessageReceipt
+import net.mamoe.mirai.message.data.*
 import net.mamoe.mirai.message.uploadImage
 import java.net.URL
 
@@ -42,30 +44,45 @@ fun Application.messageModule() {
             call.respondJson(fetch.toJson())
         }
 
+        suspend fun <C : Contact> sendMessage(
+            quote: QuoteReplyToSend?,
+            messageChain: MessageChain,
+            target: C
+        ): MessageReceipt<out Contact> {
+            val send = if (quote == null) {
+                messageChain
+            } else {
+                (quote + messageChain).toChain()
+            }
+            return target.sendMessage(send)
+        }
+
         miraiVerify<SendDTO>("/sendFriendMessage") {
+            val quote = it.quote?.let { q ->
+                it.session.messageQueue.cacheQuote(q).run {
+                    this[MessageSource].quote(sender)
+                }}
+
             it.session.bot.getFriend(it.target).apply {
-                val receipt = sendMessage(it.messageChain.toMessageChain(this)) // this aka QQ
+                val receipt = sendMessage(quote, it.messageChain.toMessageChain(this), this)
                 receipt.source.ensureSequenceIdAvailable()
                 call.respondDTO(SendRetDTO(messageId = receipt.source.id))
             }
         }
 
         miraiVerify<SendDTO>("/sendGroupMessage") {
+            val quote = it.quote?.let { q ->
+                it.session.messageQueue.cacheQuote(q).run {
+                    this[MessageSource].quote(sender)
+                }}
+
             it.session.bot.getGroup(it.target).apply {
-                val receipt = sendMessage(it.messageChain.toMessageChain(this)) // this aka Group
+                val receipt = sendMessage(quote, it.messageChain.toMessageChain(this), this)
                 receipt.source.ensureSequenceIdAvailable()
                 call.respondDTO(SendRetDTO(messageId = receipt.source.id))
             }
         }
 
-        miraiVerify<SendDTO>("/sendQuoteMessage") {
-            it.session.messageQueue.quoteCache[it.target]?.apply {
-                val receipt = quoteReply(it.messageChain.toMessageChain(group))
-                receipt.source.ensureSequenceIdAvailable()
-                call.respondDTO(SendRetDTO(messageId = receipt.source.id))
-            } ?: throw NoSuchElementException()
-        }
-
         miraiVerify<SendImageDTO>("sendImageMessage") {
             val bot = it.session.bot
             val contact = when {
@@ -115,6 +132,7 @@ fun Application.messageModule() {
 @Serializable
 private data class SendDTO(
     override val sessionKey: String,
+    val quote: Long? = null,
     val target: Long,
     val messageChain: MessageChainDTO
 ) : VerifyDTO()

From 0d7af4a111f8ce521a1d82c2fce660999c6b8e2b Mon Sep 17 00:00:00 2001
From: ryoii <ryoii@foxmail.com>
Date: Sun, 23 Feb 2020 23:01:41 +0800
Subject: [PATCH 06/17] Http api update README_CH.md

---
 mirai-api-http/README_CH.md | 41 ++-----------------------------------
 1 file changed, 2 insertions(+), 39 deletions(-)

diff --git a/mirai-api-http/README_CH.md b/mirai-api-http/README_CH.md
index 7019abd7d..30f52fce4 100644
--- a/mirai-api-http/README_CH.md
+++ b/mirai-api-http/README_CH.md
@@ -173,6 +173,7 @@ fun main() {
 | ------------ | ------ | ----- | ----------- | -------------------------------- |
 | sessionKey   | String | false | YourSession | 已经激活的Session                |
 | target       | Long   | false | 987654321   | 发送消息目标好友的QQ号           |
+| quote        | Long   | true  | 135798642   | 引用一条消息的messageId进行回复  |
 | messageChain | Array  | false | []          | 消息链,是一个消息对象构成的数组 |
 
 #### 响应: 返回统一状态码(并携带messageId)
@@ -212,45 +213,7 @@ fun main() {
 | ------------ | ------ | ----- | ----------- | -------------------------------- |
 | sessionKey   | String | false | YourSession | 已经激活的Session                |
 | target       | Long   | false | 987654321   | 发送消息目标群的群号             |
-| messageChain | Array  | false | []          | 消息链,是一个消息对象构成的数组 |
-
-#### 响应: 返回统一状态码(并携带messageId)
-
-```json5
-{
-    "code": 0,
-    "msg": "success",
-    "messageId": 1234567890 // 一个Long类型属性,标识本条消息,用于撤回和引用回复
-}
-```
-
-
-
-### 发送引用回复消息(仅支持群消息)
-
-```
-[POST] /sendQuoteMessage
-```
-
-使用此方法向指定的消息进行引用回复
-
-#### 请求
-
-```json5
-{
-    "sessionKey": "YourSession",
-    "target": 987654321,
-    "messageChain": [
-        { "type": "Plain", "text":"hello\n" },
-        { "type": "Plain", "text":"world" }
-    ]
-}
-```
-
-| 名字         | 类型   | 可选  | 举例        | 说明                             |
-| ------------ | ------ | ----- | ----------- | -------------------------------- |
-| sessionKey   | String | false | YourSession | 已经激活的Session                |
-| target       | Long   | false | 987654321   | 引用消息的Message Source的Uid    |
+| quote        | Long   | true  | 135798642   | 引用一条消息的messageId进行回复  |
 | messageChain | Array  | false | []          | 消息链,是一个消息对象构成的数组 |
 
 #### 响应: 返回统一状态码(并携带messageId)

From f4020e4a03a6868ac358dc665bbbf4e4beb3703d Mon Sep 17 00:00:00 2001
From: ryoii <ryoii@foxmail.com>
Date: Sun, 23 Feb 2020 23:58:01 +0800
Subject: [PATCH 07/17] Http api support recall

---
 mirai-api-http/README_CH.md                   | 33 +++++++++++++++++++
 .../mirai/api/http/queue/MessageQueue.kt      | 17 +++++-----
 .../api/http/route/MessageRouteModule.kt      | 19 +++++++----
 3 files changed, 54 insertions(+), 15 deletions(-)

diff --git a/mirai-api-http/README_CH.md b/mirai-api-http/README_CH.md
index 30f52fce4..ff595681d 100644
--- a/mirai-api-http/README_CH.md
+++ b/mirai-api-http/README_CH.md
@@ -297,6 +297,39 @@ Content-Type:multipart/form-data
 
 
 
+### 撤回消息
+
+```
+[POST] /recall
+```
+
+使用此方法撤回指定消息。对于bot发送的消息,又2分钟时间限制。对于撤回群聊中群员的消息,需要有相应权限
+
+#### 请求
+
+```json5
+{
+    "sessionKey": "YourSession",
+    "target": 987654321
+}
+```
+
+| 名字         | 类型   | 可选  | 举例        | 说明                             |
+| ------------ | ------ | ----- | ----------- | -------------------------------- |
+| sessionKey   | String | false | YourSession | 已经激活的Session                |
+| target       | Long   | false | 987654321   | 需要撤回的消息的messageId        |
+
+#### 响应: 返回统一状态码
+
+```json5
+{
+    "code": 0,
+    "msg": "success"
+}
+```
+
+
+
 ### 获取Bot收到的消息和事件
 
 ```
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
index 83250127a..f39a3e8e8 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
@@ -13,7 +13,6 @@ import net.mamoe.mirai.api.http.data.common.EventDTO
 import net.mamoe.mirai.api.http.data.common.IgnoreEventDTO
 import net.mamoe.mirai.api.http.data.common.toDTO
 import net.mamoe.mirai.event.events.BotEvent
-import net.mamoe.mirai.message.GroupMessage
 import net.mamoe.mirai.message.MessagePacket
 import net.mamoe.mirai.message.data.MessageSource
 import net.mamoe.mirai.utils.firstKey
@@ -21,8 +20,8 @@ import java.util.concurrent.ConcurrentLinkedDeque
 
 class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
 
-    val quoteCacheSize = 4096
-    val quoteCache = LinkedHashMap<Long, MessagePacket<*, *>>()
+    val cacheSize = 4096
+    val cache = LinkedHashMap<Long, MessagePacket<*, *>>()
 
     suspend fun fetch(size: Int): List<EventDTO> {
         var count = size
@@ -45,13 +44,13 @@ class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
         return ret
     }
 
-    fun cacheQuote(messageId: Long) =
-        quoteCache[messageId] ?: throw NoSuchElementException()
+    fun cache(messageId: Long) =
+        cache[messageId] ?: throw NoSuchElementException()
 
-    private fun addQuoteCache(msg: MessagePacket<*, *>) {
-        quoteCache[msg.message[MessageSource].id] = msg
-        if (quoteCache.size > quoteCacheSize) {
-            quoteCache.remove(quoteCache.firstKey())
+    fun addQuoteCache(msg: MessagePacket<*, *>) {
+        cache[msg.message[MessageSource].id] = msg
+        if (cache.size > cacheSize) {
+            cache.remove(cache.firstKey())
         }
     }
 }
\ No newline at end of file
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
index 7e59e6e43..46a555a99 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/MessageRouteModule.kt
@@ -29,6 +29,8 @@ import net.mamoe.mirai.api.http.data.common.VerifyDTO
 import net.mamoe.mirai.api.http.data.common.toMessageChain
 import net.mamoe.mirai.api.http.util.toJson
 import net.mamoe.mirai.contact.Contact
+import net.mamoe.mirai.message.FriendMessage
+import net.mamoe.mirai.message.GroupMessage
 import net.mamoe.mirai.message.MessageReceipt
 import net.mamoe.mirai.message.data.*
 import net.mamoe.mirai.message.uploadImage
@@ -59,26 +61,30 @@ fun Application.messageModule() {
 
         miraiVerify<SendDTO>("/sendFriendMessage") {
             val quote = it.quote?.let { q ->
-                it.session.messageQueue.cacheQuote(q).run {
+                it.session.messageQueue.cache(q).run {
                     this[MessageSource].quote(sender)
                 }}
 
             it.session.bot.getFriend(it.target).apply {
                 val receipt = sendMessage(quote, it.messageChain.toMessageChain(this), this)
                 receipt.source.ensureSequenceIdAvailable()
+
+                it.session.messageQueue.addQuoteCache(FriendMessage(bot.selfQQ, receipt.source.toChain()))
                 call.respondDTO(SendRetDTO(messageId = receipt.source.id))
             }
         }
 
         miraiVerify<SendDTO>("/sendGroupMessage") {
             val quote = it.quote?.let { q ->
-                it.session.messageQueue.cacheQuote(q).run {
+                it.session.messageQueue.cache(q).run {
                     this[MessageSource].quote(sender)
                 }}
 
             it.session.bot.getGroup(it.target).apply {
                 val receipt = sendMessage(quote, it.messageChain.toMessageChain(this), this)
                 receipt.source.ensureSequenceIdAvailable()
+
+                it.session.messageQueue.addQuoteCache(GroupMessage("", botPermission, botAsMember, receipt.source.toChain()))
                 call.respondDTO(SendRetDTO(messageId = receipt.source.id))
             }
         }
@@ -123,8 +129,10 @@ fun Application.messageModule() {
         }
 
         miraiVerify<RecallDTO>("recall") {
-            // TODO
-            call.respond(HttpStatusCode.NotFound, "未完成")
+            it.session.messageQueue.cache(it.target).apply {
+                it.session.bot.recall(get(MessageSource))
+            }
+            call.respondStateCode(StateCode.Success)
         }
     }
 }
@@ -156,6 +164,5 @@ private class SendRetDTO(
 @Serializable
 private data class RecallDTO(
     override val sessionKey: String,
-    val target: Long,
-    val sender: Long
+    val target: Long
 ) : VerifyDTO()

From 6cbc69cd685a557e4325ff1dfd04f080f1965428 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 10:26:27 +0800
Subject: [PATCH 08/17] Fix sequenceId for FriendMessage

---
 .../net/mamoe/mirai/qqandroid/ContactImpl.kt  |  2 +-
 .../net/mamoe/mirai/qqandroid/QQAndroidBot.kt |  8 +++-
 .../protocol/packet/chat/PbMessageSvc.kt      | 29 +++++++++++++
 .../packet/chat/receive/MessageSvc.kt         | 43 ++++++++++++++-----
 .../net.mamoe.mirai/message/MessageReceipt.kt | 10 ++---
 5 files changed, 72 insertions(+), 20 deletions(-)

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 614d5873c..a7c26c452 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
@@ -542,7 +542,7 @@ internal class GroupImpl(
         if (event.isCancelled) {
             throw EventCancelledException("cancelled by FriendMessageSendEvent")
         }
-        lateinit var source: MessageSvc.PbSendMsg.MessageSourceFromSend
+        lateinit var source: MessageSvc.PbSendMsg.MessageSourceFromSendGroup
         bot.network.run {
             val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup(
                 bot.client,
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 5b400fca4..c6e697b35 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
@@ -129,9 +129,15 @@ internal abstract class QQAndroidBotBase constructor(
         source.ensureSequenceIdAvailable()
 
         network.run {
-            val response: PbMessageSvc.PbMsgWithDraw.Response =
+            val response: PbMessageSvc.PbMsgWithDraw.Response = if (source.groupId == 0L) {
+                PbMessageSvc.PbMsgWithDraw.Friend(bot.client, source.senderId, source.sequenceId, source.messageRandom, source.time)
+                    .sendAndExpect()
+            } else {
+
                 PbMessageSvc.PbMsgWithDraw.Group(bot.client, source.groupId, source.sequenceId, source.messageRandom)
                     .sendAndExpect()
+            }
+
             check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" }
         }
     }
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt
index eab98c991..88dd705db 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt
@@ -71,6 +71,35 @@ internal class PbMessageSvc {
             )
         }
 
+        fun Friend(
+            client: QQAndroidClient,
+            toUin: Long,
+            messageSequenceId: Int, // 56639
+            messageRandom: Int, // 921878719
+            time: Long,
+            messageType: Int = 0
+        ): OutgoingPacket = buildOutgoingUniPacket(client) {
+            writeProtoBuf(
+                MsgSvc.PbMsgWithDrawReq.serializer(),
+                MsgSvc.PbMsgWithDrawReq(
+                    c2cWithDraw = listOf(
+                        MsgSvc.PbC2CMsgWithDrawReq(
+                            subCmd = 1,
+                            msgInfo = listOf(
+                                MsgSvc.PbC2CMsgWithDrawReq.MsgInfo(
+                                    fromUin = client.bot.uin,
+                                    toUin = toUin,
+                                    msgSeq = messageSequenceId,
+                                    msgUid = messageRandom.toLong() and 0xffffffff,
+                                    msgTime = time and 0xffffffff
+                                )
+                            )
+                        )
+                    )
+                )
+            )
+        }
+
         override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
             val resp = readProtoBuf(MsgSvc.PbMsgWithDrawResp.serializer())
             resp.groupWithDraw?.firstOrNull()?.let {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
index 7207413db..41aa13650 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
@@ -269,7 +269,28 @@ internal class MessageSvc {
             }
         }
 
-        internal class MessageSourceFromSend(
+        internal class MessageSourceFromSendFriend(
+            val messageRandom: Int,
+            override val time: Long,
+            override val senderId: Long,
+            override val groupId: Long,
+            val sequenceId: Int
+        ) : MessageSource {
+            @UseExperimental(ExperimentalCoroutinesApi::class)
+            override val id: Long
+                get() = sequenceId.toLong().shl(32) or
+                        messageRandom.toLong().and(0xFFFFFFFF)
+
+            override suspend fun ensureSequenceIdAvailable() {
+                // nothing to do
+            }
+
+            override fun toString(): String {
+                return ""
+            }
+        }
+
+        internal class MessageSourceFromSendGroup(
             val messageRandom: Int,
             override val time: Long,
             override val senderId: Long,
@@ -286,7 +307,7 @@ internal class MessageSvc {
             @UseExperimental(MiraiExperimentalAPI::class)
             fun startWaitingSequenceId(contact: Contact) {
                 sequenceIdDeferred = contact.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(timeoutMillis = 3000) {
-                    if (it.messageRandom == this@MessageSourceFromSend.messageRandom) {
+                    if (it.messageRandom == this@MessageSourceFromSendGroup.messageRandom) {
                         it.sequenceId
                     } else null
                 }
@@ -305,14 +326,14 @@ internal class MessageSvc {
             client: QQAndroidClient,
             toUin: Long,
             message: MessageChain,
-            crossinline sourceCallback: (MessageSource) -> Unit
+            crossinline sourceCallback: (MessageSourceFromSendFriend) -> Unit
         ): OutgoingPacket {
-            val source = MessageSourceFromSend(
+            val source = MessageSourceFromSendFriend(
                 messageRandom = Random.nextInt().absoluteValue,
                 senderId = client.uin,
                 time = currentTimeSeconds + client.timeDifference,
-                groupId = 0//
-                //   sourceMessage = message
+                groupId = 0,
+                sequenceId = client.atomicNextMessageSequenceId()
             )
             sourceCallback(source)
             return ToFriend(client, toUin, message, source)
@@ -326,7 +347,7 @@ internal class MessageSvc {
             client: QQAndroidClient,
             toUin: Long,
             message: MessageChain,
-            source: MessageSourceFromSend
+            source: MessageSourceFromSendFriend
         ): OutgoingPacket = buildOutgoingUniPacket(client) {
             ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
 
@@ -340,7 +361,7 @@ internal class MessageSvc {
                             elems = message.toRichTextElems(false)
                         )
                     ),
-                    msgSeq = client.atomicNextMessageSequenceId(),
+                    msgSeq = source.sequenceId,
                     msgRand = source.messageRandom,
                     syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer())
                     // msgVia = 1
@@ -353,10 +374,10 @@ internal class MessageSvc {
             client: QQAndroidClient,
             groupCode: Long,
             message: MessageChain,
-            sourceCallback: (MessageSourceFromSend) -> Unit
+            sourceCallback: (MessageSourceFromSendGroup) -> Unit
         ): OutgoingPacket {
 
-            val source = MessageSourceFromSend(
+            val source = MessageSourceFromSendGroup(
                 messageRandom = Random.nextInt().absoluteValue,
                 senderId = client.uin,
                 time = currentTimeSeconds + client.timeDifference,
@@ -375,7 +396,7 @@ internal class MessageSvc {
             client: QQAndroidClient,
             groupCode: Long,
             message: MessageChain,
-            source: MessageSourceFromSend
+            source: MessageSourceFromSendGroup
         ): OutgoingPacket = buildOutgoingUniPacket(client) {
             ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
 
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
index 7d5c37657..37e6148db 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
@@ -78,13 +78,9 @@ open class MessageReceipt<C : Contact>(
     fun recallIn(millis: Long): Job {
         @Suppress("BooleanLiteralArgument")
         if (_isRecalled.compareAndSet(false, true)) {
-            when (val contact = target) {
-                is Group -> {
-                    return contact.bot.recallIn(source, millis)
-                }
-                is QQ -> {
-                    TODO()
-                }
+            return when (val contact = target) {
+                is QQ,
+                is Group -> contact.bot.recallIn(source, millis)
                 else -> error("Unknown contact type")
             }
         } else error("message is already or planned to be recalled")

From a6f359ec962fcf8d18fd5ba2d0f2dd404586b78f Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 10:33:09 +0800
Subject: [PATCH 09/17] Add consoleversion

---
 gradle.properties | 1 +
 1 file changed, 1 insertion(+)

diff --git a/gradle.properties b/gradle.properties
index 370fdbd0c..ab40596ca 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,6 +3,7 @@ kotlin.code.style=official
 # config
 mirai_version=0.21.0
 mirai_japt_version=1.1.0
+mirai_console_version=0.1.0
 kotlin.incremental.multiplatform=true
 kotlin.parallel.tasks.in.project=true
 # kotlin

From 693b8ee1de5acdd194eb0fa094edd8e35e613fac Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 11:13:22 +0800
Subject: [PATCH 10/17] Remove mirai-api-http dependency

---
 mirai-console/build.gradle.kts                |  2 +-
 .../net/mamoe/mirai/console/MiraiConsole.kt   | 26 ++++++++-----------
 .../kotlin/net/mamoe/mirai/console/Setu.kt    |  2 --
 3 files changed, 12 insertions(+), 18 deletions(-)
 delete mode 100644 mirai-console/src/main/kotlin/net/mamoe/mirai/console/Setu.kt

diff --git a/mirai-console/build.gradle.kts b/mirai-console/build.gradle.kts
index 6937c46ee..ce59efa26 100644
--- a/mirai-console/build.gradle.kts
+++ b/mirai-console/build.gradle.kts
@@ -32,7 +32,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
 dependencies {
     api(project(":mirai-core"))
     api(project(":mirai-core-qqandroid"))
-    api(project(":mirai-api-http"))
+    // api(project(":mirai-api-http"))
     runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main"))
     runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main"))
     api(kotlin("serialization"))
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
index 19dc0335b..5ee1b1b5b 100644
--- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
+++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
@@ -12,32 +12,26 @@ package net.mamoe.mirai.console
 import kotlinx.coroutines.*
 import kotlinx.coroutines.channels.Channel
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.api.http.MiraiHttpAPIServer
-import net.mamoe.mirai.api.http.generateSessionKey
 import net.mamoe.mirai.console.MiraiConsole.CommandProcessor.processNextCommandLine
-import net.mamoe.mirai.console.command.*
+import net.mamoe.mirai.console.command.CommandManager
+import net.mamoe.mirai.console.command.CommandSender
+import net.mamoe.mirai.console.command.ConsoleCommandSender
+import net.mamoe.mirai.console.command.DefaultCommands
 import net.mamoe.mirai.console.plugins.PluginManager
 import net.mamoe.mirai.console.plugins.loadAsConfig
 import net.mamoe.mirai.console.plugins.withDefaultWrite
-import net.mamoe.mirai.console.plugins.withDefaultWriteSave
 import net.mamoe.mirai.console.utils.MiraiConsoleUI
-import net.mamoe.mirai.console.utils.checkManager
-import net.mamoe.mirai.contact.sendMessage
-import net.mamoe.mirai.event.subscribeMessages
-import net.mamoe.mirai.utils.SimpleLogger
 import net.mamoe.mirai.utils.cryptor.ECDH
 import java.io.File
-import java.security.Security
-import java.util.*
 
 
 object MiraiConsole {
     /**
      * 发布的版本号 统一修改位置
      */
-    val version = "v0.01"
-    var coreVersion = "v0.18.0"
-    val build = "Alpha"
+    const val version = "0.1.0"
+    const val coreVersion = "v0.18.0"
+    const val build = "Alpha"
 
 
     /**
@@ -194,13 +188,15 @@ object MiraiProperties {
 
     var HTTP_API_ENABLE: Boolean by config.withDefaultWrite { true }
     var HTTP_API_PORT: Int by config.withDefaultWrite { 8080 }
+    /*
     var HTTP_API_AUTH_KEY: String by config.withDefaultWriteSave {
         "InitKey" + generateSessionKey()
-    }
+    }*/
 }
 
 object HTTPAPIAdaptar {
     operator fun invoke() {
+        /*
         if (MiraiProperties.HTTP_API_ENABLE) {
             if (MiraiProperties.HTTP_API_AUTH_KEY.startsWith("InitKey")) {
                 MiraiConsole.logger("请尽快更改初始生成的HTTP API AUTHKEY")
@@ -214,7 +210,7 @@ object HTTPAPIAdaptar {
                 MiraiProperties.HTTP_API_AUTH_KEY
             )
             MiraiConsole.logger("HTTPAPI启动完成; 端口= " + MiraiProperties.HTTP_API_PORT)
-        }
+        }*/
     }
 }
 
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/Setu.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/Setu.kt
deleted file mode 100644
index 03dac5a40..000000000
--- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/Setu.kt
+++ /dev/null
@@ -1,2 +0,0 @@
-package net.mamoe.mirai.console
-

From df2a4a1b919562048dfc5ffa4dfbd1e267be37c5 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 11:13:50 +0800
Subject: [PATCH 11/17] mirai-console 0.1.1

---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index ab40596ca..9847d50b8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,7 +3,7 @@ kotlin.code.style=official
 # config
 mirai_version=0.21.0
 mirai_japt_version=1.1.0
-mirai_console_version=0.1.0
+mirai_console_version=0.1.1
 kotlin.incremental.multiplatform=true
 kotlin.parallel.tasks.in.project=true
 # kotlin

From 227a0a5e1ecf39343911d7b6a3a6159241fd026d Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 11:34:32 +0800
Subject: [PATCH 12/17] Support for kotlin object `PluginBase`

---
 .../main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt
index 4d1bd6f07..f43aa1685 100644
--- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt
+++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt
@@ -9,9 +9,9 @@
 
 package net.mamoe.mirai.console.plugins
 
-import net.mamoe.mirai.console.command.Command
 import kotlinx.coroutines.*
 import net.mamoe.mirai.console.MiraiConsole
+import net.mamoe.mirai.console.command.Command
 import net.mamoe.mirai.utils.DefaultLogger
 import net.mamoe.mirai.utils.MiraiLogger
 import net.mamoe.mirai.utils.SimpleLogger
@@ -294,7 +294,7 @@ object PluginManager {
                 }
                 return try {
                     val subClass = pluginClass.asSubclass(PluginBase::class.java)
-                    val plugin: PluginBase = subClass.getDeclaredConstructor().newInstance()
+                    val plugin: PluginBase = subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().newInstance()
                     description.loaded = true
                     logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author)
                     logger.info(description.info)

From 583c99392dac3ffe99938d00f68940cca55680dd Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 11:54:53 +0800
Subject: [PATCH 13/17] Update docs and references

---
 .../kotlin/net.mamoe.mirai/message/MessageReceipt.kt       | 2 +-
 .../kotlin/net.mamoe.mirai/message/data/MessageSource.kt   | 7 +++++--
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
index a854b8ded..a2a85d368 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
@@ -48,7 +48,7 @@ open class MessageReceipt<C : Contact>(
     /**
      * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
      *
-     * @see Group.recall
+     * @see Bot.recall
      * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
      */
     @UseExperimental(MiraiExperimentalAPI::class)
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt
index 992129835..0d5c0c187 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt
@@ -12,15 +12,18 @@
 
 package net.mamoe.mirai.message.data
 
+import net.mamoe.mirai.Bot
 import kotlin.jvm.JvmMultifileClass
 import kotlin.jvm.JvmName
 
 /**
- * 消息源, 用于被引用. 它将由协议模块实现.
- * 消息源只用于 [QuoteReply]
+ * 消息源, 它存在于 [MessageChain] 中, 用于表示这个消息的来源.
+ *
+ * 消息源只用于 [引用回复][QuoteReply] 或 [撤回][Bot.recall].
  *
  * `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg`
  *
+ * @see Bot.recall 撤回一条消息
  * @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
  */
 interface MessageSource : Message, MessageMetadata {

From 0a55733cb803393fb3f4bceca634ba54f3c064cf Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 11:56:07 +0800
Subject: [PATCH 14/17] Rename `instanceWhose` to `getInstance`

---
 .../kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt    | 2 +-
 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt
index 45adcf2b3..aa229181d 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt
@@ -63,7 +63,7 @@ private data class AuthRetDTO(val code: Int, val session: String) : DTO
 private data class BindDTO(override val sessionKey: String, val qq: Long) : VerifyDTO()
 
 private fun getBotOrThrow(qq: Long) = try {
-    Bot.instanceWhose(qq)
+    Bot.getInstance(qq)
 } catch (e: NoSuchElementException) {
     throw NoSuchBotException
 }
\ No newline at end of file
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 248ac05f1..5a001c115 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
@@ -59,7 +59,7 @@ abstract class Bot : CoroutineScope {
          * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
          */
         @JvmStatic
-        fun instanceWhose(qq: Long): Bot = BotImpl.instanceWhose(qq = qq)
+        fun getInstance(qq: Long): Bot = BotImpl.instanceWhose(qq = qq)
     }
 
     /**

From 559e24b4ce4b18b50edbf1f52bf9e12ee3bc12e0 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 12:06:40 +0800
Subject: [PATCH 15/17] Rename `instanceWhose` to `getInstance`

---
 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt     | 2 +-
 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

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 5a001c115..35c454f92 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
@@ -59,7 +59,7 @@ abstract class Bot : CoroutineScope {
          * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
          */
         @JvmStatic
-        fun getInstance(qq: Long): Bot = BotImpl.instanceWhose(qq = qq)
+        fun getInstance(qq: Long): Bot = BotImpl.getInstance(qq = qq)
     }
 
     /**
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
index 7ee7f84b3..79ef2ae49 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
@@ -60,7 +60,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
             it.get()?.let(block)
         }
 
-        fun instanceWhose(qq: Long): Bot {
+        fun getInstance(qq: Long): Bot {
             instances.forEach {
                 it.get()?.let { bot ->
                     if (bot.uin == qq) {

From b0bd3d2202e44d9d31a040c4d86fc74743eda953 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 12:13:40 +0800
Subject: [PATCH 16/17] mirai-core 0.22.0

---
 CHANGELOG.md      | 9 +++++++++
 gradle.properties | 2 +-
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93eab3aa7..00f4cc774 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
 
 开发版本. 频繁更新, 不保证高稳定性
 
+## `0.22.0` 2020/2/24
+### mirai-core
+- 重构 `MessageChain`, 引入 `CombinedMessage`. (兼容大部分原 API)
+- 新增 `MessageChainBuilder`, `buildMessageChain`
+- `ExternalImage` 现在接收多种输入参数
+
+### mirai-core-qqandroid
+- 修复访问好友消息回执 `.sequenceId` 时抛出异常的问题
+
 ## `0.21.0` 2020/2/23
 - 支持好友消息的引用回复
 - 更加结构化的 `QuoteReply` 架构, 支持引用任意群/好友消息回复给任意群/好友.
diff --git a/gradle.properties b/gradle.properties
index 9847d50b8..55dba9b74 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,7 @@
 # style guide
 kotlin.code.style=official
 # config
-mirai_version=0.21.0
+mirai_version=0.22.0
 mirai_japt_version=1.1.0
 mirai_console_version=0.1.1
 kotlin.incremental.multiplatform=true

From 8b9cef05d762eedd2dc6d3752d67c9bcc6416f0f Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 24 Feb 2020 17:09:08 +0800
Subject: [PATCH 17/17] Specify `OverFileSizeMaxException` throws

---
 .../commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt  | 1 +
 .../src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt    | 2 ++
 2 files changed, 3 insertions(+)

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 a7c26c452..fec23b029 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
@@ -579,6 +579,7 @@ internal class GroupImpl(
             when (response) {
                 is ImgStore.GroupPicUp.Response.Failed -> {
                     ImageUploadEvent.Failed(this@GroupImpl, image, 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 -> {
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
index 678af1d00..ca1f94607 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
@@ -25,6 +25,7 @@ import net.mamoe.mirai.recall
 import net.mamoe.mirai.recallIn
 import net.mamoe.mirai.utils.ExternalImage
 import net.mamoe.mirai.utils.MiraiExperimentalAPI
+import net.mamoe.mirai.utils.OverFileSizeMaxException
 import net.mamoe.mirai.utils.WeakRefProperty
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
@@ -74,6 +75,7 @@ interface Contact : CoroutineScope {
      * @see ImageUploadEvent 图片发送完成事件
      *
      * @throws EventCancelledException 当发送消息事件被取消
+     * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
      */
     suspend fun uploadImage(image: ExternalImage): Image