소스 검색

Fix serialization

Him188 5 년 전
부모
커밋
eacdfed97a

+ 37 - 5
mirai-core-api/src/commonMain/kotlin/message/MessageSerializer.kt

@@ -12,6 +12,7 @@ package net.mamoe.mirai.message
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.buildClassSerialDescriptor
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
 import kotlinx.serialization.modules.PolymorphicModuleBuilder
@@ -21,6 +22,7 @@ import kotlinx.serialization.modules.polymorphic
 import net.mamoe.mirai.Mirai
 import net.mamoe.mirai.message.data.*
 import net.mamoe.mirai.utils.MiraiExperimentalApi
+import net.mamoe.mirai.utils.MiraiInternalApi
 import kotlin.reflect.KClass
 
 @MiraiExperimentalApi
@@ -34,9 +36,12 @@ public interface MessageSerializer {
     public fun clearRegisteredSerializers()
 }
 
-internal object MessageSourceSerializer : KSerializer<MessageSource> {
+@MiraiInternalApi
+public open class MessageSourceSerializerImpl(serialName: String) : KSerializer<MessageSource> {
+    public companion object : MessageSourceSerializerImpl("net.mamoe.mirai.message.data.MessageSource")
+
     @Serializable
-    class SerialData(
+    internal class SerialData(
         val kind: MessageSourceKind,
         val bot: Long,
         val ids: IntArray,
@@ -47,7 +52,17 @@ internal object MessageSourceSerializer : KSerializer<MessageSource> {
         val originalMessage: MessageChain,
     )
 
-    override val descriptor: SerialDescriptor = SerialData.serializer().descriptor
+    override val descriptor: SerialDescriptor = buildClassSerialDescriptor(serialName) {
+        val desc = SerialData.serializer().descriptor
+        repeat(SerialData.serializer().descriptor.elementsCount) { index ->
+            element(
+                desc.getElementName(index),
+                desc.getElementDescriptor(index),
+                desc.getElementAnnotations(index),
+                desc.isElementOptional(index)
+            )
+        }
+    }
 //        buildClassSerialDescriptor("MessageSource") {
 //            element("bot", Long.serializer().descriptor)
 //            element("ids", ArraySerializer(Int.serializer()).descriptor)
@@ -96,7 +111,6 @@ private val builtInSerializersModule by lazy {
         contextual(CustomMessage::class, CustomMessage.serializer())
         contextual(CustomMessageMetadata::class, CustomMessageMetadata.serializer())
         contextual(Face::class, Face.serializer())
-        contextual(MessageSource::class, MessageSource.serializer())
         contextual(Image::class, Image.Serializer)
         contextual(PlainText::class, PlainText.serializer())
         contextual(QuoteReply::class, QuoteReply.serializer())
@@ -117,8 +131,12 @@ private val builtInSerializersModule by lazy {
         contextual(FlashImage::class, FlashImage.serializer())
 
         fun PolymorphicModuleBuilder<SingleMessage>.singleMessageSubclasses() {
+            // subclass(MessageSource::class, MessageSource.serializer())
         }
 
+        //   contextual(MessageSource::class, MessageSource.serializer())
+        polymorphicDefault(MessageSource::class) { MessageSource.serializer() }
+
         fun PolymorphicModuleBuilder<MessageMetadata>.messageMetadataSubclasses() {
             subclass(MessageSource::class, MessageSource.serializer())
             subclass(QuoteReply::class, QuoteReply.serializer())
@@ -148,7 +166,14 @@ private val builtInSerializersModule by lazy {
             subclass(FlashImage::class, FlashImage.serializer())
         }
 
+        contextual(Message::class, Message.Serializer)
+        // contextual(SingleMessage::class, SingleMessage.Serializer)
         contextual(MessageChain::class, MessageChain.Serializer)
+        contextual(MessageChainImpl::class, MessageChainImpl.serializer())
+
+        polymorphic(MessageChain::class) {
+            subclass(MessageChainImpl::class, MessageChainImpl.serializer())
+        }
         polymorphicDefault(MessageChain::class) { MessageChainImpl.serializer() }
 
         polymorphic(AbstractServiceMessage::class) {
@@ -156,9 +181,15 @@ private val builtInSerializersModule by lazy {
             subclass(ForwardMessageInternal::class, ForwardMessageInternal.serializer())
         }
 
+        //  polymorphic(SingleMessage::class) {
+        //      subclass(MessageSource::class, MessageSource.serializer())
+        //      default {
+        //          Message.Serializer.serializersModule.getPolymorphic(Message::class, it)
+        //      }
+        //  }
+
         polymorphicDefault(Image::class) { Image.Serializer }
 
-        contextual(Message::class, Message.Serializer)
         // polymorphic(Message::class) {
         //     subclass(PlainText::class, PlainText.serializer())
         // }
@@ -166,6 +197,7 @@ private val builtInSerializersModule by lazy {
             messageContentSubclasses()
             messageMetadataSubclasses()
             singleMessageSubclasses()
+            subclass(MessageChainImpl::class, MessageChainImpl.serializer())
         }
 
         //contextual(SingleMessage::class, SingleMessage.Serializer)

+ 12 - 6
mirai-core-api/src/commonMain/kotlin/message/data/Message.kt

@@ -209,8 +209,12 @@ public interface Message { // must be interface. Don't consider any changes.
          */
         @JvmOverloads
         @JvmStatic
-        public fun deserializeFromJsonString(string: String, json: Json = Json.Default): Message =
-            json.decodeFromString(Serializer, string)
+        public fun deserializeFromJsonString(
+            string: String,
+            json: Json = Json { serializersModule = Serializer.serializersModule }
+        ): Message {
+            return json.decodeFromString(Serializer, string)
+        }
 
         /**
          * 将 [Message] 序列化为 JSON 字符串.
@@ -218,8 +222,9 @@ public interface Message { // must be interface. Don't consider any changes.
          */
         @JvmOverloads
         @JvmStatic
-        public fun Message.serializeToJsonString(json: Json = Json.Default): String =
-            json.encodeToString(Serializer, this)
+        public fun Message.serializeToJsonString(
+            json: Json = Json { serializersModule = Serializer.serializersModule }
+        ): String = json.encodeToString(Serializer, this)
 
         /**
          * 将 [Message] 序列化为指定格式的字符串.
@@ -320,9 +325,10 @@ public inline operator fun Message.times(count: Int): MessageChain = this.repeat
 /**
  * 单个消息元素. 与之相对的是 [MessageChain], 是多个 [SingleMessage] 的集合.
  */
-@Serializable(SingleMessage.Serializer::class)
+// @Serializable(SingleMessage.Serializer::class)
 public interface SingleMessage : Message {
-    public object Serializer : KSerializer<SingleMessage> by PolymorphicSerializer(SingleMessage::class)
+    // @kotlinx.serialization.Serializer(forClass = SingleMessage::class)
+    //  public object Serializer : KSerializer<SingleMessage> by PolymorphicSerializer(SingleMessage::class)
 }
 
 /**

+ 38 - 2
mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt

@@ -13,12 +13,12 @@
 
 package net.mamoe.mirai.message.data
 
-import kotlinx.serialization.KSerializer
-import kotlinx.serialization.Serializable
+import kotlinx.serialization.*
 import kotlinx.serialization.builtins.ListSerializer
 import kotlinx.serialization.descriptors.SerialDescriptor
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.Json
 import net.mamoe.mirai.event.events.MessageEvent
 import net.mamoe.mirai.message.code.CodableMessage
 import net.mamoe.mirai.message.data.MessageSource.Key.quote
@@ -104,6 +104,42 @@ public interface MessageChain : Message, List<SingleMessage>, RandomAccess, Coda
     override fun appendMiraiCode(builder: StringBuilder) {
         forEach { it.safeCast<CodableMessage>()?.appendMiraiCode(builder) }
     }
+
+    public companion object {
+        /**
+         * 从 JSON 字符串解析 [MessageChain]
+         * @see serializeToJsonString
+         */
+        @JvmOverloads
+        @JvmStatic
+        public fun deserializeFromJsonString(
+            string: String,
+            json: Json = Json { serializersModule = Message.Serializer.serializersModule }
+        ): MessageChain {
+            return json.decodeFromString(Serializer, string)
+        }
+
+        /**
+         * 将 [MessageChain] 序列化为 JSON 字符串.
+         * @see deserializeFromJsonString
+         */
+        @JvmOverloads
+        @JvmStatic
+        public fun MessageChain.serializeToJsonString(
+            json: Json = Json { serializersModule = Message.Serializer.serializersModule }
+        ): String = json.encodeToString(Message.Serializer, this)
+
+        /**
+         * 将 [MessageChain] 序列化为指定格式的字符串.
+         *
+         * @see serializeToJsonString
+         * @see StringFormat.encodeToString
+         */
+        @ExperimentalSerializationApi
+        @JvmStatic
+        public fun MessageChain.serializeToString(format: StringFormat): String =
+            format.encodeToString(Serializer, this)
+    }
 }
 
 // region accessors

+ 2 - 2
mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt

@@ -25,7 +25,7 @@ import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.event.events.MessageEvent
 import net.mamoe.mirai.message.MessageReceipt
 import net.mamoe.mirai.message.MessageReceipt.Companion.recall
-import net.mamoe.mirai.message.MessageSourceSerializer
+import net.mamoe.mirai.message.MessageSourceSerializerImpl
 import net.mamoe.mirai.message.data.MessageSource.Key.isAboutFriend
 import net.mamoe.mirai.message.data.MessageSource.Key.isAboutGroup
 import net.mamoe.mirai.message.data.MessageSource.Key.isAboutTemp
@@ -69,7 +69,7 @@ import net.mamoe.mirai.utils.safeCast
  *
  * @see buildMessageSource 构造一个 [OfflineMessageSource]
  */
-@Serializable(MessageSourceSerializer::class)
+@Serializable(MessageSourceSerializerImpl.Companion::class)
 public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
     @ExperimentalMessageKey
     public final override val key: MessageKey<MessageSource>

+ 12 - 35
mirai-core/src/commonMain/kotlin/MiraiImpl.kt

@@ -25,7 +25,6 @@ import net.mamoe.mirai.internal.contact.*
 import net.mamoe.mirai.internal.message.*
 import net.mamoe.mirai.internal.network.highway.HighwayHelper
 import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo
-import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg
 import net.mamoe.mirai.internal.network.protocol.packet.chat.*
 import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
@@ -40,7 +39,6 @@ import net.mamoe.mirai.message.data.Image.Key.FRIEND_IMAGE_ID_REGEX_2
 import net.mamoe.mirai.message.data.Image.Key.GROUP_IMAGE_ID_REGEX
 import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
-import java.util.concurrent.atomic.AtomicBoolean
 import kotlin.math.absoluteValue
 import kotlin.random.Random
 
@@ -54,6 +52,15 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
             Message.Serializer.registerSerializer(OfflineGroupImage::class, OfflineGroupImage.serializer())
             Message.Serializer.registerSerializer(OfflineFriendImage::class, OfflineFriendImage.serializer())
             Message.Serializer.registerSerializer(MarketFaceImpl::class, MarketFaceImpl.serializer())
+            Message.Serializer.registerSerializer(
+                OfflineMessageSourceImplData::class,
+                OfflineMessageSourceImplData.serializer()
+            )
+
+            Message.Serializer.registerSerializer(
+                MessageSourceFromGroupImpl::class,
+                MessageSourceFromGroupImpl.serializer()
+            )
         }
     }
 
@@ -843,38 +850,8 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         time: Int,
         internalIds: IntArray,
         originalMessage: MessageChain
-    ): OfflineMessageSource {
-        return object : OfflineMessageSource(), MessageSourceInternal {
-            override val kind: MessageSourceKind get() = kind
-            override val ids: IntArray get() = ids
-            override val botId: Long get() = botId
-            override val time: Int get() = time
-            override val fromId: Long get() = fromUin
-            override val targetId: Long get() = targetUin
-            override val originalMessage: MessageChain get() = originalMessage
-            override val sequenceIds: IntArray = ids
-            override val internalIds: IntArray = internalIds
-
-            @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-            override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
-
-            override fun toJceData(): ImMsgBody.SourceMsg {
-                return ImMsgBody.SourceMsg(
-                    origSeqs = sequenceIds,
-                    senderUin = fromUin,
-                    toUin = 0,
-                    flag = 1,
-                    elems = originalMessage.toRichTextElems(
-                        null, //forGroup = kind == MessageSourceKind.GROUP,
-                        withGeneralFlags = false
-                    ),
-                    type = 0,
-                    time = time,
-                    pbReserve = EMPTY_BYTE_ARRAY,
-                    srcMsg = EMPTY_BYTE_ARRAY
-                )
-            }
-        }
-    }
+    ): OfflineMessageSource = OfflineMessageSourceImplData(
+        kind, ids, botId, time, fromUin, targetUin, originalMessage, internalIds
+    )
 
 }

+ 12 - 0
mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt

@@ -11,6 +11,8 @@
 
 package net.mamoe.mirai.internal.message
 
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.Friend
 import net.mamoe.mirai.contact.Member
@@ -22,6 +24,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.SourceMsg
 import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
 import net.mamoe.mirai.internal.utils._miraiContentToString
 import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
+import net.mamoe.mirai.message.MessageSourceSerializerImpl
 import net.mamoe.mirai.message.data.Message
 import net.mamoe.mirai.message.data.MessageChain
 import net.mamoe.mirai.message.data.MessageSource
@@ -32,12 +35,17 @@ import net.mamoe.mirai.utils.mapToIntArray
 import java.util.concurrent.atomic.AtomicBoolean
 
 internal interface MessageSourceInternal {
+    @Transient
     val sequenceIds: IntArray
+
+    @Transient
     val internalIds: IntArray // randomId
 
     @Deprecated("don't use this internally. Use sequenceId or random instead.", level = DeprecationLevel.ERROR)
+    @Transient
     val ids: IntArray
 
+    @Transient
     val isRecalledOrPlanned: AtomicBoolean
 
     fun toJceData(): ImMsgBody.SourceMsg
@@ -148,10 +156,14 @@ internal class MessageSourceFromTempImpl(
     override fun toJceData(): ImMsgBody.SourceMsg = jceData
 }
 
+@Serializable(MessageSourceFromGroupImpl.Serializer::class)
 internal data class MessageSourceFromGroupImpl(
     override val bot: Bot,
     private val msg: List<MsgComm.Msg>
 ) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceInternal {
+    object Serializer : MessageSourceSerializerImpl("net.mamoe.mirai.internal.message.MessageSourceFromGroupImpl")
+
+    @Transient
     override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
     override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
     override val internalIds: IntArray get() = msg.mapToIntArray { it.msgBody.richText.attr!!.random }

+ 66 - 0
mirai-core/src/commonMain/kotlin/message/offlineSourceImpl.kt

@@ -11,6 +11,8 @@
 
 package net.mamoe.mirai.internal.message
 
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
 import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
 import net.mamoe.mirai.internal.network.protocol.data.proto.SourceMsg
@@ -22,6 +24,70 @@ import net.mamoe.mirai.message.data.OfflineMessageSource
 import net.mamoe.mirai.utils.mapToIntArray
 import java.util.concurrent.atomic.AtomicBoolean
 
+@Serializable
+internal data class OfflineMessageSourceImplData(
+    override val kind: MessageSourceKind,
+    override val ids: IntArray,
+    override val botId: Long,
+    override val time: Int,
+    override val fromId: Long,
+    override val targetId: Long,
+    override val originalMessage: MessageChain,
+    override val internalIds: IntArray,
+) : OfflineMessageSource(), MessageSourceInternal {
+    override val sequenceIds: IntArray get() = ids
+
+    @Transient
+    @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+    override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
+
+    override fun toJceData(): ImMsgBody.SourceMsg {
+        return ImMsgBody.SourceMsg(
+            origSeqs = sequenceIds,
+            senderUin = fromId,
+            toUin = 0,
+            flag = 1,
+            elems = originalMessage.toRichTextElems(
+                null, //forGroup = kind == MessageSourceKind.GROUP,
+                withGeneralFlags = false
+            ),
+            type = 0,
+            time = time,
+            pbReserve = net.mamoe.mirai.internal.EMPTY_BYTE_ARRAY,
+            srcMsg = net.mamoe.mirai.internal.EMPTY_BYTE_ARRAY
+        )
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as OfflineMessageSourceImplData
+
+        if (kind != other.kind) return false
+        if (!ids.contentEquals(other.ids)) return false
+        if (botId != other.botId) return false
+        if (time != other.time) return false
+        if (fromId != other.fromId) return false
+        if (targetId != other.targetId) return false
+        if (originalMessage != other.originalMessage) return false
+        if (!internalIds.contentEquals(other.internalIds)) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = kind.hashCode()
+        result = 31 * result + ids.contentHashCode()
+        result = 31 * result + botId.hashCode()
+        result = 31 * result + time
+        result = 31 * result + fromId.hashCode()
+        result = 31 * result + targetId.hashCode()
+        result = 31 * result + originalMessage.hashCode()
+        result = 31 * result + internalIds.contentHashCode()
+        return result
+    }
+}
 
 internal class OfflineMessageSourceImplByMsg(
     // from other sources' originalMessage

+ 43 - 2
mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt

@@ -563,7 +563,7 @@ internal class ImMsgBody : ProtoBuf {
     ) : ProtoBuf
 
     @Serializable
-    internal class MarketFace(
+    internal data class MarketFace(
         @ProtoNumber(1) @JvmField val faceName: ByteArray = EMPTY_BYTE_ARRAY,
         @ProtoNumber(2) @JvmField val itemType: Int = 0,
         @ProtoNumber(3) @JvmField val faceInfo: Int = 0,
@@ -577,7 +577,48 @@ internal class ImMsgBody : ProtoBuf {
         @ProtoNumber(11) @JvmField val imageHeight: Int = 0,
         @ProtoNumber(12) @JvmField val mobileParam: ByteArray = EMPTY_BYTE_ARRAY,
         @ProtoNumber(13) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY
-    ) : ProtoBuf
+    ) : ProtoBuf {
+        @Suppress("DuplicatedCode")
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+
+            other as MarketFace
+
+            if (!faceName.contentEquals(other.faceName)) return false
+            if (itemType != other.itemType) return false
+            if (faceInfo != other.faceInfo) return false
+            if (!faceId.contentEquals(other.faceId)) return false
+            if (tabId != other.tabId) return false
+            if (subType != other.subType) return false
+            if (!key.contentEquals(other.key)) return false
+            if (!param.contentEquals(other.param)) return false
+            if (mediaType != other.mediaType) return false
+            if (imageWidth != other.imageWidth) return false
+            if (imageHeight != other.imageHeight) return false
+            if (!mobileParam.contentEquals(other.mobileParam)) return false
+            if (!pbReserve.contentEquals(other.pbReserve)) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            var result = faceName.contentHashCode()
+            result = 31 * result + itemType
+            result = 31 * result + faceInfo
+            result = 31 * result + faceId.contentHashCode()
+            result = 31 * result + tabId
+            result = 31 * result + subType
+            result = 31 * result + key.contentHashCode()
+            result = 31 * result + param.contentHashCode()
+            result = 31 * result + mediaType
+            result = 31 * result + imageWidth
+            result = 31 * result + imageHeight
+            result = 31 * result + mobileParam.contentHashCode()
+            result = 31 * result + pbReserve.contentHashCode()
+            return result
+        }
+    }
 
     @Serializable
     internal class MarketTrans(

+ 46 - 9
mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt

@@ -13,17 +13,20 @@ import kotlinx.serialization.KSerializer
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.serializer
 import net.mamoe.mirai.Mirai
+import net.mamoe.mirai.internal.message.MarketFaceImpl
+import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.message.data.*
 import org.junit.jupiter.api.BeforeAll
 import org.junit.jupiter.api.Test
 import kotlin.test.assertEquals
 
 internal class MessageSerializationTest {
-    private val module = Message.Serializer.serializersModule
-    private val format = Json {
-        serializersModule = module
-        useArrayPolymorphism = false
-    }
+    private val module get() = Message.Serializer.serializersModule
+    private val format
+        get() = Json {
+            serializersModule = module
+            useArrayPolymorphism = false // ?
+        }
 
     private inline fun <reified T : Any> T.serialize(serializer: KSerializer<T> = module.serializer()): String {
         return format.encodeToString(serializer, this)
@@ -34,23 +37,57 @@ internal class MessageSerializationTest {
     }
 
     private inline fun <reified T : Any> testSerialization(t: T, serializer: KSerializer<T> = module.serializer()) {
+        val deserialized = t.serialize(serializer).deserialize(serializer)
         assertEquals(
             t,
-            t.serialize(serializer).deserialize(serializer),
-            message = "serialized string: ${t.serialize(serializer)}"
+            deserialized,
+            message = "serialized string:   ${t.serialize(serializer)}\ndeserialized string: ${
+                deserialized.serialize(
+                    serializer
+                )
+            }\n"
         )
     }
 
+
+    private val image = Image("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai")
     private val testMessageContentInstances: Array<out MessageContent> = arrayOf(
         PlainText("test"),
         At(123456),
         AtAll,
-        Image("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai"),
+        image,
+        image.toForwardMessage(1L, "test"),
+        VipFace(VipFace.AiXin, 1),
+        PokeMessage.BaoBeiQiu,
+        Face(Face.AI_NI),
+        MarketFaceImpl(ImMsgBody.MarketFace()),
+        image.flash(),
+    )
+
+    private val emptySource = Mirai.constructMessageSource(
+        1L,
+        MessageSourceKind.FRIEND,
+        1,
+        2,
+        intArrayOf(1),
+        1,
+        intArrayOf(1),
+        messageChainOf()
     )
 
     @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
     private val testConstrainSingleMessageInstances: Array<out ConstrainSingle> = arrayOf(
-        LongMessage("content", "resId")
+        LongMessage("content", "resId"),
+        Mirai.constructMessageSource(
+            1L,
+            MessageSourceKind.FRIEND,
+            1,
+            2,
+            intArrayOf(1),
+            1,
+            intArrayOf(1),
+            messageChainOf(emptySource, image)
+        ),
     )
 
     companion object {