From 9a32b2690e62fb2b126305197482e827e3149f51 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Tue, 9 Feb 2021 18:36:06 +0800
Subject: [PATCH] RichMessageOrigin support for MusicShare. fix #950

---
 .../api/binary-compatibility-validator.api    |  1 +
 .../kotlin/message/data/RichMessage.kt        |  3 +
 .../kotlin/message/data/RichMessageOrigin.kt  |  8 ++-
 .../kotlin/message/LongMessageInternal.kt     |  3 +
 .../kotlin/message/ReceiveMessageHandler.kt   |  2 +-
 .../src/commonMain/kotlin/message/lightApp.kt | 62 ++++++++++---------
 6 files changed, 49 insertions(+), 30 deletions(-)

diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api
index d37eb5b9c..2f4bc8b5f 100644
--- a/binary-compatibility-validator/api/binary-compatibility-validator.api
+++ b/binary-compatibility-validator/api/binary-compatibility-validator.api
@@ -5050,6 +5050,7 @@ public final class net/mamoe/mirai/message/data/RichMessage$Key : net/mamoe/mira
 public final class net/mamoe/mirai/message/data/RichMessageKind : java/lang/Enum {
 	public static final field FORWARD Lnet/mamoe/mirai/message/data/RichMessageKind;
 	public static final field LONG Lnet/mamoe/mirai/message/data/RichMessageKind;
+	public static final field MUSIC_SHARE Lnet/mamoe/mirai/message/data/RichMessageKind;
 	public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/RichMessageKind;
 	public static fun values ()[Lnet/mamoe/mirai/message/data/RichMessageKind;
 }
diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt
index 094e40ed7..e318ed1fb 100644
--- a/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt
+++ b/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt
@@ -98,6 +98,9 @@ public interface RichMessage : MessageContent, ConstrainSingle {
 @Serializable
 @SerialName(LightApp.SERIAL_NAME)
 public data class LightApp(override val content: String) : RichMessage, CodableMessage {
+    // implementation notes: LightApp is always decoded as LightAppInternal
+    // which are transformed as RefinableMessage to LightApp
+
     public companion object Key : AbstractMessageKey<LightApp>({ it.safeCast() }) {
         public const val SERIAL_NAME: String = "LightApp"
     }
diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/RichMessageOrigin.kt b/mirai-core-api/src/commonMain/kotlin/message/data/RichMessageOrigin.kt
index 5ffea7d43..abf6c52f4 100644
--- a/mirai-core-api/src/commonMain/kotlin/message/data/RichMessageOrigin.kt
+++ b/mirai-core-api/src/commonMain/kotlin/message/data/RichMessageOrigin.kt
@@ -108,8 +108,14 @@ public enum class RichMessageKind {
 
     /**
      * 合并转发
+     * @see ForwardMessage
      */
     FORWARD,
 
-    // TODO: 2021/2/3 MusicShare  RichMessageKind
+    /**
+     * 音乐分享
+     * @see MusicShare
+     * @since 2.4
+     */
+    MUSIC_SHARE,
 }
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/message/LongMessageInternal.kt b/mirai-core/src/commonMain/kotlin/message/LongMessageInternal.kt
index 59c1b75ca..df6cff015 100644
--- a/mirai-core/src/commonMain/kotlin/message/LongMessageInternal.kt
+++ b/mirai-core/src/commonMain/kotlin/message/LongMessageInternal.kt
@@ -87,6 +87,9 @@ internal data class ForwardMessageInternal(override val content: String, val res
 
 internal interface RefinableMessage : SingleMessage {
 
+    /**
+     * This message [RefinableMessage] will be replaced by return value of [refine]
+     */
     suspend fun refine(
         contact: Contact,
         context: MessageChain,
diff --git a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt
index fe9e1a8fd..14eb70e0c 100644
--- a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt
@@ -278,7 +278,7 @@ private object ReceiveMessageTransformer {
             }
         }
 
-        list.add(LightApp(content).refine())
+        list.add(LightAppInternal(content))
     }
 
     private fun decodeCustomElem(
diff --git a/mirai-core/src/commonMain/kotlin/message/lightApp.kt b/mirai-core/src/commonMain/kotlin/message/lightApp.kt
index 74b10f7f7..dda2b9834 100644
--- a/mirai-core/src/commonMain/kotlin/message/lightApp.kt
+++ b/mirai-core/src/commonMain/kotlin/message/lightApp.kt
@@ -12,42 +12,48 @@ package net.mamoe.mirai.internal.message
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.Json
-import net.mamoe.mirai.message.data.LightApp
-import net.mamoe.mirai.message.data.MusicKind
-import net.mamoe.mirai.message.data.MusicShare
-import net.mamoe.mirai.message.data.SingleMessage
+import net.mamoe.mirai.contact.Contact
+import net.mamoe.mirai.message.data.*
+import net.mamoe.mirai.utils.safeCast
 
-private val json = Json {
-    ignoreUnknownKeys = true
-}
+internal data class LightAppInternal(
+    override val content: String
+) : RichMessage, RefinableMessage {
+    companion object Key :
+        AbstractPolymorphicMessageKey<RichMessage, LightAppInternal>(RichMessage, { it.safeCast() })
 
-internal fun LightApp.tryDeserialize(): LightAppStruct? {
-    return kotlin.runCatching {
-        json.decodeFromString(LightAppStruct.serializer(), this.content)
-    }.getOrNull()
-}
-
-/**
- * 识别 app 内容, 如果有必要
- */
-internal fun LightApp.refine(): SingleMessage {
-    val struct = tryDeserialize() ?: return this
-    struct.run {
-        if (meta.music != null) {
-            MusicKind.values().find { it.appId.toInt() == meta.music.appid }?.let { musicType ->
-                meta.music.run {
-                    return MusicShare(
-                        kind = musicType, title = title, summary = desc,
-                        jumpUrl = jumpUrl, pictureUrl = preview, musicUrl = musicUrl, brief = prompt
-                    )
+    override suspend fun refine(contact: Contact, context: MessageChain): Message {
+        val struct = tryDeserialize() ?: return LightApp(content)
+        struct.run {
+            if (meta.music != null) {
+                MusicKind.values().find { it.appId.toInt() == meta.music.appid }?.let { musicType ->
+                    meta.music.run {
+                        return RichMessageOrigin(
+                            LightApp(content),
+                            null,
+                            RichMessageKind.MUSIC_SHARE
+                        ) + MusicShare(
+                            kind = musicType, title = title, summary = desc,
+                            jumpUrl = jumpUrl, pictureUrl = preview, musicUrl = musicUrl, brief = prompt
+                        )
+                    }
                 }
             }
         }
 
-
+        return LightApp(content)
     }
+}
 
-    return this
+private val json = Json {
+    ignoreUnknownKeys = true
+    isLenient = true
+}
+
+internal fun LightAppInternal.tryDeserialize(): LightAppStruct? {
+    return kotlin.runCatching {
+        json.decodeFromString(LightAppStruct.serializer(), this.content)
+    }.getOrNull()
 }
 
 /*