Rewrite MessageSerializers for new project structure (#2159)

This commit is contained in:
Him188 2022-07-20 15:09:09 +08:00 committed by GitHub
parent 699718f958
commit e5cad1d0ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1103 additions and 37 deletions

View File

@ -4815,8 +4815,13 @@ public final class net/mamoe/mirai/message/data/MessageSource$Key : net/mamoe/mi
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/MessageSource$Serializer : net/mamoe/mirai/internal/message/MessageSourceSerializerImpl {
public final class net/mamoe/mirai/message/data/MessageSource$Serializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/MessageSource$Serializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/MessageSource;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/MessageSource;)V
}
public final class net/mamoe/mirai/message/data/MessageSourceAmender : net/mamoe/mirai/message/data/MessageSourceBuilder {

View File

@ -4815,8 +4815,13 @@ public final class net/mamoe/mirai/message/data/MessageSource$Key : net/mamoe/mi
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/MessageSource$Serializer : net/mamoe/mirai/internal/message/MessageSourceSerializerImpl {
public final class net/mamoe/mirai/message/data/MessageSource$Serializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/MessageSource$Serializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/MessageSource;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/MessageSource;)V
}
public final class net/mamoe/mirai/message/data/MessageSourceAmender : net/mamoe/mirai/message/data/MessageSourceBuilder {

View File

@ -0,0 +1,121 @@
/*
* 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
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.encoding.*
import net.mamoe.mirai.utils.cast
import kotlin.jvm.JvmName
import kotlin.reflect.KClass
internal abstract class AbstractPolymorphicSerializer<T : Any> internal constructor() : KSerializer<T> {
/**
* Base class for all classes that this polymorphic serializer can serialize or deserialize.
*/
abstract val baseClass: KClass<T>
final override fun serialize(encoder: Encoder, value: T) {
val actualSerializer = findPolymorphicSerializer(encoder, value)
encoder.encodeStructure(descriptor) {
encodeStringElement(descriptor, 0, actualSerializer.descriptor.serialName)
encodeSerializableElement(descriptor, 1, actualSerializer.cast(), value)
}
}
final override fun deserialize(decoder: Decoder): T = decoder.decodeStructure(descriptor) {
var klassName: String? = null
var value: Any? = null
if (decodeSequentially()) {
return decodeSequentially(this)
}
mainLoop@ while (true) {
when (val index = decodeElementIndex(descriptor)) {
CompositeDecoder.DECODE_DONE -> {
break@mainLoop
}
0 -> {
klassName = decodeStringElement(descriptor, index)
}
1 -> {
klassName = requireNotNull(klassName) { "Cannot read polymorphic value before its type token" }
val serializer = findPolymorphicSerializer(this, klassName)
value = decodeSerializableElement(descriptor, index, serializer)
}
else -> throw SerializationException(
"Invalid index in polymorphic deserialization of " +
(klassName ?: "unknown class") +
"\n Expected 0, 1 or DECODE_DONE(-1), but found $index"
)
}
}
@Suppress("UNCHECKED_CAST")
requireNotNull(value) { "Polymorphic value has not been read for class $klassName" } as T
}
private fun decodeSequentially(compositeDecoder: CompositeDecoder): T {
val klassName = compositeDecoder.decodeStringElement(descriptor, 0)
val serializer = findPolymorphicSerializer(compositeDecoder, klassName)
return compositeDecoder.decodeSerializableElement(descriptor, 1, serializer)
}
/**
* Lookups an actual serializer for given [klassName] withing the current [base class][baseClass].
* May use context from the [decoder].
*/
open fun findPolymorphicSerializerOrNull(
decoder: CompositeDecoder,
klassName: String?
): DeserializationStrategy<out T>? = decoder.serializersModule.getPolymorphic(baseClass, klassName)
/**
* Lookups an actual serializer for given [value] within the current [base class][baseClass].
* May use context from the [encoder].
*/
public open fun findPolymorphicSerializerOrNull(
encoder: Encoder,
value: T
): SerializationStrategy<T>? =
encoder.serializersModule.getPolymorphic(baseClass, value)
}
internal fun <T : Any> AbstractPolymorphicSerializer<T>.findPolymorphicSerializer(
decoder: CompositeDecoder,
klassName: String?
): DeserializationStrategy<out T> =
findPolymorphicSerializerOrNull(decoder, klassName) ?: throwSubtypeNotRegistered(klassName, baseClass)
internal fun <T : Any> AbstractPolymorphicSerializer<T>.findPolymorphicSerializer(
encoder: Encoder,
value: T
): SerializationStrategy<T> =
findPolymorphicSerializerOrNull(encoder, value) ?: throwSubtypeNotRegistered(value::class, baseClass)
@JvmName("throwSubtypeNotRegistered")
internal fun throwSubtypeNotRegistered(subClassName: String?, baseClass: KClass<*>): Nothing {
val scope = "in the scope of '${baseClass.simpleName}'"
throw SerializationException(
if (subClassName == null)
"Class discriminator was missing and no default polymorphic serializers were registered $scope"
else
"Class '$subClassName' is not registered for polymorphic serialization $scope.\n" +
"Mark the base class as 'sealed' or register the serializer explicitly."
)
}
@JvmName("throwSubtypeNotRegistered")
internal fun throwSubtypeNotRegistered(subClass: KClass<*>, baseClass: KClass<*>): Nothing =
throwSubtypeNotRegistered(subClass.simpleName ?: "$subClass", baseClass)

View File

@ -23,7 +23,11 @@ import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
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 me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
import net.mamoe.mirai.Bot
import net.mamoe.mirai.IMirai
@ -111,6 +115,7 @@ import kotlin.native.CName
* @see FlashImage 闪照
* @see Image.flash 转换普通图片为闪照
*/
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
@Serializable(Image.Serializer::class)
@NotStableForInheritance
public interface Image : Message, MessageContent, CodableMessage {
@ -183,17 +188,43 @@ public interface Image : Message, MessageContent, CodableMessage {
deserialize = { Image(it) },
)
public object Serializer : KSerializer<Image> by FallbackSerializer("Image")
@Deprecated(
message = "For internal use only. Deprecated for removal. Please retrieve serializer from MessageSerializers.serializersModule.",
level = DeprecationLevel.WARNING
)
@DeprecatedSinceMirai(warningSince = "2.13") // error since 2.15, hidden since 2.16
public object Serializer : KSerializer<Image> by FallbackSerializer(SERIAL_NAME)
// move to mirai-core in 2.16. Delegate Serializer to the implementation from MessageSerializers.
@MiraiInternalApi
public open class FallbackSerializer(serialName: String) : KSerializer<Image> by Delegate.serializer().map(
buildClassSerialDescriptor(serialName) { element("imageId", String.serializer().descriptor) },
serialize = { Delegate(imageId) },
deserialize = { Image(imageId) },
) {
public open class FallbackSerializer(serialName: String) : KSerializer<Image> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor(serialName) {
element("imageId", String.serializer().descriptor)
}
// Note: Manually written to overcome discriminator issues.
// Without this implementation you will need `ignoreUnknownKeys` on deserialization.
override fun deserialize(decoder: Decoder): Image {
decoder.decodeStructure(descriptor) {
if (this.decodeSequentially()) {
val imageId = this.decodeStringElement(descriptor, 0)
return Image(imageId)
} else {
val index = this.decodeElementIndex(descriptor)
check(index == 0)
val imageId = this.decodeStringElement(descriptor, index)
return Image(imageId)
}
}
}
override fun serialize(encoder: Encoder, value: Image) {
Delegate.serializer().serialize(encoder, Delegate(value.imageId))
}
@SerialName(SERIAL_NAME)
@Serializable
internal data class Delegate(
private data class Delegate(
val imageId: String
)
}

View File

@ -46,6 +46,8 @@ public interface MarketFace : HummerMessage {
public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, MarketFace>(HummerMessage, { it.safeCast() }) {
// Notice that for MarketFaceImpl, its serial name is 'MarketFace';
// while for Dice, that is 'Dice' instead of 'MarketFace' again. (Dice extends MarketFace)
public const val SERIAL_NAME: String = "MarketFace"
}
}

View File

@ -16,7 +16,9 @@ package net.mamoe.mirai.message.data
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonConfiguration
import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
import net.mamoe.mirai.Bot
import net.mamoe.mirai.IMirai
@ -25,10 +27,12 @@ import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.action.AsyncRecallResult
import net.mamoe.mirai.message.data.MessageSource.Key.quote
import net.mamoe.mirai.message.data.MessageSource.Key.recall
import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.utils.DeprecatedSinceMirai
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.safeCast
@ -111,6 +115,7 @@ import kotlin.jvm.JvmSynthetic
*
* @see buildMessageSource 构建一个 [OfflineMessageSource]
*/
@Suppress("DEPRECATION")
@Serializable(MessageSource.Serializer::class)
public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
public final override val key: MessageKey<MessageSource>
@ -198,9 +203,16 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
return visitor.visitMessageSource(this, data)
}
public object Serializer : MessageSourceSerializerImpl("MessageSource")
@Deprecated("Do not use this serializer. Retrieve from `MessageSerializers.serializersModule`.")
@DeprecatedSinceMirai(warningSince = "2.13")
public object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("MessageSource")
public companion object Key : AbstractMessageKey<MessageSource>({ it.safeCast() }) {
/**
* [MessageSerializers] 获取到的对应[序列化器][KSerializer]在参与多态序列化时的[类型标识符][JsonConfiguration.classDiscriminator]的值.
*
* [OnlineMessageSource] 的部分属性无法通过序列化保存. 所有 [MessageSource] 子类型在序列化时都会序列化为 [OfflineMessageSource]. 反序列化时会得到 [OfflineMessageSource] 而不是原类型.
*/
public const val SERIAL_NAME: String = "MessageSource"
/**

View File

@ -13,6 +13,7 @@
package net.mamoe.mirai.message.data
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.data.MessageSource.Key.quote
@ -47,7 +48,7 @@ public data class QuoteReply(
/**
* 指代被引用的消息. 其中 [MessageSource.originalMessage] 可以控制客户端显示的消息内容.
*/
public val source: MessageSource
public val source: @Polymorphic MessageSource
) : Message, MessageMetadata, ConstrainSingle {
/**
* 从消息链中获取 [MessageSource] 并构造.

View File

@ -34,4 +34,8 @@ internal data class MarketFaceImpl internal constructor(
override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R {
return visitor.ex()?.visitMarketFaceImpl(this, data) ?: super.accept(visitor, data)
}
companion object {
const val SERIAL_NAME = MarketFace.SERIAL_NAME
}
}

View File

@ -40,11 +40,11 @@ internal class ImageProtocol : MessageProtocol() {
add(ImagePatcherForGroup())
MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) {
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
add(MessageSerializer(Image::class, Image.Serializer, registerAlsoContextual = true))
}
MessageSerializer.superclassesScope(Image::class, MessageContent::class, SingleMessage::class) {
add(MessageSerializer(Image::class, Image.Serializer))
add(MessageSerializer(OfflineGroupImage::class, OfflineGroupImage.serializer()))
add(MessageSerializer(OfflineFriendImage::class, OfflineFriendImage.serializer()))
add(MessageSerializer(OnlineFriendImageImpl::class, OnlineFriendImageImpl.serializer()))

View File

@ -20,7 +20,9 @@ import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Co
import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.copy
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.map
internal class MarketFaceProtocol : MessageProtocol() {
@ -30,16 +32,37 @@ internal class MarketFaceProtocol : MessageProtocol() {
add(MarketFaceDecoder())
MessageSerializer.superclassesScope(MarketFace::class, MessageContent::class, SingleMessage::class) {
add(
MessageSerializer(
MarketFaceImpl::class,
MarketFaceImpl.serializer()
)
// Serialization overview:
// Using MarketFace as serial type:
// - convert data to MarketFaceImpl on serialization. Convert them back to subtypes on deserialization.
// - serial name is always "MarketFace"
// Using subtypes:
// - serial name is name of subtype, i.e. "MarketFace" / "Dice".
// - Note that we don't use MarketFaceImpl but MarketFace for compatibility concerns.
add(
MessageSerializer(
MarketFace::class, MarketFaceImpl.serializer().map(
resultantDescriptor = MarketFaceImpl.serializer().descriptor.copy(MarketFace.SERIAL_NAME),
deserialize = {
it.delegate.toDiceOrNull() ?: it
},
serialize = {
when (it) {
is Dice -> MarketFaceImpl(it.toJceStruct())
is MarketFaceImpl -> it
else -> {
error("Unsupported MarketFace type ${it::class.qualifiedName}")
}
}
}
), emptyArray()
)
}
)
MessageSerializer.superclassesScope(MarketFace::class, MessageContent::class, SingleMessage::class) {
add(MessageSerializer(MarketFaceImpl::class, MarketFaceImpl.serializer()))
add(MessageSerializer(Dice::class, Dice.serializer()))
}
}

View File

@ -36,7 +36,7 @@ internal class MusicShareProtocol : MessageProtocol() {
add(Sender())
MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) {
add(MessageSerializer(MusicShare::class, MusicShare.serializer()))
add(MessageSerializer(MusicShare::class, MusicShare.serializer(), registerAlsoContextual = true))
}
}

View File

@ -11,6 +11,7 @@ package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.contact.AnonymousMember
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder
@ -26,6 +27,8 @@ import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer
import net.mamoe.mirai.internal.message.source.*
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.copy
import net.mamoe.mirai.utils.map
internal class QuoteReplyProtocol : MessageProtocol(PRIORITY_METADATA) {
override fun ProcessorCollector.collectProcessorsImpl() {
@ -36,6 +39,7 @@ internal class QuoteReplyProtocol : MessageProtocol(PRIORITY_METADATA) {
currentMessageChain[QuoteReply]?.source?.ensureSequenceIdAvailable()
})
val baseSourceSerializer = MessageSourceSerializerImpl(MessageSource.SERIAL_NAME)
MessageSerializer.superclassesScope(MessageSource::class, MessageMetadata::class, SingleMessage::class) {
add(
MessageSerializer(
@ -92,9 +96,37 @@ internal class QuoteReplyProtocol : MessageProtocol(PRIORITY_METADATA) {
)
)
add(MessageSerializer(MessageSource::class, MessageSource.serializer()))
}
MessageSerializer.superclassesScope(MessageMetadata::class, SingleMessage::class) {
@Suppress("DEPRECATION")
add(
MessageSerializer(
MessageSource::class,
OfflineMessageSourceImplData.serializer().map(
OfflineMessageSourceImplData.serializer().descriptor.copy(MessageSource.SERIAL_NAME),
{ it },
{
OfflineMessageSourceImplData(
kind, ids, botId, time, fromId, targetId,
originalMessage, internalIds
)
}
),
registerAlsoContextual = true
)
)
}
// add(
// MessageSerializer(
// MessageSource::class,
// PolymorphicSerializer(MessageSource::class),
// emptyArray(),
// registerAlsoContextual = true
// )
// )
MessageSerializer.superclassesScope(MessageMetadata::class, SingleMessage::class) {
add(MessageSerializer(QuoteReply::class, QuoteReply.serializer()))
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")

View File

@ -11,6 +11,7 @@ package net.mamoe.mirai.internal.message.protocol.serialization
import kotlinx.serialization.KSerializer
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.message.data.SingleMessage
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ -18,7 +19,8 @@ import kotlin.jvm.JvmInline
import kotlin.reflect.KClass
/**
* Collectd in [MessageProtocol.collectProcessors]
* Collectd in [MessageProtocol.collectProcessors].
* @see ProcessorCollector.add
*/
internal class MessageSerializer<T : Any>(
/**
@ -38,7 +40,7 @@ internal class MessageSerializer<T : Any>(
// This can help native targets, which has no reflection support.
companion object {
fun <T : SingleMessage, R> superclassesScope(
inline fun <T : SingleMessage, R> superclassesScope(
vararg superclasses: KClass<in T>,
block: SuperclassesScope<T>.() -> R
): R {

View File

@ -13,6 +13,7 @@ package net.mamoe.mirai.internal.message.source
import kotlinx.atomicfu.AtomicBoolean
import kotlinx.atomicfu.atomic
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.Bot
@ -32,6 +33,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.SourceMsg
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.internal.utils.structureToString
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.visitor.MessageVisitor
@ -45,7 +47,7 @@ internal class OnlineMessageSourceFromFriendImpl(
override val bot: Bot,
msg: List<MsgComm.Msg>,
) : OnlineMessageSource.Incoming.FromFriend(), IncomingMessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceFromFriend")
object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceFromFriend")
override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq }
override var isRecalledOrPlanned: AtomicBoolean = atomic(false)
@ -78,7 +80,7 @@ internal class OnlineMessageSourceFromStrangerImpl(
override val bot: Bot,
msg: List<MsgComm.Msg>,
) : OnlineMessageSource.Incoming.FromStranger(), IncomingMessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceFromStranger")
object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceFromStranger")
override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq }
override var isRecalledOrPlanned: AtomicBoolean = atomic(false)
@ -150,7 +152,7 @@ internal class OnlineMessageSourceFromTempImpl(
override val bot: Bot,
msg: List<MsgComm.Msg>,
) : OnlineMessageSource.Incoming.FromTemp(), IncomingMessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceFromTemp")
object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceFromTemp")
override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq }
override val internalIds: IntArray = msg.mapToIntArray { it.msgBody.richText.attr!!.random }
@ -187,7 +189,7 @@ internal class OnlineMessageSourceFromGroupImpl(
override val bot: Bot,
msg: List<MsgComm.Msg>,
) : OnlineMessageSource.Incoming.FromGroup(), IncomingMessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceFromGroupImpl")
object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceFromGroupImpl")
@Transient
override var isRecalledOrPlanned: AtomicBoolean = atomic(false)

View File

@ -12,6 +12,7 @@ package net.mamoe.mirai.internal.message.source
import kotlinx.atomicfu.AtomicBoolean
import kotlinx.atomicfu.atomic
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.Bot
@ -23,6 +24,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.SourceMsg
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.OfflineMessageSource
import net.mamoe.mirai.message.data.visitor.MessageVisitor
@ -42,7 +44,7 @@ internal class OfflineMessageSourceImplData(
private val originalMessageLazy: Lazy<MessageChain>,
override val internalIds: IntArray,
) : OfflineMessageSource(), MessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OfflineMessageSource")
object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OfflineMessageSource")
override val sequenceIds: IntArray get() = ids
override val originalMessage: MessageChain by originalMessageLazy

View File

@ -14,6 +14,7 @@ package net.mamoe.mirai.internal.message.source
import kotlinx.atomicfu.AtomicBoolean
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
@ -88,7 +89,7 @@ internal class OnlineMessageSourceToFriendImpl(
override val sender: Bot,
override val target: Friend,
) : OnlineMessageSource.Outgoing.ToFriend(), MessageSourceInternal, OutgoingMessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceToFriend")
object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceToFriend")
override val isOriginalMessageInitialized: Boolean
get() = true
@ -122,7 +123,7 @@ internal class OnlineMessageSourceToStrangerImpl(
target: Stranger,
) : this(delegate.ids, delegate.internalIds, delegate.time, delegate.originalMessage, delegate.sender, target)
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceToStranger")
object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceToStranger")
override val isOriginalMessageInitialized: Boolean
get() = true
@ -154,7 +155,7 @@ internal class OnlineMessageSourceToTempImpl(
target: Member,
) : this(delegate.ids, delegate.internalIds, delegate.time, delegate.originalMessage, delegate.sender, target)
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceToTemp")
object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceToTemp")
override val isOriginalMessageInitialized: Boolean
get() = true
@ -183,7 +184,7 @@ internal class OnlineMessageSourceToGroupImpl(
override val target: Group,
providedSequenceIds: IntArray? = null,
) : OnlineMessageSource.Outgoing.ToGroup(), MessageSourceInternal, OutgoingMessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceToGroup")
object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceToGroup")
override val isOriginalMessageInitialized: Boolean
get() = true

View File

@ -195,7 +195,7 @@ internal class MessageSerializationTest : AbstractTest() {
serializersModule = module
ignoreUnknownKeys = true
}
val source = j.decodeFromString(MessageSource.Serializer, a)
val source = j.decodeFromString(MessageSerializers.serializersModule.serializer<MessageSource>(), a)
println(source.structureToString())
assertEquals(
expected = Mirai.buildMessageSource(692928873, MessageSourceKind.GROUP) {

View File

@ -13,6 +13,14 @@ import io.ktor.utils.io.core.*
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.*
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 kotlinx.serialization.json.*
import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
@ -41,10 +49,14 @@ import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
import net.mamoe.mirai.internal.notice.processors.GroupExtensions
import net.mamoe.mirai.internal.test.runBlockingUnit
import net.mamoe.mirai.internal.testFramework.dynamicTest
import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString
import net.mamoe.mirai.utils.*
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.reflect.KClass
import kotlin.test.*
@OptIn(TestOnly::class)
@ -352,4 +364,294 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
}
}
}
///////////////////////////////////////////////////////////////////////////
// serialization
///////////////////////////////////////////////////////////////////////////
open val format: Json
// `serializersModule` is volatile, always return new Json instances.
get() = Json {
prettyPrint = true
serializersModule = MessageSerializers.serializersModule
}
///////////////////////////////////////////////////////////////////////////
// serialization - polymorphism
///////////////////////////////////////////////////////////////////////////
interface PolymorphicWrapper {
val message: SingleMessage
}
/**
* @param expectedSerialName also known as *poly discriminator*, give `null` to check for no discriminator's presence
*/
protected open fun <M : SingleMessage, P : PolymorphicWrapper> testPolymorphicIn(
polySerializer: KSerializer<P>,
polyConstructor: (message: M) -> P,
data: M,
expectedSerialName: String?,
expectedInstance: M = data,
) {
val string = format.encodeToString(
polySerializer,
polyConstructor(data)
)
println(string)
var element = format.parseToJsonElement(string)
element as JsonObject
element = element["message"] as JsonObject
if (expectedSerialName != null) {
assertEquals(expectedSerialName, element["type"]?.cast<JsonPrimitive>()?.content)
} else {
assertEquals(null, element["type"])
}
assertEquals(
expectedInstance,
format.decodeFromString(polySerializer, string).message
)
}
@Serializable
data class PolymorphicWrapperSingleMessage(
override val message: @Polymorphic SingleMessage
) : PolymorphicWrapper
protected open fun <M : SingleMessage> testPolymorphicInSingleMessage(
data: M,
expectedSerialName: String,
expectedInstance: M = data,
) = listOf(dynamicTest("testPolymorphicInSingleMessage") {
testPolymorphicIn(
polySerializer = PolymorphicWrapperSingleMessage.serializer(),
polyConstructor = ::PolymorphicWrapperSingleMessage,
data = data,
expectedSerialName = expectedSerialName,
expectedInstance = expectedInstance
)
})
@Serializable
data class PolymorphicWrapperMessageContent(
override val message: @Polymorphic MessageContent
) : PolymorphicWrapper
protected open fun <M : MessageContent> testPolymorphicInMessageContent(
data: M,
expectedSerialName: String,
expectedInstance: M = data,
) = listOf(dynamicTest("testPolymorphicInMessageContent") {
testPolymorphicIn(
polySerializer = PolymorphicWrapperMessageContent.serializer(),
polyConstructor = ::PolymorphicWrapperMessageContent,
data = data,
expectedSerialName = expectedSerialName,
expectedInstance = expectedInstance
)
})
@Serializable
data class PolymorphicWrapperMessageMetadata(
override val message: @Polymorphic MessageMetadata
) : PolymorphicWrapper
protected open fun <M : MessageMetadata> testPolymorphicInMessageMetadata(
data: M,
expectedSerialName: String,
expectedInstance: M = data,
) = listOf(dynamicTest("testPolymorphicInMessageMetadata") {
testPolymorphicIn(
polySerializer = PolymorphicWrapperMessageMetadata.serializer(),
polyConstructor = ::PolymorphicWrapperMessageMetadata,
data = data,
expectedSerialName = expectedSerialName,
expectedInstance = expectedInstance
)
})
///////////////////////////////////////////////////////////////////////////
// serialization - in MessageChain
///////////////////////////////////////////////////////////////////////////
protected open fun <M : SingleMessage> testInsideMessageChain(
data: M,
expectedSerialName: String,
expectedInstance: M = data,
) = listOf(dynamicTest("testInsideMessageChain") {
val chain = messageChainOf(data)
val string = chain.serializeToJsonString(format)
println(string)
val element = format.parseToJsonElement(string).jsonArray.single().jsonObject
assertEquals(expectedSerialName, element["type"]?.cast<JsonPrimitive>()?.content)
assertEquals(
expectedInstance,
MessageChain.deserializeFromJsonString(string).single()
)
})
///////////////////////////////////////////////////////////////////////////
// serialization - contextual
///////////////////////////////////////////////////////////////////////////
@Serializable
data class ContextualWrapper(
val message: @Contextual SingleMessage,
)
@Serializable
data class GenericTypedWrapper<T : SingleMessage>(
val message: T,
)
protected open fun <M : T, T : SingleMessage> testContextual(
data: M,
expectedSerialName: String,
expectedInstance: M = data,
targetType: KClass<out T> = data::class,
) = listOf(
testContextualWithWrapper<M, T>(data, expectedSerialName, expectedInstance),
testContextualWithStaticType(targetType, data, expectedInstance),
testContextualGeneric(targetType, data, expectedInstance),
testContextualWithoutWrapper<M, T>(data, expectedInstance)
).flatten()
private fun <M : T, T : SingleMessage> testContextualWithoutWrapper(
data: M,
expectedInstance: M
) = listOf(dynamicTest("testContextualWithoutWrapper") {
@Suppress("UNCHECKED_CAST")
val serializer = ContextualSerializer(data::class) as KSerializer<M>
val string = format.encodeToString(serializer, data)
println(string)
val element = format.parseToJsonElement(string).jsonObject
assertEquals(null, element["type"])
assertEquals(
expectedInstance,
format.decodeFromString(serializer, string)
)
})
private fun <M : T, T : SingleMessage> testContextualGeneric(
targetType: KClass<out T>,
data: M,
expectedInstance: M
) = listOf(dynamicTest("testContextualGeneric") {
val messageSerializer: ContextualSerializer<SingleMessage> = ContextualSerializer(targetType).cast()
/**
* ```
* data class StaticTypedWrapper(
* val message: T
* )
* ```
* without concern of generic types.
*/
/**
* ```
* data class StaticTypedWrapper(
* val message: T
* )
* ```
* without concern of generic types.
*/
val serializer = GenericTypedWrapper.serializer(messageSerializer)
val string = format.encodeToString(serializer, GenericTypedWrapper(data))
println(string)
val element = format.parseToJsonElement(string).jsonObject["message"]!!.jsonObject
assertEquals(null, element["type"]?.jsonPrimitive?.content)
assertEquals(
expectedInstance,
format.decodeFromString(serializer, string).message
)
})
private fun <M : T, T : SingleMessage> testContextualWithStaticType(
targetType: KClass<out T>,
data: M,
expectedInstance: M,
) = listOf(dynamicTest("testContextualWithStaticType") {
val messageSerializer: ContextualSerializer<SingleMessage> = ContextualSerializer(targetType).cast()
/**
* ```
* data class StaticTypedWrapper(
* val message: T
* )
* ```
* without concern of generic types.
*/
/**
* ```
* data class StaticTypedWrapper(
* val message: T
* )
* ```
* without concern of generic types.
*/
val serializer = object : KSerializer<SingleMessage> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("StaticTypedWrapper") {
element("message", messageSerializer.descriptor, listOf(Contextual()))
}
override fun deserialize(decoder: Decoder): SingleMessage {
decoder.decodeStructure(descriptor) {
if (this.decodeSequentially()) {
return this.decodeSerializableElement(
descriptor.getElementDescriptor(0),
0,
messageSerializer
)
} else {
val index = this.decodeElementIndex(descriptor)
check(index == 0)
return this.decodeSerializableElement(descriptor, index, messageSerializer)
}
}
}
override fun serialize(encoder: Encoder, value: SingleMessage) {
encoder.encodeStructure(descriptor) {
encodeSerializableElement(descriptor, 0, messageSerializer, value)
}
}
}
val string = format.encodeToString(serializer, data)
println(string)
val element = format.parseToJsonElement(string).jsonObject["message"]!!.jsonObject
assertEquals(null, element["type"]?.jsonPrimitive?.content)
assertEquals(
expectedInstance,
format.decodeFromString(serializer, string)
)
})
private fun <M : T, T : SingleMessage> testContextualWithWrapper(
data: M,
expectedSerialName: String,
expectedInstance: M,
afterSerialization: (element: JsonObject) -> Unit = {}
) = listOf(dynamicTest("testContextualWithWrapper") {
val string = format.encodeToString(ContextualWrapper.serializer(), ContextualWrapper(data))
println(string)
val element = format.parseToJsonElement(string).jsonObject["message"]!!.jsonObject
afterSerialization(element)
assertEquals(expectedSerialName, element["type"]?.jsonPrimitive?.content)
assertEquals(
expectedInstance,
format.decodeFromString(ContextualWrapper.serializer(), string).message
)
})
}

View File

@ -82,4 +82,17 @@ internal class CustomMessageProtocolTest : AbstractMessageProtocolTest() {
message(MyCustomMessage(1))
}.doEncoderChecks()
}
// not supported, see https://github.com/mamoe/mirai/issues/2144
// @TestFactory
// fun `test serialization`(): DynamicTestsResult {
// val data = MyCustomMessage(1)
// val serialName = "CustomMessage"
// return runDynamicTests(
// testPolymorphicInMessageMetadata(data, serialName),
// testPolymorphicInSingleMessage(data, serialName),
// testInsideMessageChain(data, serialName),
// testContextual(data),
// )
// }
}

View File

@ -11,6 +11,9 @@ 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.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.Face
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.messageChainOf
@ -60,4 +63,15 @@ internal class FaceProtocolTest : AbstractMessageProtocolTest() {
}
@TestFactory
fun `test serialization`(): DynamicTestsResult {
val data = Face(1)
val serialName = Face.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
}

View File

@ -11,6 +11,9 @@ 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.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.FileMessage
import net.mamoe.mirai.utils.hexToBytes
import kotlin.test.BeforeTest
@ -61,4 +64,16 @@ internal class FileMessageProtocolTest : AbstractMessageProtocolTest() {
useOrdinaryEquality()
}.doDecoderChecks()
}
@TestFactory
fun `test serialization`(): DynamicTestsResult {
val data = FileMessage("id", 1, "name", 2)
val serialName = FileMessage.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
}

View File

@ -14,6 +14,9 @@ import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFla
import net.mamoe.mirai.internal.message.data.ForwardMessageInternal
import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.castUp
@ -143,4 +146,23 @@ internal class ForwardMessageProtocolTest : AbstractMessageProtocolTest() {
// }
// }
// }
@TestFactory
fun `test serialization`(): DynamicTestsResult {
val data = buildForwardMessage(defaultTarget.castUp()) {
add(1, "senderName", time = 123, message = PlainText("simple text"))
add(1, "senderName", time = 123) {
+PlainText("simple")
+Face(1)
+Image("{90CCED1C-2D64-313B-5D66-46625CAB31D7}.jpg")
}
}
val serialName = ForwardMessage.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
}

View File

@ -10,13 +10,22 @@
package net.mamoe.mirai.internal.message.protocol.impl
import io.ktor.utils.io.core.*
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.internal.message.image.OfflineFriendImage
import net.mamoe.mirai.internal.message.image.OfflineGroupImage
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.dynamicTest
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.ImageType
import net.mamoe.mirai.utils.hexToBytes
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertIs
internal class ImageProtocolTest : AbstractMessageProtocolTest() {
override val protocols: Array<out MessageProtocol> = arrayOf(ImageProtocol())
@ -563,4 +572,67 @@ internal class ImageProtocolTest : AbstractMessageProtocolTest() {
}
///////////////////////////////////////////////////////////////////////////
// serialization
///////////////////////////////////////////////////////////////////////////
@Serializable
data class PolymorphicWrapperImage(
override val message: @Polymorphic Image
) : PolymorphicWrapper
private fun <M : Image> testPolymorphicInImage(
data: M,
expectedInstance: M = data,
) = listOf(dynamicTest("testPolymorphicInImage") {
testPolymorphicIn(
polySerializer = PolymorphicWrapperImage.serializer(),
polyConstructor = ::PolymorphicWrapperImage,
data = data,
expectedSerialName = null,
expectedInstance = expectedInstance,
)
})
@TestFactory
fun `test serialization for OfflineGroupImage`(): DynamicTestsResult {
val data = Image("{90CCED1C-2D64-313B-5D66-46625CAB31D7}.jpg")
assertIs<OfflineGroupImage>(data)
val serialName = Image.SERIAL_NAME
return runDynamicTests(
testPolymorphicInImage(data),
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
@TestFactory
fun `test serialization for OfflineFriendImage type 1`(): DynamicTestsResult {
val data = Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f") // type 1
assertIs<OfflineFriendImage>(data)
val serialName = Image.SERIAL_NAME
return runDynamicTests(
testPolymorphicInImage(data),
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
@TestFactory
fun `test serialization for OfflineFriendImage type 2`(): DynamicTestsResult {
val data = Image("/000000000-3814297509-BFB7027B9354B8F899A062061D74E206") // type 1
assertIs<OfflineFriendImage>(data)
val serialName = Image.SERIAL_NAME
return runDynamicTests(
testPolymorphicInImage(data),
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
}

View File

@ -134,4 +134,6 @@ internal class LongMessageProtocolTest : AbstractMessageProtocolTest() {
}
}
}
// should add tests for refining received LongMessage to normal messages (with a MessageOrigin)
}

View File

@ -10,10 +10,17 @@
package net.mamoe.mirai.internal.message.protocol.impl
import io.ktor.utils.io.core.*
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.Serializable
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.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.dynamicTest
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.Dice
import net.mamoe.mirai.message.data.MarketFace
import net.mamoe.mirai.utils.hexToBytes
import kotlin.test.BeforeTest
import kotlin.test.Test
@ -194,5 +201,84 @@ internal class MarketFaceProtocolTest : AbstractMessageProtocolTest() {
}.doBothChecks()
}
///////////////////////////////////////////////////////////////////////////
// serialization
///////////////////////////////////////////////////////////////////////////
@Serializable
data class PolymorphicWrapperMarketFace(
override val message: @Polymorphic MarketFace
) : PolymorphicWrapper
@Serializable
data class StaticWrapperDice(
override val message: Dice
) : PolymorphicWrapper
private fun <M : MarketFace> testPolymorphicInMarketFace(
data: M,
expectedSerialName: String,
expectedInstance: M = data,
) = listOf(dynamicTest("testPolymorphicInMarketFace") {
testPolymorphicIn(
polySerializer = PolymorphicWrapperMarketFace.serializer(),
polyConstructor = ::PolymorphicWrapperMarketFace,
data = data,
expectedSerialName = expectedSerialName, // MarketFaceImpl is 'MarketFace', Dice is 'Dice', should include discriminator
expectedInstance = expectedInstance,
)
})
private fun testStaticDice(
data: Dice,
expectedInstance: Dice = data,
) = listOf(dynamicTest("testStaticDice") {
testPolymorphicIn(
polySerializer = StaticWrapperDice.serializer(),
polyConstructor = ::StaticWrapperDice,
data = data,
expectedSerialName = null,
expectedInstance = expectedInstance,
)
})
@TestFactory
fun `test serialization for MarketFaceImpl`(): DynamicTestsResult {
val data = 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(),
)
)
val serialName = MarketFaceImpl.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMarketFace(data, serialName),
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName, targetType = MarketFace::class),
)
}
@TestFactory
fun `test serialization for Dice`(): DynamicTestsResult {
val data = Dice(1)
val serialName = Dice.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMarketFace(data, serialName),
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName, targetType = Dice::class),
testStaticDice(data),
)
}
}

View File

@ -16,6 +16,9 @@ import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageProcessorAdapter
import net.mamoe.mirai.internal.pipeline.replaceProcessor
import net.mamoe.mirai.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.LightApp
import net.mamoe.mirai.message.data.MessageOrigin
import net.mamoe.mirai.message.data.MessageOriginKind
@ -116,4 +119,26 @@ internal class MusicShareProtocolTest : AbstractMessageProtocolTest() {
}
}
@TestFactory
fun `test serialization for MusicShare`(): DynamicTestsResult {
val data = 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 = "[分享]ジェリーフィッシュ",
)
val serialName = MusicShare.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
}

View File

@ -11,6 +11,9 @@ 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.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.PokeMessage
import net.mamoe.mirai.utils.hexToBytes
import kotlin.test.BeforeTest
@ -82,4 +85,20 @@ internal class PokeMessageProtocolTest : AbstractMessageProtocolTest() {
message(PokeMessage.ChuoYiChuo)
}.doEncoderChecks()
}
///////////////////////////////////////////////////////////////////////////
// serialization
///////////////////////////////////////////////////////////////////////////
@TestFactory
fun `test serialization for PokeMessage`(): DynamicTestsResult {
val data = PokeMessage.ChuoYiChuo
val serialName = PokeMessage.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
}

View File

@ -9,15 +9,19 @@
package net.mamoe.mirai.internal.message.protocol.impl
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData
import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.dynamicTest
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.internal.utils.runCoroutineInPlace
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.MessageSource.Key.quote
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.QuoteReply
import net.mamoe.mirai.message.data.messageChainOf
import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.utils.hexToBytes
import kotlin.test.Test
@ -447,4 +451,97 @@ internal class QuoteReplyProtocolTest : AbstractMessageProtocolTest() {
}
///////////////////////////////////////////////////////////////////////////
// serialization
///////////////////////////////////////////////////////////////////////////
@TestFactory
fun `test serialization for QuoteReply`(): DynamicTestsResult {
val source = MessageSourceBuilder()
.sender(123)
.target(123)
.messages {
append("test")
}
.build(123, MessageSourceKind.FRIEND)
val data = QuoteReply(source)
val serialName = QuoteReply.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageMetadata(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
///////////////////////////////////////////////////////////////////////////
// MessageSource serialization
///////////////////////////////////////////////////////////////////////////
// TODO: 2022/7/20 MessageSource 在 MessageMetadata 的 scope 多态序列化后会输出 'type' = 'MessageSource', 这是期望的行为.
// 但是在反序列化时会错误 unknown field 'type'
override val format: Json
get() = Json {
prettyPrint = true
serializersModule = MessageSerializers.serializersModule
ignoreUnknownKeys = true
}
@Serializable
data class PolymorphicWrapperMessageSource(
override val message: MessageSource
) : PolymorphicWrapper
private fun <M : MessageSource> testPolymorphicInMessageSource(
data: M,
expectedInstance: M = data,
) = listOf(dynamicTest("testPolymorphicInMessageSource") {
testPolymorphicIn(
polySerializer = PolymorphicWrapperMessageSource.serializer(),
polyConstructor = ::PolymorphicWrapperMessageSource,
data = data,
expectedInstance = expectedInstance,
expectedSerialName = null,
)
})
@TestFactory
fun `test serialization for OfflineMessageSource`(): DynamicTestsResult {
val data = MessageSourceBuilder()
.sender(123)
.target(123)
.messages {
append("test")
}
.build(123, MessageSourceKind.FRIEND)
val serialName = MessageSource.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageSource(data),
testPolymorphicInMessageMetadata(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
@TestFactory
fun `test serialization for OnlineMessageSource`(): DynamicTestsResult {
val data = onlineIncomingGroupMessage[MessageSource]!!
val expected = (data as OnlineMessageSource).toOffline()
val serialName = MessageSource.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageSource(data, expectedInstance = expected),
testPolymorphicInMessageMetadata(data, serialName, expectedInstance = expected),
testPolymorphicInSingleMessage(data, serialName, expectedInstance = expected),
testInsideMessageChain(data, serialName, expectedInstance = expected),
testContextual(data, serialName, expectedInstance = expected),
)
}
}

View File

@ -9,8 +9,15 @@
package net.mamoe.mirai.internal.message.protocol.impl
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.dynamicTest
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.RichMessage
import net.mamoe.mirai.utils.hexToBytes
import kotlin.test.BeforeTest
import kotlin.test.Test
@ -80,4 +87,66 @@ internal class RichMessageProtocolTest : AbstractMessageProtocolTest() {
}
// no encoder. specially handled, no test for now.
///////////////////////////////////////////////////////////////////////////
// serialization
///////////////////////////////////////////////////////////////////////////
@Serializable
data class PolymorphicWrapperRichMessage(
override val message: @Polymorphic RichMessage
) : PolymorphicWrapper
private fun <M : RichMessage> testPolymorphicInRichMessage(
data: M,
expectedSerialName: String,
expectedInstance: M = data,
) = listOf(dynamicTest("testPolymorphicInRichMessage") {
testPolymorphicIn(
polySerializer = PolymorphicWrapperRichMessage.serializer(),
polyConstructor = ::PolymorphicWrapperRichMessage,
data = data,
expectedSerialName = expectedSerialName,
expectedInstance = expectedInstance
)
})
@TestFactory
fun `test serialization for RichMessage`(): DynamicTestsResult {
val data = 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()
)
val serialName = net.mamoe.mirai.message.data.SimpleServiceMessage.SERIAL_NAME
return runDynamicTests(
testPolymorphicInRichMessage(data, serialName),
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
@TestFactory
fun `test serialization for LightApp`(): DynamicTestsResult {
val data = net.mamoe.mirai.message.data.LightApp(
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()
)
val serialName = net.mamoe.mirai.message.data.LightApp.SERIAL_NAME
return runDynamicTests(
testPolymorphicInRichMessage(data, serialName),
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
}

View File

@ -11,6 +11,9 @@ 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.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.AtAll
import net.mamoe.mirai.message.data.PlainText
@ -115,4 +118,51 @@ internal class TextProtocolTest : AbstractMessageProtocolTest() {
})
}.doBothChecks()
}
///////////////////////////////////////////////////////////////////////////
// serialization
///////////////////////////////////////////////////////////////////////////
@TestFactory
fun `test serialization for PlainText`(): DynamicTestsResult {
val data = PlainText(
content = """foo""",
)
val serialName = PlainText.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
@TestFactory
fun `test serialization for At`(): DynamicTestsResult {
val data = At(
100
)
val serialName = At.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
@TestFactory
fun `test serialization for AtAll`(): DynamicTestsResult {
val data = AtAll
val serialName = AtAll.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
}

View File

@ -11,6 +11,9 @@ 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.internal.testFramework.DynamicTestsResult
import net.mamoe.mirai.internal.testFramework.TestFactory
import net.mamoe.mirai.internal.testFramework.runDynamicTests
import net.mamoe.mirai.message.data.VipFace
import net.mamoe.mirai.utils.hexToBytes
import kotlin.test.BeforeTest
@ -48,4 +51,24 @@ internal class VipFaceProtocolTest : AbstractMessageProtocolTest() {
useOrdinaryEquality()
}.doDecoderChecks()
}
///////////////////////////////////////////////////////////////////////////
// serialization
///////////////////////////////////////////////////////////////////////////
@TestFactory
fun `test serialization for VipFace`(): DynamicTestsResult {
val data = VipFace(
VipFace.LiuLian, 1
)
val serialName = VipFace.SERIAL_NAME
return runDynamicTests(
testPolymorphicInMessageContent(data, serialName),
testPolymorphicInSingleMessage(data, serialName),
testInsideMessageChain(data, serialName),
testContextual(data, serialName),
)
}
}

View File

@ -0,0 +1,14 @@
/*
* 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.serialization
import net.mamoe.mirai.internal.test.AbstractTest
internal abstract class AbstractMessageSerializationTest : AbstractTest()