From 08d1fc1f387df9b17b34d22669303489f6649483 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Mon, 16 May 2022 20:57:07 +0100
Subject: [PATCH] Add more MessageProtocolTest

---
 .../kotlin/message/data/MusicShare.kt         |  28 +-
 .../message/protocol/MessageProtocolFacade.kt |  21 +
 .../protocol/impl/MusicShareProtocol.kt       |  14 +-
 .../impl/AbstractMessageProtocolTest.kt       |  20 +-
 .../message/protocol/impl/FaceProtocolTest.kt |   5 +-
 .../protocol/impl/FlashImageProtocolTest.kt   | Bin 0 -> 5686 bytes
 .../protocol/impl/ImageProtocolTest.kt        | 565 ++++++++++++++++++
 .../protocol/impl/MarketFaceProtocolTest.kt   | 197 ++++++
 .../protocol/impl/MusicShareProtocolTest.kt   |  77 +++
 .../protocol/impl/PokeMessageProtocolTest.kt  |  85 +++
 .../protocol/impl/RichMessageProtocolTest.kt  |  83 +++
 .../message/protocol/impl/TextProtocolTest.kt |   4 +-
 12 files changed, 1062 insertions(+), 37 deletions(-)
 create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/FlashImageProtocolTest.kt
 create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/ImageProtocolTest.kt
 create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/MarketFaceProtocolTest.kt
 create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/MusicShareProtocolTest.kt
 create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/PokeMessageProtocolTest.kt
 create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/RichMessageProtocolTest.kt

diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt
index 7944da786..80478ddd3 100644
--- a/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt
+++ b/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt
@@ -64,25 +64,25 @@ public data class MusicShare(
 
        // Kotlin
        MusicShare(
-           kind = MusicKind.NeteaseCloudMusic,
-           title = "ファッション",
-           summary = "rinahamu/Yunomi",
-           brief = "",
-           jumpUrl = "http://music.163.com/song/1338728297/?userid=324076307",
-           pictureUrl = "http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg",
-           musicUrl = "http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307"
+           kind = NeteaseCloudMusic,
+           title = "ジェリーフィッシュ",
+           summary = "Yunomi/ローラーガール",
+           jumpUrl = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46",
+           pictureUrl = "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg",
+           musicUrl = "http://music.163.com/song/media/outer/url?id=562591636&&sc=wmv&tn=",
+           brief = "[分享]ジェリーフィッシュ",
        )
 
        // Java
        new MusicShare(
-           MusicKind.NeteaseCloudMusic,
-           "ファッション",
-           "rinahamu/Yunomi",
-           "http://music.163.com/song/1338728297/?userid=324076307",
-           "http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg",
-           "http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307"
+           NeteaseCloudMusic,
+           "ジェリーフィッシュ",
+           "Yunomi/ローラーガール",
+           "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46",
+           "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg",
+           "http://music.163.com/song/media/outer/url?id=562591636&&sc=wmv&tn=",
+           "[分享]ジェリーフィッシュ",
        );
-
      */
 
     public constructor(
diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt
index c51f50f8e..b71235510 100644
--- a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt
+++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt
@@ -11,6 +11,10 @@ package net.mamoe.mirai.internal.message.protocol
 
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.ContactOrBot
+import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
+import net.mamoe.mirai.internal.message.EmptyRefineContext
+import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight
+import net.mamoe.mirai.internal.message.RefineContext
 import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.internal.utils.runCoroutineInPlace
 import net.mamoe.mirai.message.data.*
@@ -50,6 +54,23 @@ internal interface MessageProtocolFacade {
     companion object INSTANCE : MessageProtocolFacade by MessageProtocolFacadeImpl()
 }
 
+internal fun MessageProtocolFacade.decodeAndRefineLight(
+    elements: List<ImMsgBody.Elem>,
+    groupIdOrZero: Long,
+    messageSourceKind: MessageSourceKind,
+    bot: Bot,
+    refineContext: RefineContext = EmptyRefineContext
+): MessageChain = decode(elements, groupIdOrZero, messageSourceKind, bot).refineLight(bot, refineContext)
+
+internal suspend fun MessageProtocolFacade.decodeAndRefineDeep(
+    elements: List<ImMsgBody.Elem>,
+    groupIdOrZero: Long,
+    messageSourceKind: MessageSourceKind,
+    bot: Bot,
+    refineContext: RefineContext = EmptyRefineContext
+): MessageChain = decode(elements, groupIdOrZero, messageSourceKind, bot).refineDeep(bot, refineContext)
+
+
 internal class MessageProtocolFacadeImpl(
     protocols: Iterable<MessageProtocol> = ServiceLoader.load(MessageProtocol::class.java)
 ) : MessageProtocolFacade {
diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt
index fd2fd2b3a..d1d646201 100644
--- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt
+++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt
@@ -9,8 +9,10 @@
 
 package net.mamoe.mirai.internal.message.protocol.impl
 
-import net.mamoe.mirai.internal.message.protocol.*
-import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
+import net.mamoe.mirai.internal.message.protocol.MessageEncoder
+import net.mamoe.mirai.internal.message.protocol.MessageEncoderContext
+import net.mamoe.mirai.internal.message.protocol.MessageProtocol
+import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
 import net.mamoe.mirai.message.data.MusicShare
 import net.mamoe.mirai.message.data.PlainText
 import net.mamoe.mirai.message.data.content
@@ -18,6 +20,7 @@ import net.mamoe.mirai.message.data.content
 internal class MusicShareProtocol : MessageProtocol() {
     override fun ProcessorCollector.collectProcessorsImpl() {
         add(Encoder())
+        // no decoder. refined from LightApp
 //        add(Decoder())
     }
 
@@ -29,11 +32,4 @@ internal class MusicShareProtocol : MessageProtocol() {
             processAlso(PlainText(data.content))
         }
     }
-
-    private class Decoder : MessageDecoder {
-        override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
-
-        }
-
-    }
 }
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt
index c3332d283..6db200857 100644
--- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt
@@ -30,7 +30,7 @@ import kotlin.contracts.contract
 
 internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest(), GroupExtensions {
 
-    protected abstract val protocol: MessageProtocol
+    protected abstract val protocols: Array<out MessageProtocol>
     protected var defaultTarget: ContactOrBot? = null
 
     private var decoderLoggerEnabled = false
@@ -60,12 +60,12 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
 
     protected fun doEncoderChecks(
         expectedStruct: List<ImMsgBody.Elem>,
-        protocol: MessageProtocol,
+        protocols: Array<out MessageProtocol>,
         encode: MessageProtocolFacade.() -> List<ImMsgBody.Elem>
     ) {
         asserter.assertEquals(
             expectedStruct,
-            facadeOf(protocol).encode(),
+            facadeOf(*protocols).encode(),
             message = "Failed to check single Protocol"
         )
         asserter.assertEquals(
@@ -87,12 +87,12 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
 
     protected fun doDecoderChecks(
         expectedChain: MessageChain,
-        protocol: MessageProtocol = this.protocol,
+        protocols: Array<out MessageProtocol> = this.protocols,
         decode: MessageProtocolFacade.() -> MessageChain
     ) {
         asserter.assertEquals(
             expectedChain.toList(),
-            facadeOf(protocol).decode().toList(),
+            facadeOf(*protocols).decode().toList(),
             message = "Failed to check single Protocol"
         )
         asserter.assertEquals(
@@ -104,9 +104,9 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
 
     protected fun doEncoderChecks(
         vararg expectedStruct: ImMsgBody.Elem,
-        protocol: MessageProtocol = this.protocol,
+        protocols: Array<out MessageProtocol> = this.protocols,
         encode: MessageProtocolFacade.() -> List<ImMsgBody.Elem>
-    ): Unit = doEncoderChecks(expectedStruct.toList(), protocol, encode)
+    ): Unit = doEncoderChecks(expectedStruct.toList(), protocols, encode)
 
 
     inner class ChecksBuilder {
@@ -175,7 +175,7 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
     @OptIn(ExperimentalCoroutinesApi::class)
     protected open fun Deferred<ChecksConfiguration>.doEncoderChecks() {
         val config = this.getCompleted()
-        doEncoderChecks(config.elems, protocol) {
+        doEncoderChecks(config.elems, protocols) {
             encode(
                 config.messageChain,
                 config.target,
@@ -188,8 +188,8 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
     @OptIn(ExperimentalCoroutinesApi::class)
     protected open fun Deferred<ChecksConfiguration>.doDecoderChecks() {
         val config = this.getCompleted()
-        doDecoderChecks(config.messageChain, protocol) {
-            decode(config.elems, config.groupIdOrZero, config.messageSourceKind, bot)
+        doDecoderChecks(config.messageChain, protocols) {
+            decodeAndRefineLight(config.elems, config.groupIdOrZero, config.messageSourceKind, bot)
         }
     }
 
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/FaceProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/FaceProtocolTest.kt
index bbae19c54..14e43f1b2 100644
--- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/FaceProtocolTest.kt
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/FaceProtocolTest.kt
@@ -10,6 +10,7 @@
 package net.mamoe.mirai.internal.message.protocol.impl
 
 import net.mamoe.mirai.internal.message.protocol.MessageProtocol
+import net.mamoe.mirai.internal.message.protocol.decodeAndRefineLight
 import net.mamoe.mirai.message.data.Face
 import net.mamoe.mirai.message.data.MessageSourceKind
 import net.mamoe.mirai.message.data.messageChainOf
@@ -17,7 +18,7 @@ import net.mamoe.mirai.utils.hexToBytes
 import org.junit.jupiter.api.Test
 
 internal class FaceProtocolTest : AbstractMessageProtocolTest() {
-    override val protocol: MessageProtocol = FaceProtocol()
+    override val protocols: Array<out MessageProtocol> = arrayOf(FaceProtocol())
 
     @Test
     fun `can encode`() {
@@ -42,7 +43,7 @@ internal class FaceProtocolTest : AbstractMessageProtocolTest() {
         doDecoderChecks(
             messageChainOf(Face(Face.YIN_XIAN)),
         ) {
-            decode(
+            decodeAndRefineLight(
                 listOf(
                     net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
                         face = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Face(
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/FlashImageProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/FlashImageProtocolTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2ee81d41c91ece86b32f163ef26b9dca64379e72
GIT binary patch
literal 5686
zcmeHL-EZ4e6z>y4ed13z%2PXaj^po443Q6~s<f`z=)?m;niJo|UL9Y{zG+ubB|so;
zn#7PmOhQ9Lg2IFZ-5b*;#venQcK-$E+$8PVq-nPyUZ|Da_}=q<j_<iYCx31l&OwDI
z>uJ~y1ZZlZcwSRA4Hmr)XRyqgLChoG4p|0X>_d~sA`Kf|!P87ZSrYI4_EYc4SKCj2
z-2LIx-S0l%{`0ThZyxU4y$AD`uR*C+bFVJJynETbTDk=FYvoG|6~M21`8^j_S(=4B
z1|9Z(f4p;dYj2A*>^}Lq_x*!8z=iFnn>+U(;<)|z&hxLI^}gKf-F~)n>(SoU=Dq~^
z0oO7Hyagf%Gst*1Z8G%LXMoc-M>8?=p|Os1)^}^ZerZ*QC~UGAB}L!~XnHX;7_@j7
z0~JBayX`;<w0IQpwJ>fYf4njbI}jqtF68s=Pz2qE(&U|dhmw}>gsB(i8xe2heYTn(
z#EMSlr>7IIx#G1M#7roXTanyK7z^~#i<Az_GGtJalndVEkrH;2XnGn)o{AAMDu>q;
z$|CDDSXyUkC(J0J$MGL;X0dO#J}`Tng02XoObOV{WnNwvEF1Fiw5{Ce#-YG52{9JM
zOG2g0T0CX0*9<1}EwfBujDs9NGxD+wYB&Z99Zbg1DPn^QP-<i%#i)<COw^fK_;?!d
zqLQZ})_>KDV3^AcWz*DKf17s&9FcwpE`mqIrPj=-<(b(z+zU6w(36?j*=cF#-4{Y6
zyRB{v*F($$wDI{#uJ1cUM4z?1ZX}kyw9N$CYjB}>zCTZ#GX+hb0~}OU)n_q#NwhwQ
zg+%E>)_$Fdm)PbY*Uea}=jJe)!w-P6^geF5S?MFmZpgchfzvYIv@G3~p1zQ7NLiDG
ztGE+V-hoo=r#!@+AB8LyhXZPK!^p1$thtgM@s&laGqZ1<Owo1hQ;&o-o~|4`^}Z*(
z{!CfuEN1O8_tzD7tWr{po3F!TqGMWL(@JGu*JuD_i!RLiDz=X0^@O28eQqSuJ{v&N
z7&w(vOW>42Entm7Gr+JwFM@7^jyDF4h9*A+d5<faq^goC8FUp4UGke?n4sgwkyM<b
z6!I5H18PI6=7LoMTLlYEI8f8UsX$eS8v0NML#36f1x^v18mJB!4wR9kgNar&FiTKS
z!M4Gank@q?6D*e$qH+seHHp)5Xx*riGqw(vN&%u>l&uxP#-J=xTe6_4Nm<mRLd62p
z1rv>$P|~1){<z>cB&(`Wl1f~h+EAAFWxPlCBm+6=Kt--s<Vt@m+E6n<HK8g=sE0I2
zg|*>IQ3Dk}CDMQl=p>GqHykk*9Bo{;axb(u-1lQkJ7)P{uTkv}&;H}xAh1)t{yw07
zH(>{e6iwtlcz*BK-7kLmaBu66{<+`#_R;R`TfN(NdiS4Pxzc;ExqJUkpXcvSKFj?p
z*&G~^qrM&8_ya~oHj{&<J}O8R3+bepl(nHY$*3&Fhy|Ez-SUiDz+-y6Vp8to6VUd=
zME$4hft7A1c&Fwy+40uMVk?YT-4g*7saCWt!z!0cR#mfA!?4tfWxA$gm?f=HHeBUK
z(mtj%q>3(5x8S3)^99w>9LrLz!uVqL`TH%Br3(qkXoZ65ShnpHPa{(&q;nArV?tjY
z`ZVakmUV*SupN7%n^FOCYgaU%#fjT+>j7Idvz#I%n333kPWt@0m+pLs?S_Gm`^96r
z)s4{j2}w)B=Av(slP}pmPt&x9j>!jjkhlWkh{>s23w`+%Vck#Q448a}z#vuUgwhQV
z`bB)<I6-L^endqpT6XcI+%>K;I+taQ-<*1DN}H-r-F#zQbtm?J!s)|Q9sM2C_laf9
z^5FyEV78(7yvpd+B<%yInLh25&@5xIe}=(iSzZl;A%Y?RhR_I6$1xa8t4ctH=taUX
zoDxnOA~Bkx;1G<_^b~l^t{#G9uZEEZ00yDd8BjTREjR-z^oPUfUkGPF<qW8dyrNAw
X+^DOb0Tp?+><|BMpyI~kfXc={p=P+&

literal 0
HcmV?d00001

diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/ImageProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/ImageProtocolTest.kt
new file mode 100644
index 000000000..9fcb885ed
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/ImageProtocolTest.kt
@@ -0,0 +1,565 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.message.protocol.impl
+
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.internal.message.protocol.MessageProtocol
+import net.mamoe.mirai.message.data.Image
+import net.mamoe.mirai.message.data.ImageType
+import net.mamoe.mirai.utils.hexToBytes
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+internal class ImageProtocolTest : AbstractMessageProtocolTest() {
+    override val protocols: Array<out MessageProtocol> = arrayOf(ImageProtocol())
+
+    @BeforeEach
+    fun `init group`() {
+        defaultTarget = bot.addGroup(123, 1230003).apply {
+            addMember(1230003, "user3", MemberPermission.OWNER)
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // receive from macOS client
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    fun `group Image receive from macOS`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace(
+                        filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg",
+                        fileId = -1866484636,
+                        useful = 1,
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        thumbUrl = "/gchatpic_new/123456/12345678-2428482660-A7CBB52943A2127CE42659D29BAA8515/198?term=2",
+                        origUrl = "/gchatpic_new/123456/12345678-2428482660-A7CBB52943A2127CE42659D29BAA8515/0?term=2",
+                        width = 904,
+                        height = 1214,
+                        size = 170426,
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/gchatpic_new/123456/12345678-2428482660-A7CBB52943A2127CE42659D29BAA8515/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") {
+                width = 904
+                height = 1214
+                size = 170426
+                type = ImageType.JPG
+                isEmoji = false
+            })
+            targetGroup()
+            useOrdinaryEquality()
+        }.doDecoderChecks()
+    }
+
+    @Test
+    fun `friend Image receive from macOS`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage(
+                        filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg",
+                        fileLen = 170426,
+                        downloadPath = "/123456-306012740-A7CBB52943A2127CE42659D29BAA8515",
+                        oldVerSendFile = "16 20 31 32 32 31 30 31 31 31 31 41 42 20 20 20 20 31 37 30 34 32 36 6B 7B 41 37 43 42 42 35 32 39 2D 34 33 41 32 2D 31 32 37 43 2D 45 34 32 36 2D 35 39 44 32 39 42 41 41 38 35 31 35 7D 2E 6A 70 67 77 2F 31 30 34 30 34 30 30 32 39 30 2D 33 30 36 30 31 32 37 34 30 2D 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 41".hexToBytes(),
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        picHeight = 1214,
+                        picWidth = 904,
+                        resId = "/123456-306012740-A7CBB52943A2127CE42659D29BAA8515",
+                        thumbUrl = "/offpic_new/123456//123456-306012740-A7CBB52943A2127CE42659D29BAA8515/198?term=2",
+                        origUrl = "/offpic_new/123456//123456-306012740-A7CBB52943A2127CE42659D29BAA8515/0?term=2",
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/offpic_new/123456//123456-306012740-A7CBB52943A2127CE42659D29BAA8515/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") {
+                width = 904
+                height = 1214
+                size = 170426
+                type = ImageType.JPG
+                isEmoji = false
+            })
+            targetFriend()
+        }.doDecoderChecks()
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // receive from Android
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    fun `group Image receive from Android`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace(
+                        filePath = "A7CBB52943A2127CE42659D29BAA8515.jpg",
+                        oldData = "15 36 20 38 36 65 41 31 42 61 66 34 35 64 37 38 63 36 66 31 65 39 30 33 66 20 20 20 20 20 20 35 30 57 4A 4B 53 53 71 52 79 61 52 46 42 7A 77 38 34 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 2E 6A 70 67 41".hexToBytes(),
+                        fileId = -1354377332,
+                        serverIp = 1864273983,
+                        serverPort = 80,
+                        fileType = 66,
+                        signature = "WJKSSqRyaRFBzw84".toByteArray(), /* 57 4A 4B 53 53 71 52 79 61 52 46 42 7A 77 38 34 */
+                        useful = 1,
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        thumbUrl = "/gchatpic_new/123456/622457678-2940589964-A7CBB52943A2127CE42659D29BAA8515/198?term=2",
+                        origUrl = "/gchatpic_new/123456/622457678-2940589964-A7CBB52943A2127CE42659D29BAA8515/0?term=2",
+                        imageType = 1000,
+                        width = 904,
+                        height = 1214,
+                        source = 103,
+                        size = 170426,
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/gchatpic_new/123456/622457678-2940589964-A7CBB52943A2127CE42659D29BAA8515/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                        pbReserve = "08 01 10 00 32 00 4A 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 06".hexToBytes(),
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") {
+                width = 904
+                height = 1214
+                size = 170426
+                type = ImageType.JPG
+                isEmoji = true
+            })
+            targetGroup()
+            useOrdinaryEquality()
+        }.doDecoderChecks()
+    }
+
+    @Test
+    fun `friend Image receive from Android`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage(
+                        filePath = "A7CBB52943A2127CE42659D29BAA8515.jpg",
+                        fileLen = 170426,
+                        downloadPath = "/123456-113241016-A7CBB52943A2127CE42659D29BAA8515",
+                        oldVerSendFile = "16 20 31 31 36 31 30 31 30 35 31 41 42 20 20 20 20 31 37 30 34 32 36 65 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 2E 6A 70 67 77 2F 31 30 34 30 34 30 30 32 39 30 2D 31 31 33 32 34 31 30 31 36 2D 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 41".hexToBytes(),
+                        imgType = 1000,
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        picHeight = 1214,
+                        picWidth = 904,
+                        resId = "/123456-113241016-A7CBB52943A2127CE42659D29BAA8515",
+                        thumbUrl = "/offpic_new/123456//123456-113241016-A7CBB52943A2127CE42659D29BAA8515/198?term=2",
+                        origUrl = "/offpic_new/123456//123456-113241016-A7CBB52943A2127CE42659D29BAA8515/0?term=2",
+                        bizType = 5,
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/offpic_new/123456//123456-113241016-A7CBB52943A2127CE42659D29BAA8515/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                        pbReserve = "08 01 10 00 32 00 42 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 06".hexToBytes(),
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") {
+                width = 904
+                height = 1214
+                size = 170426
+                type = ImageType.JPG
+                isEmoji = true
+            })
+            targetFriend()
+            useOrdinaryEquality()
+        }.doDecoderChecks()
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // receive from iOS
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    fun `group Image receive from iOS`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage(
+                        filePath = "C344D6240014DA35BB63A958BC435134.png",
+                        fileLen = 108536,
+                        downloadPath = "/123456-346835805-C344D6240014DA35BB63A958BC435134",
+                        oldVerSendFile = "16 20 31 31 36 31 30 31 30 35 31 41 42 20 20 20 20 31 30 38 35 33 36 65 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 2E 70 6E 67 77 2F 32 36 35 32 33 38 36 32 32 38 2D 33 34 36 38 33 35 38 30 35 2D 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 41".hexToBytes(),
+                        imgType = 1000,
+                        picMd5 = "C3 44 D6 24 00 14 DA 35 BB 63 A9 58 BC 43 51 34".hexToBytes(),
+                        picHeight = 1214,
+                        picWidth = 904,
+                        resId = "/123456-346835805-C344D6240014DA35BB63A958BC435134",
+                        thumbUrl = "/offpic_new/123456//123456-346835805-C344D6240014DA35BB63A958BC435134/198?term=2",
+                        origUrl = "/offpic_new/123456//123456-346835805-C344D6240014DA35BB63A958BC435134/0?term=2",
+                        bizType = 4,
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/offpic_new/123456//123456-346835805-C344D6240014DA35BB63A958BC435134/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                        pbReserve = "08 00 10 00 18 00 50 00 78 04".hexToBytes(),
+                    ),
+                )
+            )
+            message(Image("{C344D624-0014-DA35-BB63-A958BC435134}.jpg") {
+                width = 904
+                height = 1214
+                size = 108536
+                type = ImageType.JPG
+                isEmoji = false
+            })
+            targetGroup()
+            useOrdinaryEquality()
+        }.doDecoderChecks()
+    }
+
+    @Test
+    fun `friend Image receive from iOS`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage(
+                        filePath = "C344D6240014DA35BB63A958BC435134.png",
+                        fileLen = 108536,
+                        downloadPath = "/123455-346835805-C344D6240014DA35BB63A958BC435134",
+                        oldVerSendFile = "16 20 31 31 36 31 30 31 30 35 31 41 42 20 20 20 20 31 30 38 35 33 36 65 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 2E 70 6E 67 77 2F 32 36 35 32 33 38 36 32 32 38 2D 33 34 36 38 33 35 38 30 35 2D 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 41".hexToBytes(),
+                        imgType = 1000,
+                        picMd5 = "C3 44 D6 24 00 14 DA 35 BB 63 A9 58 BC 43 51 34".hexToBytes(),
+                        picHeight = 1214,
+                        picWidth = 904,
+                        resId = "/123455-346835805-C344D6240014DA35BB63A958BC435134",
+                        thumbUrl = "/offpic_new/123455//123455-346835805-C344D6240014DA35BB63A958BC435134/198?term=2",
+                        origUrl = "/offpic_new/123455//123455-346835805-C344D6240014DA35BB63A958BC435134/0?term=2",
+                        bizType = 4,
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/offpic_new/123455//123455-346835805-C344D6240014DA35BB63A958BC435134/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                        pbReserve = "08 00 10 00 18 00 50 00 78 04".hexToBytes(),
+                    ),
+                )
+            )
+            message(Image("{C344D624-0014-DA35-BB63-A958BC435134}.jpg") {
+                width = 904
+                height = 1214
+                size = 108536
+                type = ImageType.JPG
+                isEmoji = false
+            })
+            targetFriend()
+            useOrdinaryEquality()
+        }.doDecoderChecks()
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // receive from Windows
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    fun `group Image receive from Windows`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace(
+                        filePath = "{C344D624-0014-DA35-BB63-A958BC435134}.jpg",
+                        flag = "00 00 00 00".hexToBytes(),
+                        oldData = "15 36 20 39 32 6B 41 31 43 39 32 65 39 64 35 30 64 34 39 38 64 33 33 37 39 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 43 33 34 34 44 36 32 34 2D 30 30 31 34 2D 44 41 33 35 2D 42 42 36 33 2D 41 39 35 38 42 43 34 33 35 31 33 34 7D 2E 6A 70 67 41".hexToBytes(),
+                        fileId = -1830169331,
+                        serverIp = 1233990521,
+                        serverPort = 80,
+                        fileType = 67,
+                        useful = 1,
+                        picMd5 = "C3 44 D6 24 00 14 DA 35 BB 63 A9 58 BC 43 51 34".hexToBytes(),
+                        thumbUrl = "/gchatpic_new/123456/123456-2464797965-C344D6240014DA35BB63A958BC435134/198?term=2",
+                        origUrl = "/gchatpic_new/123456/123456-2464797965-C344D6240014DA35BB63A958BC435134/0?term=2",
+                        imageType = 1000,
+                        width = 904,
+                        height = 1214,
+                        size = 108536,
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/gchatpic_new/123456/123456-2464797965-C344D6240014DA35BB63A958BC435134/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                    ),
+                )
+            )
+            message(Image("{C344D624-0014-DA35-BB63-A958BC435134}.jpg") {
+                width = 904
+                height = 1214
+                size = 108536
+                type = ImageType.JPG
+                isEmoji = false
+            })
+            targetGroup()
+        }.doDecoderChecks()
+    }
+
+    @Test
+    fun `friend Image receive from Windows`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage(
+                        filePath = "J9R_MJL9@9Z02}QZ0LTTX77.jpg",
+                        fileLen = 108536,
+                        downloadPath = "/123456-2313394132-C344D6240014DA35BB63A958BC435134",
+                        oldVerSendFile = "16 20 31 31 37 31 30 31 30 36 31 43 42 20 20 20 20 31 30 38 35 33 36 65 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 2E 6A 70 67 78 2F 33 32 37 39 38 32 36 34 38 34 2D 32 33 31 33 33 39 34 31 33 32 2D 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 41".hexToBytes(),
+                        imgType = 1000,
+                        picMd5 = "C3 44 D6 24 00 14 DA 35 BB 63 A9 58 BC 43 51 34".hexToBytes(),
+                        picHeight = 1214,
+                        picWidth = 904,
+                        resId = "/123456-2313394132-C344D6240014DA35BB63A958BC435134",
+                        flag = "00 00 00 00".hexToBytes(),
+                        thumbUrl = "/offpic_new/123456//123456-2313394132-C344D6240014DA35BB63A958BC435134/198?term=2",
+                        origUrl = "/offpic_new/123456//123456-2313394132-C344D6240014DA35BB63A958BC435134/0?term=2",
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/offpic_new/123456//123456-2313394132-C344D6240014DA35BB63A958BC435134/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                    ),
+                )
+            )
+            message(Image("{C344D624-0014-DA35-BB63-A958BC435134}.jpg") {
+                width = 904
+                height = 1214
+                size = 108536
+                type = ImageType.JPG
+                isEmoji = false
+            })
+            targetFriend()
+            useOrdinaryEquality()
+        }.doDecoderChecks()
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // receive from iPadOS
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    fun `group Image receive from iPadOS`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace(
+                        filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg",
+                        oldData = "15 36 20 39 32 6B 41 31 E8 38 63 34 65 39 63 38 39 20 20 20 20 20 20 20 30 20 20 20 20 20 20 20 30 71 76 4A 51 79 6D 7A 37 4D 77 7A 7A 33 6A 74 4E 7B 41 37 43 42 42 35 32 39 2D 34 33 41 32 2D 31 32 37 43 2D 45 34 32 36 2D 35 39 44 32 39 42 41 41 38 35 31 35 7D 2E 6A 70 67 41".hexToBytes(),
+                        fileId = -1941005175,
+                        fileType = -24,
+                        signature = "qvJQymz7Mwzz3jtN".toByteArray(), /* 71 76 4A 51 79 6D 7A 37 4D 77 7A 7A 33 6A 74 4E */
+                        useful = 1,
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        thumbUrl = "/gchatpic_new/123456/123456-2353962121-A7CBB52943A2127CE42659D29BAA8515/198?term=2",
+                        origUrl = "/gchatpic_new/123456/123456-2353962121-A7CBB52943A2127CE42659D29BAA8515/0?term=2",
+                        imageType = 1000,
+                        width = 904,
+                        height = 1214,
+                        source = 203,
+                        size = 170426,
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/gchatpic_new/123456/123456-2353962121-A7CBB52943A2127CE42659D29BAA8515/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                        pbReserve = "08 01 10 00 18 00 2A 0C E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 4A 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 05".hexToBytes(),
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") {
+                width = 904
+                height = 1214
+                size = 170426
+                type = ImageType.JPG
+                isEmoji = true
+            })
+            targetGroup()
+        }.doDecoderChecks()
+    }
+
+    @Test
+    fun `friend Image receive from iPadOS`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage(
+                        filePath = "A7CBB52943A2127CE42659D29BAA8515.png",
+                        fileLen = 170426,
+                        downloadPath = "/1040400290-197707644-A7CBB52943A2127CE42659D29BAA8515",
+                        oldVerSendFile = "16 20 31 31 36 31 30 31 30 35 31 41 42 20 20 20 20 31 37 30 34 32 36 65 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 2E 70 6E 67 77 2F 31 30 34 30 34 30 30 32 39 30 2D 31 39 37 37 30 37 36 34 34 2D 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 41".hexToBytes(),
+                        imgType = 1000,
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        picHeight = 1214,
+                        picWidth = 904,
+                        resId = "/123456-197707644-A7CBB52943A2127CE42659D29BAA8515",
+                        thumbUrl = "/offpic_new/123456//123456-197707644-A7CBB52943A2127CE42659D29BAA8515/198?term=2",
+                        origUrl = "/offpic_new/123456//123456-197707644-A7CBB52943A2127CE42659D29BAA8515/0?term=2",
+                        bizType = 5,
+                        thumbWidth = 147,
+                        thumbHeight = 198,
+                        _400Url = "/offpic_new/123456//123456-197707644-A7CBB52943A2127CE42659D29BAA8515/400?term=2",
+                        _400Width = 285,
+                        _400Height = 384,
+                        pbReserve = "08 01 10 00 18 00 2A 0C E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 42 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 05".hexToBytes(),
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") {
+                width = 904
+                height = 1214
+                size = 170426
+                type = ImageType.JPG
+                isEmoji = true
+            })
+            targetFriend()
+            useOrdinaryEquality()
+        }.doDecoderChecks()
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // send without dimension
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    fun `group Image send without dimension`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace(
+                        filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg",
+                        flag = byteArrayOf(0, 0, 0, 0),
+                        fileType = 66,
+                        useful = 1,
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        bizType = 5,
+                        imageType = 1000,
+                        width = 1,
+                        height = 1,
+                        origin = 1,
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg"))
+            targetGroup()
+        }.doEncoderChecks()
+    }
+
+    @Test
+    fun `friend Image send without dimension`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage(
+                        filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg",
+                        downloadPath = "/000000000-000000000-A7CBB52943A2127CE42659D29BAA8515",
+                        imgType = 1000,
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        picHeight = 1,
+                        picWidth = 1,
+                        resId = "/000000000-000000000-A7CBB52943A2127CE42659D29BAA8515",
+                        original = 1,
+                        bizType = 5,
+                        pbReserve = "x".toByteArray(), /* 78 02 */
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg"))
+            targetFriend()
+        }.doEncoderChecks()
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // send with dimension
+    ///////////////////////////////////////////////////////////////////////////
+
+
+    @Test
+    fun `group Image send with dimension`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace(
+                        filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg",
+                        flag = byteArrayOf(0, 0, 0, 0),
+                        fileType = 66,
+                        useful = 1,
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        bizType = 5,
+                        imageType = 1000,
+                        width = 904,
+                        height = 1214,
+                        size = 170426,
+                        origin = 1,
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") {
+                width = 904
+                height = 1214
+                size = 170426
+                type = ImageType.JPG
+                isEmoji = false
+            })
+            targetGroup()
+        }.doEncoderChecks()
+    }
+
+    private fun ChecksBuilder.targetGroup() {
+        target(bot.addGroup(1, 1))
+    }
+
+    @Test
+    fun `friend Image send with dimension`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage(
+                        filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg",
+                        fileLen = 170426,
+                        downloadPath = "/000000000-000000000-A7CBB52943A2127CE42659D29BAA8515",
+                        imgType = 1000,
+                        picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(),
+                        picHeight = 1214,
+                        picWidth = 904,
+                        resId = "/000000000-000000000-A7CBB52943A2127CE42659D29BAA8515",
+                        original = 1,
+                        bizType = 5,
+                        pbReserve = "x".toByteArray(), /* 78 02 */
+                    ),
+                )
+            )
+            message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") {
+                width = 904
+                height = 1214
+                size = 170426
+                type = ImageType.JPG
+                isEmoji = false
+            })
+            targetFriend()
+        }.doEncoderChecks()
+    }
+
+    private fun ChecksBuilder.targetFriend() {
+        target(bot.addFriend(1))
+    }
+
+
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/MarketFaceProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/MarketFaceProtocolTest.kt
new file mode 100644
index 000000000..526bd46bb
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/MarketFaceProtocolTest.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.message.protocol.impl
+
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.internal.message.data.MarketFaceImpl
+import net.mamoe.mirai.internal.message.protocol.MessageProtocol
+import net.mamoe.mirai.message.data.Dice
+import net.mamoe.mirai.utils.hexToBytes
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+internal class MarketFaceProtocolTest : AbstractMessageProtocolTest() {
+    override val protocols: Array<out MessageProtocol> = arrayOf(MarketFaceProtocol(), TextProtocol())
+
+    @BeforeEach
+    fun `init group`() {
+        defaultTarget = bot.addGroup(123, 1230003).apply {
+            addMember(1230003, "user3", MemberPermission.OWNER)
+        }
+    }
+
+    @Test
+    fun `decode Dice`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace(
+                        faceName = "5B E9 9A 8F E6 9C BA E9 AA B0 E5 AD 90 5D".hexToBytes(),
+                        itemType = 6,
+                        faceInfo = 1,
+                        faceId = "48 23 D3 AD B1 5D F0 80 14 CE 5D 67 96 B7 6E E1".hexToBytes(),
+                        tabId = 11464,
+                        subType = 3,
+                        key = "409e2a69b16918f9".toByteArray(), /* 34 30 39 65 32 61 36 39 62 31 36 39 31 38 66 39 */
+                        imageWidth = 200,
+                        imageHeight = 200,
+                        mobileParam = "72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 34".hexToBytes(),
+                        pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(),
+                    ),
+                ),
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+                        str = "[随机骰子]",
+                    ),
+                ),
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags(
+                        pbReserve = "78 00 90 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 CA 04 00 D2 05 02 08 37".hexToBytes(),
+                    ),
+                )
+            )
+            message(Dice(5))
+            useOrdinaryEquality()
+        }.doDecoderChecks()
+    }
+
+
+    @Test
+    fun `encode Dice`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace(
+                        faceName = "5B E9 AA B0 E5 AD 90 5D".hexToBytes(), // [骰子]
+                        itemType = 6,
+                        faceInfo = 1,
+                        faceId = "48 23 D3 AD B1 5D F0 80 14 CE 5D 67 96 B7 6E E1".hexToBytes(),
+                        tabId = 11464,
+                        subType = 3,
+                        key = "409e2a69b16918f9".toByteArray(), /* 34 30 39 65 32 61 36 39 62 31 36 39 31 38 66 39 */
+                        imageWidth = 200,
+                        imageHeight = 200,
+                        mobileParam = "72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 34".hexToBytes(),
+                        pbReserve = "0A 06 08 C8 01 10 C8 01 40 01 58 00 62 09 23 30 30 30 30 30 30 30 30 6A 09 23 30 30 30 30 30 30 30 30".hexToBytes(),
+                    ),
+                ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+                        str = "[骰子]",
+                    ),
+                ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo(
+                        flags = 8,
+                        groupMask = 1,
+                    ),
+                )
+            )
+            message(Dice(5))
+        }.doEncoderChecks()
+    }
+
+
+    @Test
+    fun `encode decode MarketFace from Android`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace(
+                        faceName = "5B E5 8F 91 E5 91 86 5D".hexToBytes(),
+                        itemType = 6,
+                        faceInfo = 1,
+                        faceId = "71 26 44 B5 27 94 46 11 99 8A EC 31 86 75 19 D2".hexToBytes(),
+                        tabId = 10278,
+                        subType = 3,
+                        key = "726a53a5372b7289".toByteArray(), /* 37 32 36 61 35 33 61 35 33 37 32 62 37 32 38 39 */
+                        imageWidth = 200,
+                        imageHeight = 200,
+                        pbReserve = "0A 06 08 C8 01 10 C8 01 10 64 1A 0B 51 51 E5 A4 A7 E9 BB 84 E8 84 B8 22 40 68 74 74 70 73 3A 2F 2F 7A 62 2E 76 69 70 2E 71 71 2E 63 6F 6D 2F 69 70 3F 5F 77 76 3D 31 36 37 37 38 32 34 31 26 66 72 6F 6D 3D 61 69 6F 45 6D 6F 6A 69 4E 65 77 26 69 64 3D 31 30 38 39 31 30 2A 06 E6 9D A5 E8 87 AA 30 B5 BB B4 E3 0D 38 B5 BB B4 E3 0D 40 01 50 00".hexToBytes(),
+                    ),
+                ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+                        str = "[发呆]",
+                    ),
+                ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo(
+                        flags = 8,
+                        groupMask = 1,
+                    ),
+                )
+            )
+            // MarketFaceImpl 不支持手动构造
+            message(
+                MarketFaceImpl(
+                    net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace(
+                        faceName = "5B E5 8F 91 E5 91 86 5D".hexToBytes(),
+                        itemType = 6,
+                        faceInfo = 1,
+                        faceId = "71 26 44 B5 27 94 46 11 99 8A EC 31 86 75 19 D2".hexToBytes(),
+                        tabId = 10278,
+                        subType = 3,
+                        key = "726a53a5372b7289".toByteArray(), /* 37 32 36 61 35 33 61 35 33 37 32 62 37 32 38 39 */
+                        imageWidth = 200,
+                        imageHeight = 200,
+                        pbReserve = "0A 06 08 C8 01 10 C8 01 10 64 1A 0B 51 51 E5 A4 A7 E9 BB 84 E8 84 B8 22 40 68 74 74 70 73 3A 2F 2F 7A 62 2E 76 69 70 2E 71 71 2E 63 6F 6D 2F 69 70 3F 5F 77 76 3D 31 36 37 37 38 32 34 31 26 66 72 6F 6D 3D 61 69 6F 45 6D 6F 6A 69 4E 65 77 26 69 64 3D 31 30 38 39 31 30 2A 06 E6 9D A5 E8 87 AA 30 B5 BB B4 E3 0D 38 B5 BB B4 E3 0D 40 01 50 00".hexToBytes(),
+                    )
+                )
+            )
+        }.doBothChecks()
+    }
+
+    @Test
+    fun `encode decode MarketFace from macOS`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace(
+                        faceName = "5B E5 8F 91 E5 91 86 5D".hexToBytes(),
+                        itemType = 6,
+                        faceInfo = 1,
+                        faceId = "71 26 44 B5 27 94 46 11 99 8A EC 31 86 75 19 D2".hexToBytes(),
+                        tabId = 10278,
+                        subType = 3,
+                        key = "726a53a5372b7289".toByteArray(), /* 37 32 36 61 35 33 61 35 33 37 32 62 37 32 38 39 */
+                        imageWidth = 200,
+                        imageHeight = 200,
+                        pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(),
+                    ),
+                ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+                        str = "[发呆]",
+                    ),
+                ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo(
+                        flags = 8,
+                        groupMask = 1,
+                    ),
+                )
+            )
+            // MarketFaceImpl 不支持手动构造
+            message(
+                MarketFaceImpl(
+                    net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace(
+                        faceName = "5B E5 8F 91 E5 91 86 5D".hexToBytes(),
+                        itemType = 6,
+                        faceInfo = 1,
+                        faceId = "71 26 44 B5 27 94 46 11 99 8A EC 31 86 75 19 D2".hexToBytes(),
+                        tabId = 10278,
+                        subType = 3,
+                        key = "726a53a5372b7289".toByteArray(), /* 37 32 36 61 35 33 61 35 33 37 32 62 37 32 38 39 */
+                        imageWidth = 200,
+                        imageHeight = 200,
+                        pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(),
+                    )
+                )
+            )
+        }.doBothChecks()
+    }
+
+
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/MusicShareProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/MusicShareProtocolTest.kt
new file mode 100644
index 000000000..8381c155f
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/MusicShareProtocolTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.message.protocol.impl
+
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.internal.message.protocol.MessageProtocol
+import net.mamoe.mirai.message.data.LightApp
+import net.mamoe.mirai.message.data.MessageOrigin
+import net.mamoe.mirai.message.data.MessageOriginKind
+import net.mamoe.mirai.message.data.MusicKind.NeteaseCloudMusic
+import net.mamoe.mirai.message.data.MusicShare
+import net.mamoe.mirai.utils.hexToBytes
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+internal class MusicShareProtocolTest : AbstractMessageProtocolTest() {
+    override val protocols: Array<out MessageProtocol> =
+        arrayOf(TextProtocol(), MusicShareProtocol(), RichMessageProtocol())
+
+    @BeforeEach
+    fun `init group`() {
+        defaultTarget = bot.addGroup(123, 1230003).apply {
+            addMember(1230003, "user3", MemberPermission.OWNER)
+        }
+    }
+
+    @Test
+    fun `decode from Android`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    lightApp = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.LightAppElem(
+                        data = "01 78 9C 7D 53 5D 6B D4 40 14 FD 2F 81 EE 53 99 CC E4 63 76 53 08 42 15 A5 16 2D B5 08 16 23 61 3A 99 4D D3 6E 66 86 64 D2 A5 96 7D D9 80 F8 68 11 C1 37 1F 44 44 AB 4F 3E A8 45 7F 4D AA E2 BF F0 4E 58 B7 50 44 32 24 F7 DC B9 33 E7 7E 9C 9C 38 4C 6B 67 CD E1 AA 44 46 48 2E A4 41 B5 A9 1A 6E CA 3A 77 56 9D 4C D4 1C B6 7F BF FA 74 F1 F5 19 E0 A3 42 4C 01 97 4D 5D 70 0B 45 05 08 23 FB 10 C0 BA 52 A5 36 E0 7A F8 E3 E9 93 8B F3 B3 47 DD FC 4B 37 7F DB B5 EF BB F6 5B D7 BE E8 E6 AF BB B6 ED E6 9F BB F6 0D 84 97 C2 30 67 ED 64 71 1D 18 8C 9B 42 49 38 0F 9B 4C 66 95 2A B2 54 1F E6 A9 64 A5 58 78 B5 4E CD B1 06 44 7A 50 64 60 61 1C 44 21 1E 85 AB 0E 9C B7 91 84 86 5E 44 A9 EF 7B CB 12 76 1B A9 CA 22 71 BB F6 63 9F CB 3B FB 9E 7F E8 ED 33 B8 F8 A0 29 F5 FD 6A 02 91 FB C6 E8 7A 2D 71 13 F7 18 F5 99 21 42 7D 04 1D 4A 5C 58 B5 92 F9 B5 22 8B 43 EA 85 11 6C D0 01 34 2B DE DE C4 1B 5B 7C C5 BB B9 73 7D 63 6B 94 AF DF BD B5 E2 AD 4F F9 5E 9D AF F8 37 60 0D 6C DE D0 AD 1A CA 8B 47 68 88 02 6A EB B7 B7 5F 92 F6 9C 57 18 2D 1D 38 45 56 B0 C4 55 8D 11 55 E2 36 D5 E4 4A 06 B5 A8 C0 E1 7B 01 1E 52 1F 0F 07 35 8F A7 E5 D1 C0 C8 B8 1F 8A 58 8C 6D 49 A2 C9 DF CA 3C 8A A4 30 89 BB C9 76 77 F6 22 B5 BB FD 78 7F E2 3D 58 17 B7 F9 C1 E8 5E 1E C7 89 4B 70 14 85 04 3A 4A BC 90 62 32 C4 1E 3A D0 56 1B B5 6A 2A 2E EE D4 F9 46 66 45 B0 F4 A4 05 EF 67 78 D9 C7 02 E5 30 97 1C 71 09 35 68 01 6F DB 0E 1B 96 B8 18 27 6E 10 25 6E 08 DF 51 68 E9 16 C3 4C C1 4A 4B A4 E5 25 57 DA F4 BD 02 6C 58 0E C6 AF EF A7 3F 5F 3E BF 38 3F 5D EA D3 14 66 62 95 F2 7F DD 35 05 E4 47 3C 3F 08 E9 6C 06 A2 51 72 5C E4 56 7F FF 90 CF 58 55 53 56 41 85 F0 57 08 20 50 87 C2 16 47 30 E1 41 38 1E 31 3F DB C3 8C 92 30 8B 08 1B B2 60 8C 23 1A 8D B9 6F 73 E9 65 EA 48 55 95 6C E2 CC 66 7F 00 45 5B 33 67".hexToBytes(),
+                    ),
+                ),
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+                        str = """
+                                |[分享]ジェリーフィッシュ
+                                |Yunomi / ローラーガール
+                                |https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46
+                                |来自: 网易云音乐 """.trimMargin(),
+                    ),
+                ),
+            )
+            message(
+                MessageOrigin(
+                    LightApp(
+                        """
+                            {"app":"com.tencent.structmsg","desc":"音乐","view":"music","ver":"0.0.0.1","prompt":"[分享]ジェリーフィッシュ","meta":{"music":{"action":"","android_pkg_name":"","app_type":1,"appid":100495085,"ctime":1652966332,"desc":"Yunomi\/ローラーガール","jumpUrl":"https:\/\/y.music.163.com\/m\/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46","musicUrl":"http:\/\/music.163.com\/song\/media\/outer\/url?id=562591636&userid=324076307&sc=wmv&tn=","preview":"http:\/\/p1.music.126.net\/KaYSb9oYQzhl2XBeJcj8Rg==\/109951165125601702.jpg","sourceMsgId":"0","source_icon":"https:\/\/i.gtimg.cn\/open\/app_icon\/00\/49\/50\/85\/100495085_100_m.png","source_url":"","tag":"网易云音乐","title":"ジェリーフィッシュ","uin":123456}},"config":{"ctime":1652966332,"forward":true,"token":"101c45f8a3db0a615d91a7a4f0969fc3","type":"normal"}}
+                        """.trimIndent()
+                    ),
+                    null,
+                    MessageOriginKind.MUSIC_SHARE
+                ),
+                MusicShare(
+                    kind = NeteaseCloudMusic,
+                    title = "ジェリーフィッシュ",
+                    summary = "Yunomi/ローラーガール",
+                    jumpUrl = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46",
+                    pictureUrl = "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg",
+                    musicUrl = "http://music.163.com/song/media/outer/url?id=562591636&userid=324076307&sc=wmv&tn=",
+                    brief = "[分享]ジェリーフィッシュ",
+                )
+            )
+        }.doDecoderChecks()
+    }
+
+    // no encoder. specially handled, no test for now.
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/PokeMessageProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/PokeMessageProtocolTest.kt
new file mode 100644
index 000000000..e797e8894
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/PokeMessageProtocolTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.message.protocol.impl
+
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.internal.message.protocol.MessageProtocol
+import net.mamoe.mirai.message.data.PokeMessage
+import net.mamoe.mirai.utils.hexToBytes
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+internal class PokeMessageProtocolTest : AbstractMessageProtocolTest() {
+    override val protocols: Array<out MessageProtocol> = arrayOf(TextProtocol(), PokeMessageProtocol())
+
+    @BeforeEach
+    fun `init group`() {
+        defaultTarget = bot.addGroup(123, 1230003).apply {
+            addMember(1230003, "user3", MemberPermission.OWNER)
+        }
+    }
+
+    @Test
+    fun `test PokeMessage`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    commonElem = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CommonElem(
+                        serviceType = 2,
+                        pbElem = "08 01 18 00 20 FF FF FF FF 0F 2A 00 32 00 38 00 50 00".hexToBytes(),
+                        businessType = 1,
+                    ),
+                )
+            )
+            message(PokeMessage("戳一戳", 1, -1))
+            useOrdinaryEquality()
+        }.doDecoderChecks()
+    }
+
+
+    // Unsupported kinds
+//    @Test
+//    fun `test PokeMessage`() {
+//        buildChecks {
+//            elem(
+//                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+//                    commonElem = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CommonElem(
+//                        serviceType = 23,
+//                        pbElem = "08 0A 10 01 1A 09 E7 95 A5 E7 95 A5 E7 95 A5".hexToBytes(),
+//                        businessType = 10,
+//                    ),
+//                )
+//            )
+//            message(PokeMessage("略略略", -1, 1))
+//            useOrdinaryEquality()
+//        }.doDecoderChecks()
+//    }
+
+    @Test
+    fun encode() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    commonElem = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CommonElem(
+                        serviceType = 2,
+                        pbElem = "08 01 20 FF FF FF FF FF FF FF FF FF 01 2A 09 E6 88 B3 E4 B8 80 E6 88 B3 32 05 37 2E 32 2E 30".hexToBytes(),
+                        businessType = 1,
+                    ),
+                ),
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+                        str = "[戳一戳]请使用最新版手机QQ体验新功能。",
+                    ),
+                )
+            )
+            message(PokeMessage.ChuoYiChuo)
+        }.doEncoderChecks()
+    }
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/RichMessageProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/RichMessageProtocolTest.kt
new file mode 100644
index 000000000..3cbf0f8c2
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/RichMessageProtocolTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.message.protocol.impl
+
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.internal.message.protocol.MessageProtocol
+import net.mamoe.mirai.utils.hexToBytes
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+internal class RichMessageProtocolTest : AbstractMessageProtocolTest() {
+    override val protocols: Array<out MessageProtocol> = arrayOf(TextProtocol(), RichMessageProtocol())
+
+    @BeforeEach
+    fun `init group`() {
+        defaultTarget = bot.addGroup(123, 1230003).apply {
+            addMember(1230003, "user3", MemberPermission.OWNER)
+        }
+    }
+
+    @Test
+    fun `decode from Android`() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    richMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichMsg(
+                        template1 = "01 78 9C 7D 52 41 6F D3 30 14 FE 2B 96 A5 D1 13 71 92 36 59 03 71 2A 95 AA A8 4C 30 8D 82 44 85 50 E5 BA 9E EB 29 76 A2 C4 D9 28 C7 46 42 1C 99 10 12 37 0E 08 21 18 9C 38 00 13 FC 9A 14 10 FF 02 27 5D 27 4E 48 D6 F3 7B F6 F7 FC FC 7D EF 85 BD C7 32 06 C7 2C CB 45 A2 70 CB B1 EC 16 60 8A 26 73 A1 38 6E DD BF 37 BC DA 6D 81 5C 13 35 27 71 A2 18 6E 2D 59 DE 02 BD 28 94 39 07 39 CB 8E 05 65 A3 01 86 0E 04 9A C9 34 26 7A 13 BA 6D 08 08 D5 F5 A3 10 82 59 26 D8 21 86 0F 7F 3E 7B BA 3E 3F 7B 54 AD BE 56 AB 77 55 F9 A1 2A BF 57 E5 CB 6A F5 A6 2A CB 6A F5 A5 2A DF 42 90 27 45 46 D9 ED 9C 8F E6 18 DA 10 14 59 8C E1 42 EB 34 BF 86 D0 D2 92 45 2E A8 E5 F8 6D 8B 26 12 49 94 27 8A F7 C4 1C 7B BE EB 05 E6 D8 BF 42 64 7A BD A0 1A 1F EC D9 A3 7D BA E3 0E C7 37 46 FB 5D DE BF 73 73 C7 ED 9F D0 59 CE 77 DA 03 B3 1A 20 49 D3 E9 96 7D D7 DA B5 3A 3E 04 87 31 E1 4D 69 32 37 57 63 C1 55 13 C9 22 D6 C2 FC 6B 78 71 1D 85 C2 50 06 31 59 26 85 C6 D0 BD C0 6B 61 64 99 8A CD E7 C9 66 8F C2 54 50 5D 64 0C D0 C4 40 36 7C 0C 9D D4 D9 F2 71 7D 4B 31 8D F6 C8 64 3C 0B 92 C9 C1 93 45 EC 3E E8 B3 5B F4 A8 7B 97 63 8C 1C 3B 08 3C C7 F1 3D C7 F5 7C DB D9 B5 5D EB 28 E5 10 9C 34 65 16 8D 45 51 A8 85 8E 59 F4 1F 7D 43 B4 81 84 79 21 25 C9 96 D1 A4 50 89 14 A8 2A 3F 35 E0 F7 B5 5D 7D 6C FC B3 10 6D 51 21 AA B9 9A AC A6 39 40 11 C9 30 FC FD E3 F4 D7 AB 17 EB F3 D3 3F AF 3F AF BF 3D 87 40 D0 BA DD DB 5E 09 8B 6B 21 B9 45 15 4A 52 A6 50 2D 75 8D 40 B6 8D 3A 01 F2 6C D4 F5 0C 2F BB 13 78 76 D7 9B 1A 6F 2A AD 54 F1 7F 07 87 4C 37 FE 80 68 82 A1 36 93 C9 94 BE CC 31 45 0C 24 4D 6B 91 2F 0F 1B 1D 90 19 CF E8 2F D6 1B 08 55".hexToBytes(),
+                        serviceId = 1,
+                    ),
+                ),
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+                        str = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46",
+                    ),
+                ),
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags(
+                        pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 CA 04 00 D2 05 02 08 41".hexToBytes(),
+                    ),
+                )
+            )
+            message(
+                net.mamoe.mirai.message.data.SimpleServiceMessage(
+                    serviceId = 1,
+                    content = """
+                    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="1" templateID="123" action="" brief="[分享]ジェリーフィッシュ" sourceMsgId="0" url="https://y.music.163.com/m/song?id=562591636&amp;uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&amp;app_version=8.7.46" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2" advertiser_id="0" aid="0"><picture cover="http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg" w="0" h="0" /><title>ジェリーフィッシュ</title><summary>Yunomi/ローラーガール</summary></item><source name="网易云音乐" icon="https://i.gtimg.cn/open/app_icon/00/49/50/85/100495085_100_m.png" action="" a_actionData="tencent100495085://" appid="100495085" /></msg>
+                """.trimIndent()
+                )
+            )
+        }.doDecoderChecks()
+    }
+
+    @Test
+    fun encode() {
+        buildChecks {
+            elem(
+                net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+                    richMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichMsg(
+                        template1 = "01 78 9C 7D 52 41 6F D3 30 14 FE 2B 96 A5 D1 13 71 92 36 59 03 71 2A 95 AA A8 4C 30 8D 82 44 85 50 E5 BA 9E EB 29 76 A2 C4 D9 28 C7 46 42 1C 99 10 12 37 0E 08 21 18 9C 38 00 13 FC 9A 14 10 FF 02 27 5D 27 4E 48 D6 F3 7B F6 F7 FC FC 7D EF 85 BD C7 32 06 C7 2C CB 45 A2 70 CB B1 EC 16 60 8A 26 73 A1 38 6E DD BF 37 BC DA 6D 81 5C 13 35 27 71 A2 18 6E 2D 59 DE 02 BD 28 94 39 07 39 CB 8E 05 65 A3 01 86 0E 04 9A C9 34 26 7A 13 BA 6D 08 08 D5 F5 A3 10 82 59 26 D8 21 86 0F 7F 3E 7B BA 3E 3F 7B 54 AD BE 56 AB 77 55 F9 A1 2A BF 57 E5 CB 6A F5 A6 2A CB 6A F5 A5 2A DF 42 90 27 45 46 D9 ED 9C 8F E6 18 DA 10 14 59 8C E1 42 EB 34 BF 86 D0 D2 92 45 2E A8 E5 F8 6D 8B 26 12 49 94 27 8A F7 C4 1C 7B BE EB 05 E6 D8 BF 42 64 7A BD A0 1A 1F EC D9 A3 7D BA E3 0E C7 37 46 FB 5D DE BF 73 73 C7 ED 9F D0 59 CE 77 DA 03 B3 1A 20 49 D3 E9 96 7D D7 DA B5 3A 3E 04 87 31 E1 4D 69 32 37 57 63 C1 55 13 C9 22 D6 C2 FC 6B 78 71 1D 85 C2 50 06 31 59 26 85 C6 D0 BD C0 6B 61 64 99 8A CD E7 C9 66 8F C2 54 50 5D 64 0C D0 C4 40 36 7C 0C 9D D4 D9 F2 71 7D 4B 31 8D F6 C8 64 3C 0B 92 C9 C1 93 45 EC 3E E8 B3 5B F4 A8 7B 97 63 8C 1C 3B 08 3C C7 F1 3D C7 F5 7C DB D9 B5 5D EB 28 E5 10 9C 34 65 16 8D 45 51 A8 85 8E 59 F4 1F 7D 43 B4 81 84 79 21 25 C9 96 D1 A4 50 89 14 A8 2A 3F 35 E0 F7 B5 5D 7D 6C FC B3 10 6D 51 21 AA B9 9A AC A6 39 40 11 C9 30 FC FD E3 F4 D7 AB 17 EB F3 D3 3F AF 3F AF BF 3D 87 40 D0 BA DD DB 5E 09 8B 6B 21 B9 45 15 4A 52 A6 50 2D 75 8D 40 B6 8D 3A 01 F2 6C D4 F5 0C 2F BB 13 78 76 D7 9B 1A 6F 2A AD 54 F1 7F 07 87 4C 37 FE 80 68 82 A1 36 93 C9 94 BE CC 31 45 0C 24 4D 6B 91 2F 0F 1B 1D 90 19 CF E8 2F D6 1B 08 55".hexToBytes(),
+                        serviceId = 1,
+                    ),
+                ),
+            )
+            message(
+                net.mamoe.mirai.message.data.SimpleServiceMessage(
+                    serviceId = 1,
+                    content = """
+                    <?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="1" templateID="123" action="" brief="[分享]ジェリーフィッシュ" sourceMsgId="0" url="https://y.music.163.com/m/song?id=562591636&amp;uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&amp;app_version=8.7.46" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2" advertiser_id="0" aid="0"><picture cover="http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg" w="0" h="0" /><title>ジェリーフィッシュ</title><summary>Yunomi/ローラーガール</summary></item><source name="网易云音乐" icon="https://i.gtimg.cn/open/app_icon/00/49/50/85/100495085_100_m.png" action="" a_actionData="tencent100495085://" appid="100495085" /></msg>
+                """.trimIndent()
+                )
+            )
+        }.doEncoderChecks()
+    }
+
+    // no encoder. specially handled, no test for now.
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/TextProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/TextProtocolTest.kt
index 2184b95d5..45e12d479 100644
--- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/TextProtocolTest.kt
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/TextProtocolTest.kt
@@ -10,6 +10,7 @@
 package net.mamoe.mirai.internal.message.protocol.impl
 
 import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.internal.message.protocol.MessageProtocol
 import net.mamoe.mirai.message.data.At
 import net.mamoe.mirai.message.data.AtAll
 import net.mamoe.mirai.message.data.PlainText
@@ -18,8 +19,7 @@ import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 
 internal class TextProtocolTest : AbstractMessageProtocolTest() {
-
-    override val protocol = TextProtocol()
+    override val protocols: Array<out MessageProtocol> = arrayOf(TextProtocol())
 
     @BeforeEach
     fun `init group`() {