Fix serialization

This commit is contained in:
Him188 2020-12-26 23:42:44 +08:00
parent 5196cc410a
commit eacdfed97a
9 changed files with 268 additions and 61 deletions

View File

@ -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)

View File

@ -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)
}
/**

View File

@ -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

View File

@ -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>

View File

@ -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
)
}
}
}
}

View File

@ -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 }

View File

@ -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

View File

@ -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(

View File

@ -13,16 +13,19 @@ 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 {
private val module get() = Message.Serializer.serializersModule
private val format
get() = Json {
serializersModule = module
useArrayPolymorphism = false
useArrayPolymorphism = false // ?
}
private inline fun <reified T : Any> T.serialize(serializer: KSerializer<T> = module.serializer()): String {
@ -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 {