Skip to content

Commit

Permalink
Merge pull request #21 from simple-robot/dev/support-unknown-segment
Browse files Browse the repository at this point in the history
增加 OneBotUnknownSegment 类型支持
  • Loading branch information
ForteScarlet authored Jun 12, 2024
2 parents 677d26c + dbfc64e commit 372bb08
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 5 deletions.
16 changes: 15 additions & 1 deletion Writerside/topics/onebot11-message.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ var element = OneBotMessageSegments.toElement(segment);
</tab>
</tabs>


### 消息段定义

<deflist>
Expand Down Expand Up @@ -99,6 +98,21 @@ var element = OneBotMessageSegments.toElement(segment);
</def>
<def id="OneBotXml" title="OneBotXml">

</def>
<def id="OneBotUnknownSegment" title="OneBotUnknownSegment">

一个当出现了除上述其他已知类型以外的消息段类型时使用的包装类型。
它通过对 `SerializersModule` 的配置增加了 `OneBotMessageSegment`
类型的默认序列化/反序列化器来支持解析为此默认类型。

它只支持JSON结构,因为它使用了 `JsonObject` 作为 `data` 属性的类型。

<warning title="实验性">

`OneBotUnknownSegment` 是实验性的。可能不稳定或在未来发生变更、移除。

</warning>

</def>
</deflist>

Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ kotlin {
compilerOptions {
optIn.addAll(
"love.forte.simbot.annotations.InternalSimbotAPI",
"love.forte.simbot.component.onebot.common.annotations.InternalOneBotAPI"
"love.forte.simbot.component.onebot.common.annotations.InternalOneBotAPI",
"love.forte.simbot.component.onebot.common.annotations.ExperimentalOneBotAPI",
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import love.forte.simbot.annotations.InternalSimbotAPI
import love.forte.simbot.annotations.FragileSimbotAPI
import love.forte.simbot.bot.serializableBotConfigurationPolymorphic
import love.forte.simbot.component.onebot.v11.core.bot.OneBotBotSerializableConfiguration
import love.forte.simbot.component.onebot.v11.message.OneBotMessageElement
import love.forte.simbot.component.onebot.v11.message.includeAllComponentMessageElementImpls
import love.forte.simbot.component.onebot.v11.message.includeAllOneBotSegmentImpls
import love.forte.simbot.component.onebot.v11.message.segment.OneBotMessageSegment
import love.forte.simbot.component.onebot.v11.message.segment.OneBotUnknownSegment
import love.forte.simbot.component.onebot.v11.message.segment.OneBotUnknownSegmentDeserializer
import love.forte.simbot.component.onebot.v11.message.segment.OneBotUnknownSegmentPolymorphicSerializer
import love.forte.simbot.message.messageElementPolymorphic
import kotlin.jvm.JvmField

Expand All @@ -43,8 +46,8 @@ public object OneBot11 {
* 和 [OneBotMessageSegment] 的多态序列化信息的
* [SerializersModule]。

Check warning on line 47 in simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/OneBot11.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unresolved reference in KDoc

Cannot resolve symbol 'SerializersModule'
*/
@OptIn(FragileSimbotAPI::class)
@JvmField
@OptIn(InternalSimbotAPI::class)
public val serializersModule: SerializersModule = SerializersModule {
messageElementPolymorphic {
includeAllComponentMessageElementImpls()
Expand All @@ -54,6 +57,15 @@ public object OneBot11 {
}
polymorphic(OneBotMessageSegment::class) {
includeAllOneBotSegmentImpls()

defaultDeserializer { OneBotUnknownSegmentDeserializer }
}
polymorphicDefaultSerializer(OneBotMessageSegment::class) { base ->
if (base is OneBotUnknownSegment) {
OneBotUnknownSegmentPolymorphicSerializer(base.type)
} else {
null
}
}
serializableBotConfigurationPolymorphic {
subclass(OneBotBotSerializableConfiguration.serializer())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ kotlin {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
optIn.addAll(
"love.forte.simbot.component.onebot.common.annotations.InternalOneBotAPI"
"love.forte.simbot.component.onebot.common.annotations.InternalOneBotAPI",
"love.forte.simbot.component.onebot.common.annotations.ExperimentalOneBotAPI"
)
}

Expand All @@ -64,6 +65,7 @@ kotlin {
implementation(libs.simbot.api)
implementation(libs.simbot.common.annotations)
api(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}

commonTest.dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2024. ForteScarlet.
*
* This file is part of simbot-component-onebot.
*
* simbot-component-onebot is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* simbot-component-onebot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with simbot-component-onebot.
* If not, see <https://www.gnu.org/licenses/>.
*/

package love.forte.simbot.component.onebot.v11.message.segment

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.element
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.JsonObject
import love.forte.simbot.annotations.FragileSimbotAPI
import love.forte.simbot.component.onebot.common.annotations.ExperimentalOneBotAPI
import love.forte.simbot.component.onebot.common.annotations.InternalOneBotAPI


/**
* 一个未知类型的 [OneBotMessageSegment]。
*
* 当所有已知的 [OneBotMessageSegment]
* 子类型均无法直接通过多态序列化器反序列化时,
* 将会直接被解析为 [OneBotUnknownSegment]。
*
* [OneBotUnknownSegment] 本身不可序列化,
* 需要向 [OneBotUnknownSegmentDeserializer] 提供一个多态类型后进行序列化,
* 且 [data] 的类型为 [JsonObject],因此仅支持 JSON 格式。

Check warning on line 43 in simbot-component-onebot-v11/simbot-component-onebot-v11-message/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/message/segment/OneBotUnknownSegment.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unresolved reference in KDoc

Cannot resolve symbol 'JsonObject'
*
* 实验性:[OneBotUnknownSegment] 的应用(包括序列化与反序列化)尚在实验中,
* 可能不稳定,且未来可能会随时删除、更改。
*
* @see OneBotUnknownSegmentDeserializer
*
* @author ForteScarlet
*/
@FragileSimbotAPI
@ExperimentalOneBotAPI
public data class OneBotUnknownSegment
@InternalOneBotAPI
constructor(
val type: String,
override val data: JsonObject? = null
) : OneBotMessageSegment

/**
* [OneBotUnknownSegment] 的反序列化器,
* 将任意未知的segment内容解析为 [OneBotUnknownSegment]
*
*/
@OptIn(FragileSimbotAPI::class)
@InternalOneBotAPI
@ExperimentalOneBotAPI
public object OneBotUnknownSegmentDeserializer :
DeserializationStrategy<OneBotUnknownSegment> {

override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("OneBotUnknownSegment") {
element<String>("type")
element<JsonObject?>("data", isOptional = true)
}

@OptIn(ExperimentalSerializationApi::class)
override fun deserialize(decoder: Decoder): OneBotUnknownSegment {
return decoder.decodeStructure(descriptor) {
if (decodeSequentially()) {
val type = decodeStringElement(descriptor, 0)
val data = decodeNullableSerializableElement(descriptor, 1, JsonObject.serializer())

return@decodeStructure OneBotUnknownSegment(type, data)
}

var type: String? = null
var data: JsonObject? = null

while (true) {
when (val index = decodeElementIndex(descriptor)) {
CompositeDecoder.DECODE_DONE -> break
0 -> type = decodeStringElement(descriptor, 0)
1 -> data = decodeSerializableElement(descriptor, 1, JsonObject.serializer())
else -> error("Unexpected index: $index")
}
}

if (type == null) throw SerializationException("Required property 'type' is null or miss")

OneBotUnknownSegment(type, data)
}

}
}

/**
* [OneBotUnknownSegment] 的多态序列化器,
* 提供类型 `type` 后进行序列化。
*/
@OptIn(FragileSimbotAPI::class)
@InternalOneBotAPI
@ExperimentalOneBotAPI
public class OneBotUnknownSegmentPolymorphicSerializer(type: String) :
SerializationStrategy<OneBotMessageSegment> {
override val descriptor: SerialDescriptor =
buildClassSerialDescriptor(type) {
element<JsonObject?>("data", isOptional = true)
}

@OptIn(ExperimentalSerializationApi::class)
override fun serialize(encoder: Encoder, value: OneBotMessageSegment) {
if (value is OneBotUnknownSegment) {
encoder.encodeStructure(descriptor) {
this.encodeNullableSerializableElement(descriptor, 0, JsonObject.serializer(), value.data)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package love.forte.simbot.component.onebot.v11.message

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import love.forte.simbot.annotations.FragileSimbotAPI
import love.forte.simbot.component.onebot.v11.message.segment.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs


/**
*
* @author ForteScarlet
*/
class UnknownSegmentTests {
@OptIn(ExperimentalSerializationApi::class, FragileSimbotAPI::class)
val json = Json {
isLenient = true
ignoreUnknownKeys = true
serializersModule = SerializersModule {
polymorphic(OneBotMessageSegment::class) {
includeAllOneBotSegmentImpls()
defaultDeserializer { OneBotUnknownSegmentDeserializer }
}
polymorphicDefaultSerializer(OneBotMessageSegment::class) { base ->
if (base is OneBotUnknownSegment) {
OneBotUnknownSegmentPolymorphicSerializer(base.type)
} else {
null
}
}
}
prettyPrint = true
prettyPrintIndent = "\t"
}


@OptIn(FragileSimbotAPI::class)
@Test
fun decodeTest() {
val segment = json.decodeFromString(
PolymorphicSerializer(OneBotMessageSegment::class),
"""
{
"data": {
"file": "1718187789891.mp4",
"path": "",
"file_id": "/d5e14a94-86be-49fd-abe3-c03817b5ac27",
"file_size": "2808485"
},
"type": "file"
}
""".trimIndent()
)

assertIs<OneBotUnknownSegment>(segment)

val obj = json.encodeToJsonElement(PolymorphicSerializer(OneBotMessageSegment::class), segment)
val objType = obj.jsonObject["type"]?.jsonPrimitive?.content
assertEquals("file", objType)
}


}

0 comments on commit 372bb08

Please sign in to comment.