From 2b60130bb4b2ab7188c7918f77d6f3398e808876 Mon Sep 17 00:00:00 2001
From: ryoii <ryoii@foxmail.com>
Date: Fri, 14 Feb 2020 23:35:58 +0800
Subject: [PATCH] http-api support quoteReply

---
 mirai-api-http/README_CH.md                   | 54 +++++++++++++++++++
 .../mirai/api/http/data/common/MessageDTO.kt  |  6 ++-
 .../mirai/api/http/queue/MessageQueue.kt      | 17 +++++-
 .../api/http/route/SendMessageRouteModule.kt  | 12 ++++-
 .../net/mamoe/mirai/api/http/util/Json.kt     |  2 +
 5 files changed, 87 insertions(+), 4 deletions(-)

diff --git a/mirai-api-http/README_CH.md b/mirai-api-http/README_CH.md
index 0216f73f6..8fd0da5f1 100644
--- a/mirai-api-http/README_CH.md
+++ b/mirai-api-http/README_CH.md
@@ -220,6 +220,44 @@ fun main() {
 
 
 
+### 发送引用回复消息(仅支持群消息)
+
+```
+[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    |
+| messageChain | Array  | false | []          | 消息链,是一个消息对象构成的数组 |
+
+#### 响应: 返回统一状态码
+
+```json5
+{
+    "code": 0,
+    "msg": "success"
+}
+```
+
+
+
 ### 发送图片消息(通过URL)
 
 ```
@@ -308,6 +346,9 @@ Content-Type:multipart/form-data
 [{
     "type": "GroupMessage",        // 消息类型:GroupMessage或FriendMessage
 	"messageChain": [{             // 消息链,是一个消息对象构成的数组
+	    "type": "Source",
+	    "uid": 123456
+	},{
         "type": "Plain",
         "text": "Miral牛逼"
     }],
@@ -350,6 +391,19 @@ Content-Type:multipart/form-data
 + [ ] Xml,Xml卡片消息
 + [ ] 敬请期待
 
+#### Source
+
+```json5
+{
+    "type": "Source",
+    "uid": 123456
+}
+```
+
+| 名字 | 类型 | 说明                                                         |
+| ---- | ---- | ------------------------------------------------------------ |
+| uid  | Long | 消息的识别号,用于引用回复(Source类型只在群消息中返回,且永远为chain的第一个元素) |
+
 #### At
 
 ```json5
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 ccd6d2440..f50f00e11 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
@@ -36,6 +36,9 @@ data class UnKnownMessagePacketDTO(val msg: String) : MessagePacketDTO()
 
 // Message
 @Serializable
+@SerialName("Source")
+data class MessageSourceDTO(val uid: Long) : MessageDTO()
+@Serializable
 @SerialName("At")
 data class AtDTO(val target: Long, val display: String) : MessageDTO()
 @Serializable
@@ -85,6 +88,7 @@ fun MessageChainDTO.toMessageChain() =
 
 @UseExperimental(ExperimentalUnsignedTypes::class)
 fun Message.toDTO() = when (this) {
+    is MessageSource -> MessageSourceDTO(messageUid)
     is At -> AtDTO(target, display)
     is AtAll -> AtAllDTO(0L)
     is Face -> FaceDTO(id.value.toInt())
@@ -102,7 +106,7 @@ fun MessageDTO.toMessage() = when (this) {
     is PlainDTO -> PlainText(text)
     is ImageDTO -> Image(imageId)
     is XmlDTO -> XMLMessage(xml)
-    is UnknownMessageDTO -> PlainText("assert cannot reach")
+    is MessageSourceDTO, is UnknownMessageDTO -> PlainText("assert cannot reach")
 }
 
 
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 28a79f184..e3a1637ef 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
@@ -9,17 +9,32 @@
 
 package net.mamoe.mirai.api.http.queue
 
+import net.mamoe.mirai.message.GroupMessage
 import net.mamoe.mirai.message.MessagePacket
+import net.mamoe.mirai.message.data.MessageSource
+import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.ConcurrentLinkedDeque
 
 class MessageQueue : ConcurrentLinkedDeque<MessagePacket<*, *>>() {
 
+    val quoteCache = ConcurrentHashMap<Long, GroupMessage>()
+
     fun fetch(size: Int): List<MessagePacket<*, *>> {
         var count = size
+        quoteCache.clear()
         val ret = ArrayList<MessagePacket<*, *>>(count)
         while (!this.isEmpty() && count-- > 0) {
-            ret.add(this.pop())
+            val packet = pop()
+            ret.add(packet)
+
+            if (packet is GroupMessage) {
+                addCache(packet)
+            }
         }
         return ret
     }
+
+    private fun addCache(msg: GroupMessage) {
+        quoteCache[msg.message[MessageSource].messageUid] = msg
+    }
 }
\ No newline at end of file
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt
index 6a41fe76c..aa2beb5b8 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt
@@ -52,6 +52,12 @@ fun Application.messageModule() {
             call.respondStateCode(StateCode.Success)
         }
 
+        miraiVerify<SendDTO>("/quoteMessage") {
+            it.session.messageQueue.quoteCache[it.target]?.quoteReply(it.messageChain.toMessageChain())
+                ?: throw NoSuchElementException()
+            call.respondStateCode(StateCode.Success)
+        }
+
         miraiVerify<SendImageDTO>("sendImageMessage") {
             val bot = it.session.bot
             val contact = when {
@@ -72,12 +78,14 @@ fun Application.messageModule() {
             if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException
             val session = try {
                 SessionManager[sessionKey] as AuthedSession
-            } catch (e: TypeCastException) { throw NotVerifiedSessionException }
+            } catch (e: TypeCastException) {
+                throw NotVerifiedSessionException
+            }
 
             val type = parts.value("type")
             parts.file("img")?.apply {
                 val image = streamProvider().use {
-                    when(type) {
+                    when (type) {
                         "group" -> session.bot.groups.toList().random().uploadImage(it)
                         "friend" -> session.bot.qqs.toList().random().uploadImage(it)
                         else -> null
diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt
index 8b37ebd80..e46fbb1f5 100644
--- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt
+++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt
@@ -13,6 +13,7 @@ import kotlinx.serialization.*
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.modules.SerializersModule
 import net.mamoe.mirai.api.http.data.common.*
+import net.mamoe.mirai.message.data.MessageSource
 
 // 解析失败时直接返回null,由路由判断响应400状态
 @UseExperimental(ImplicitReflectionSerializer::class)
@@ -50,6 +51,7 @@ object MiraiJson {
             UnKnownMessagePacketDTO::class with UnKnownMessagePacketDTO.serializer()
         }
         polymorphic(MessageDTO.serializer()) {
+            MessageSourceDTO::class with MessageSourceDTO.serializer()
             AtDTO::class with AtDTO.serializer()
             AtAllDTO::class with AtAllDTO.serializer()
             FaceDTO::class with FaceDTO.serializer()