Browse Source

Support forward message refinement, close #623

Karlatemp 5 years ago
parent
commit
b659d55fec

+ 2 - 0
binary-compatibility-validator/api/binary-compatibility-validator.api

@@ -91,6 +91,8 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve
 	public abstract fun createImage (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image;
 	public synthetic fun deleteGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLjava/lang/String;)Lkotlin/Unit;
 	public fun deleteGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLjava/lang/String;)V
+	public fun downloadForwardMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;)Ljava/util/List;
+	public abstract fun downloadForwardMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public fun downloadLongMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain;
 	public abstract fun downloadLongMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public abstract fun getBotFactory ()Lnet/mamoe/mirai/BotFactory;

+ 9 - 0
mirai-core-api/src/commonMain/kotlin/IMirai.kt

@@ -179,6 +179,15 @@ public interface IMirai : LowLevelApiAccessor {
         resourceId: String,
     ): MessageChain
 
+    /**
+     * @since 2.3
+     */
+    @JvmBlockingBridge
+    public suspend fun downloadForwardMessage(
+        bot: Bot,
+        resourceId: String,
+    ): List<ForwardMessage.Node>
+
     /**
      * 通过好友验证
      *

+ 25 - 5
mirai-core/src/commonMain/kotlin/MiraiImpl.kt

@@ -965,6 +965,28 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
     )
 
     override suspend fun downloadLongMessage(bot: Bot, resourceId: String): MessageChain {
+        return downloadMultiMsgTransmit(bot, resourceId, ResourceKind.LONG_MESSAGE).msg
+            .toMessageChainNoSource(bot.id, 0, MessageSourceKind.GROUP)
+    }
+
+    override suspend fun downloadForwardMessage(bot: Bot, resourceId: String): List<ForwardMessage.Node> {
+        return downloadMultiMsgTransmit(bot, resourceId, ResourceKind.FORWARD_MESSAGE).msg.map { msg ->
+            ForwardMessage.Node(
+                senderId = msg.msgHead.fromUin,
+                time = msg.msgHead.msgTime,
+                senderName = msg.msgHead.groupInfo?.groupCard
+                    ?: msg.msgHead.fromNick.takeIf { it.isNotEmpty() }
+                    ?: msg.msgHead.fromUin.toString(),
+                messageChain = listOf(msg).toMessageChainNoSource(bot.id, 0, MessageSourceKind.GROUP)
+            )
+        }
+    }
+
+    private suspend fun downloadMultiMsgTransmit(
+        bot: Bot,
+        resourceId: String,
+        resourceKind: ResourceKind,
+    ): MsgTransmit.PbMultiMsgTransmit {
         bot.asQQAndroidBot()
         when (val resp = MultiMsg.ApplyDown(bot.client, 2, resourceId, 1).sendAndExpect(bot)) {
             is MultiMsg.ApplyDown.Response.RequireDownload -> {
@@ -977,7 +999,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
                         host = "https://ssl.htdata.qq.com",
                         port = 443,
                         times = 3,
-                        resourceKind = ResourceKind.LONG_MESSAGE,
+                        resourceKind = resourceKind,
                         channelKind = ChannelKind.HTTP
                     ) { host, _ ->
                         http.get("$host${origin.thumbDownPara}")
@@ -985,7 +1007,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
                 } else tryServersDownload(
                     bot = bot,
                     servers = origin.uint32DownIp.zip(origin.uint32DownPort),
-                    resourceKind = ResourceKind.LONG_MESSAGE,
+                    resourceKind = resourceKind,
                     channelKind = ChannelKind.HTTP
                 ) { ip, port ->
                     http.get("http://$ip:$port${origin.thumbDownPara}")
@@ -1011,9 +1033,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
                 }
 
                 val content = down.msgContent.ungzip()
-                val transmit = content.loadAs(MsgTransmit.PbMultiMsgTransmit.serializer())
-
-                return transmit.msg.toMessageChainNoSource(bot.id, 0, MessageSourceKind.GROUP)
+                return content.loadAs(MsgTransmit.PbMultiMsgTransmit.serializer())
             }
             MultiMsg.ApplyDown.Response.MessageTooLarge -> {
                 error("Message is too large and cannot download")

+ 45 - 6
mirai-core/src/commonMain/kotlin/message/LongMessageInternal.kt

@@ -32,18 +32,57 @@ internal data class LongMessageInternal internal constructor(override val conten
 }
 
 // internal runtime value, not serializable
-internal data class ForwardMessageInternal(override val content: String, val resId: String) : AbstractServiceMessage(), RefinableMessage {
+@Suppress("RegExpRedundantEscape", "UnnecessaryVariable")
+internal data class ForwardMessageInternal(override val content: String, val resId: String) : AbstractServiceMessage(),
+    RefinableMessage {
     override val serviceId: Int get() = 35
 
     override suspend fun refine(contact: Contact, context: MessageChain): Message {
-        // val bot = contact.bot.asQQAndroidBot()
-        // TODO: 2021/2/2 Support forward message refinement
-        // https://github.com/mamoe/mirai/issues/623
-        return this
+        val bot = contact.bot.asQQAndroidBot()
+
+        val msgXml = content.substringAfter("<msg", "")
+        val xmlHead = msgXml.substringBefore("<item")
+        val xmlFoot: String
+        val xmlContent = msgXml.substringAfter("<item").let {
+            xmlFoot = it.substringAfter("</item", "")
+            it.substringBefore("</item")
+        }
+        val brief = xmlHead.findField("brief")
+
+        val summary = SUMMARY_REGEX.find(xmlContent)?.let { it.groupValues[1] } ?: ""
+
+        val titles = TITLE_REGEX.findAll(xmlContent)
+            .map { it.groupValues[2].trim() }.toMutableList()
+
+        val title = titles.removeFirstOrNull() ?: ""
+
+        val preview = titles
+        val source = xmlFoot.findField("name")
+
+        return RichMessageOrigin(resId) + ForwardMessage(
+            preview = preview,
+            title = title,
+            brief = brief,
+            source = source,
+            summary = summary.trim(),
+            nodeList = Mirai.downloadForwardMessage(bot, resId)
+        )
     }
 
     companion object Key :
-        AbstractPolymorphicMessageKey<ServiceMessage, ForwardMessageInternal>(ServiceMessage, { it.safeCast() })
+        AbstractPolymorphicMessageKey<ServiceMessage, ForwardMessageInternal>(ServiceMessage, { it.safeCast() }) {
+
+        val SUMMARY_REGEX = """\<summary.*\>(.*?)\<\/summary\>""".toRegex()
+
+        @Suppress("SpellCheckingInspection")
+        val TITLE_REGEX = """\<title([A-Za-z\s#\"0-9\=]*)\>([\u0000-\uFFFF]*?)\<\/title\>""".toRegex()
+
+
+        fun String.findField(type: String): String {
+            return substringAfter("$type=\"", "")
+                .substringBefore("\"", "")
+        }
+    }
 }
 
 internal interface RefinableMessage : SingleMessage {

+ 9 - 2
mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt

@@ -391,11 +391,18 @@ private object ReceiveMessageTransformer {
 
                 val resId = findStringProperty("m_resid")
 
-                val msg = when(findStringProperty("multiMsgFlag").toIntOrNull()) {
+                val msg = if (resId.isEmpty()) {
+                    SimpleServiceMessage(35, content)
+                } else when (findStringProperty("multiMsgFlag").toIntOrNull()) {
                     1 -> LongMessageInternal(content, resId)
                     0 -> ForwardMessageInternal(content, resId)
                     else -> {
-                       SimpleServiceMessage(35, content)
+                        // from PC QQ
+                        if (findStringProperty("action") == "viewMultiMsg") {
+                            ForwardMessageInternal(content, resId)
+                        } else {
+                            SimpleServiceMessage(35, content)
+                        }
                     }
                 }