Forráskód Böngészése

Message serialization

Him188 5 éve
szülő
commit
f59fcf7d5d

+ 117 - 34
mirai-core-api/src/commonMain/kotlin/message/MessageSerializer.kt

@@ -14,12 +14,12 @@ import kotlinx.serialization.Serializable
 import kotlinx.serialization.descriptors.SerialDescriptor
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.modules.PolymorphicModuleBuilder
 import kotlinx.serialization.modules.SerializersModule
 import kotlinx.serialization.modules.plus
-import kotlinx.serialization.modules.serializersModuleOf
+import kotlinx.serialization.modules.polymorphic
 import net.mamoe.mirai.Mirai
 import net.mamoe.mirai.message.data.*
-import net.mamoe.mirai.message.data.CombinedMessage
 import net.mamoe.mirai.utils.MiraiExperimentalApi
 import kotlin.reflect.KClass
 
@@ -29,6 +29,8 @@ public interface MessageSerializer {
 
     public fun <M : Message> registerSerializer(clazz: KClass<M>, serializer: KSerializer<M>)
 
+    public fun registerSerializers(serializersModule: SerializersModule)
+
     public fun clearRegisteredSerializers()
 }
 
@@ -80,54 +82,135 @@ internal object MessageSourceSerializer : KSerializer<MessageSource> {
 }
 
 
-private val builtInSerializersModule = SerializersModule {
-    // In case Proguard or something else obfuscated the Kotlin metadata, providing the serializesrs explicity will help.
-    contextual(At::class, At.serializer())
-    contextual(AtAll::class, AtAll.serializer())
-    contextual(CombinedMessage::class, CombinedMessage.serializer())
-    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())
+private val builtInSerializersModule by lazy {
+    SerializersModule {
+        // non-Message classes
+        contextual(RawForwardMessage::class, RawForwardMessage.serializer())
+        contextual(ForwardMessage.Node::class, ForwardMessage.Node.serializer())
+        contextual(VipFace.Kind::class, VipFace.Kind.serializer())
+
+
+        // In case Proguard or something else obfuscated the Kotlin metadata, providing the serializers explicitly will help.
+        contextual(At::class, At.serializer())
+        contextual(AtAll::class, AtAll.serializer())
+        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())
+
+        contextual(ForwardMessage::class, ForwardMessage.serializer())
+
+
+        contextual(LightApp::class, LightApp.serializer())
+        contextual(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
+        contextual(AbstractServiceMessage::class, AbstractServiceMessage.serializer())
+        contextual(LongMessage::class, LongMessage.serializer())
+        contextual(ForwardMessageInternal::class, ForwardMessageInternal.serializer())
 
-    contextual(ForwardMessage::class, ForwardMessage.serializer())
-    contextual(RawForwardMessage::class, RawForwardMessage.serializer())
-    contextual(ForwardMessage.Node::class, ForwardMessage.Node.serializer())
+        contextual(PttMessage::class, PttMessage.serializer())
+        contextual(Voice::class, Voice.serializer())
 
+        contextual(HummerMessage::class, HummerMessage.serializer())
+        contextual(PokeMessage::class, PokeMessage.serializer())
+        contextual(VipFace::class, VipFace.serializer())
+        contextual(FlashImage::class, FlashImage.serializer())
 
-    contextual(LightApp::class, LightApp.serializer())
-    contextual(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
-    contextual(AbstractServiceMessage::class, AbstractServiceMessage.serializer())
-    contextual(LongMessage::class, LongMessage.serializer())
-    contextual(ForwardMessageInternal::class, ForwardMessageInternal.serializer())
+        fun PolymorphicModuleBuilder<SingleMessage>.singleMessageSubclasses() {
+        }
+
+        fun PolymorphicModuleBuilder<MessageMetadata>.messageMetadataSubclasses() {
+            subclass(MessageSource::class, MessageSource.serializer())
+            subclass(QuoteReply::class, QuoteReply.serializer())
+        }
 
-    contextual(PttMessage::class, PttMessage.serializer())
-    contextual(Voice::class, Voice.serializer())
+        fun PolymorphicModuleBuilder<MessageContent>.messageContentSubclasses() {
+            subclass(At::class, At.serializer())
+            subclass(AtAll::class, AtAll.serializer())
+            subclass(Face::class, Face.serializer())
+            subclass(Image::class, Image.Serializer)
+            subclass(PlainText::class, PlainText.serializer())
 
-    contextual(HummerMessage::class, HummerMessage.serializer())
-    contextual(PokeMessage::class, PokeMessage.serializer())
-    contextual(VipFace::class, VipFace.serializer())
-    contextual(FlashImage::class, FlashImage.serializer())
-    contextual(VipFace.Kind::class, VipFace.Kind.serializer())
+            subclass(ForwardMessage::class, ForwardMessage.serializer())
 
 
-    contextual(Message::class, Message.Serializer)
-    contextual(MessageChain::class, MessageChain.Serializer)
+            subclass(LightApp::class, LightApp.serializer())
+            subclass(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
+            subclass(LongMessage::class, LongMessage.serializer())
+            subclass(ForwardMessageInternal::class, ForwardMessageInternal.serializer())
+
+            //  subclass(PttMessage::class, PttMessage.serializer())
+            subclass(Voice::class, Voice.serializer())
+
+            // subclass(HummerMessage::class, HummerMessage.serializer())
+            subclass(PokeMessage::class, PokeMessage.serializer())
+            subclass(VipFace::class, VipFace.serializer())
+            subclass(FlashImage::class, FlashImage.serializer())
+        }
+
+        contextual(MessageChain::class, MessageChain.Serializer)
+        polymorphicDefault(MessageChain::class) { MessageChainImpl.serializer() }
+
+        polymorphic(AbstractServiceMessage::class) {
+            subclass(LongMessage::class, LongMessage.serializer())
+            subclass(ForwardMessageInternal::class, ForwardMessageInternal.serializer())
+        }
+
+        polymorphicDefault(Image::class) { Image.Serializer }
+
+        contextual(Message::class, Message.Serializer)
+        // polymorphic(Message::class) {
+        //     subclass(PlainText::class, PlainText.serializer())
+        // }
+        polymorphic(Message::class) {
+            messageContentSubclasses()
+            messageMetadataSubclasses()
+            singleMessageSubclasses()
+        }
+
+        //contextual(SingleMessage::class, SingleMessage.Serializer)
+        // polymorphic(SingleMessage::class, SingleMessage.Serializer) {
+        //     messageContentSubclasses()
+        //     messageMetadataSubclasses()
+        //     singleMessageSubclasses()
+        // }
+
+        // contextual(MessageContent::class, MessageContent.Serializer)
+        // polymorphic(MessageContent::class, MessageContent.Serializer) {
+        //     messageContentSubclasses()
+        // }
+
+        // contextual(MessageMetadata::class, MessageMetadata.Serializer)
+        // polymorphic(MessageMetadata::class, MessageMetadata.Serializer) {
+        //     messageMetadataSubclasses()
+        // }
+    }
 }
 
 internal object MessageSerializerImpl : MessageSerializer {
-    override var serializersModule: SerializersModule = builtInSerializersModule
+    @Volatile
+    private var serializersModuleField: SerializersModule? = null
+    override val serializersModule: SerializersModule get() = serializersModuleField ?: builtInSerializersModule
 
     @Synchronized
     override fun <M : Message> registerSerializer(clazz: KClass<M>, serializer: KSerializer<M>) {
-        serializersModule = serializersModule.plus(serializersModuleOf(clazz, serializer))
+        serializersModuleField = serializersModule.plus(SerializersModule {
+            contextual(clazz, serializer)
+            polymorphic(Message::class) {
+                subclass(clazz, serializer)
+            }
+        })
+    }
+
+    @Synchronized
+    override fun registerSerializers(serializersModule: SerializersModule) {
+        serializersModuleField = serializersModule
     }
 
     @Synchronized
     override fun clearRegisteredSerializers() {
-        serializersModule = builtInSerializersModule
+        serializersModuleField = builtInSerializersModule
     }
 }

+ 16 - 6
mirai-core-api/src/commonMain/kotlin/message/data/Image.kt

@@ -28,8 +28,11 @@ import kotlinx.serialization.KSerializer
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.builtins.serializer
 import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.buildClassSerialDescriptor
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.encoding.decodeStructure
+import kotlinx.serialization.encoding.encodeStructure
 import net.mamoe.kjbb.JvmBlockingBridge
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.IMirai
@@ -97,14 +100,21 @@ public interface Image : Message, MessageContent, CodableMessage {
     public val imageId: String
 
     public object Serializer : KSerializer<Image> {
-        override val descriptor: SerialDescriptor = String.serializer().descriptor
-        override fun deserialize(decoder: Decoder): Image {
-            val id = String.serializer().deserialize(decoder)
-            return Image(id)
+        override val descriptor: SerialDescriptor = buildClassSerialDescriptor("net.mamoe.mirai.message.data.Image") {
+            element("imageId", String.serializer().descriptor)
         }
 
-        override fun serialize(encoder: Encoder, value: Image) {
-            String.serializer().serialize(encoder, value.imageId)
+        override fun deserialize(decoder: Decoder): Image = decoder.decodeStructure(descriptor) {
+            val imageId = if (decodeSequentially()) {
+                decodeStringElement(descriptor, 0)
+            } else {
+                decodeStringElement(descriptor, decodeElementIndex(descriptor))
+            }
+            Image(imageId)
+        }
+
+        override fun serialize(encoder: Encoder, value: Image): Unit = encoder.encodeStructure(descriptor) {
+            encodeStringElement(descriptor, 0, value.imageId)
         }
     }
 

+ 10 - 1
mirai-core-api/src/commonMain/kotlin/message/data/Message.kt

@@ -274,7 +274,10 @@ public inline operator fun Message.times(count: Int): MessageChain = this.repeat
 /**
  * 单个消息元素. 与之相对的是 [MessageChain], 是多个 [SingleMessage] 的集合.
  */
-public interface SingleMessage : Message
+@Serializable(SingleMessage.Serializer::class)
+public interface SingleMessage : Message {
+    public object Serializer : KSerializer<SingleMessage> by PolymorphicSerializer(SingleMessage::class)
+}
 
 /**
  * 消息元数据, 即不含内容的元素.
@@ -289,11 +292,14 @@ public interface SingleMessage : Message
  *
  * @see ConstrainSingle 约束一个 [MessageChain] 中只存在这一种类型的元素
  */
+@Serializable(MessageMetadata.Serializer::class)
 public interface MessageMetadata : SingleMessage {
     /**
      * 返回空字符串
      */
     override fun contentToString(): String = ""
+
+    public object Serializer : KSerializer<MessageMetadata> by PolymorphicSerializer(MessageMetadata::class)
 }
 
 /**
@@ -378,9 +384,12 @@ public abstract class AbstractPolymorphicMessageKey<out B : SingleMessage, out M
  * @see ForwardMessage 合并转发
  * @see Voice 语音
  */
+@Serializable(MessageContent.Serializer::class)
 public interface MessageContent : SingleMessage {
     @ExperimentalMessageKey
     public companion object Key : AbstractMessageKey<MessageContent>({ it.safeCast() })
+
+    public object Serializer : KSerializer<MessageContent> by PolymorphicSerializer(MessageContent::class)
 }
 
 /**

+ 4 - 1
mirai-core/src/commonMain/kotlin/MiraiImpl.kt

@@ -56,7 +56,10 @@ import kotlin.random.Random
 internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
     companion object INSTANCE : MiraiImpl() {
         @Suppress("ObjectPropertyName", "unused")
-        private val _init = Mirai.let { }
+        private val _init = Mirai.let {
+            Message.Serializer.registerSerializer(OfflineGroupImage::class, OfflineGroupImage.serializer())
+            Message.Serializer.registerSerializer(OfflineFriendImage::class, OfflineFriendImage.serializer())
+        }
     }
 
     override val BotFactory: BotFactory

+ 2 - 0
mirai-core/src/jvmTest/kotlin/AtomicResizeCacheListTest.kt

@@ -7,6 +7,8 @@
  *  https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
+package net.mamoe.mirai.internal
+
 /*
 
 internal class AtomicResizeCacheListTest {

+ 81 - 0
mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt

@@ -0,0 +1,81 @@
+/*
+ * Copyright 2019-2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.message.data
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.serializer
+import net.mamoe.mirai.Mirai
+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 inline fun <reified T : Any> T.serialize(serializer: KSerializer<T> = module.serializer()): String {
+        return format.encodeToString(serializer, this)
+    }
+
+    private inline fun <reified T : Any> String.deserialize(serializer: KSerializer<T> = module.serializer()): T {
+        return format.decodeFromString(serializer, this)
+    }
+
+    private inline fun <reified T : Any> testSerialization(t: T, serializer: KSerializer<T> = module.serializer()) {
+        assertEquals(
+            t,
+            t.serialize(serializer).deserialize(serializer),
+            message = "serialized string: ${t.serialize(serializer)}"
+        )
+    }
+
+    private val testMessageContentInstances: Array<out MessageContent> = arrayOf(
+        PlainText("test"),
+        At._lowLevelConstructAtInstance(123456, ""),
+        AtAll,
+        Image("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai"),
+    )
+
+    @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+    private val testConstrainSingleMessageInstances: Array<out ConstrainSingle> = arrayOf(
+        LongMessage("content", "resId")
+    )
+
+    companion object {
+        @BeforeAll
+        @JvmStatic
+        fun init() {
+            Mirai
+        }
+    }
+
+    @Test
+    fun `test serialize each message contents`() {
+        for (message in testMessageContentInstances) {
+            testSerialization(message, module.serializer(message.javaClass))
+        }
+        for (message in testConstrainSingleMessageInstances) {
+            testSerialization(message, module.serializer(message.javaClass))
+        }
+    }
+
+    @Test
+    fun `test serialize message chain`() {
+        val chain = testMessageContentInstances.asMessageChain()
+        println(chain.serialize()) // [["net.mamoe.mirai.message.data.PlainText",{"content":"test"}],["net.mamoe.mirai.message.data.At",{"target":123456,"display":""}],["net.mamoe.mirai.message.data.AtAll",{}],["net.mamoe.mirai.internal.message.OfflineGroupImage",{"imageId":"{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai"}]]
+
+        testSerialization(chain)
+    }
+}

+ 10 - 0
mirai-core/src/jvmTest/kotlin/package.kt

@@ -0,0 +1,10 @@
+/*
+ * Copyright 2019-2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal