diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index ff19fedb..c301fedd 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -41,9 +41,15 @@ jobs: uses: gradle/actions/setup-gradle@v3 with: gradle-version: ${{ env.GRADLE_VERSION }} + arguments: | + assemble + allTests + --stacktrace + --warning-mode all + -Pkotlin.native.enableKlibsCrossCompilation=false - - name: Run all tests - run: gradle assemble allTests --stacktrace --warning-mode all +# - name: Run all tests +# run: gradle assemble allTests --stacktrace --warning-mode all -Pkotlin.native.enableKlibsCrossCompilation=false publish-releases: name: Publish releases diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index 4c5e30ab..fe0a9f95 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -62,9 +62,15 @@ jobs: uses: gradle/actions/setup-gradle@v3 with: gradle-version: ${{ env.GRADLE_VERSION }} + arguments: | + assemble + allTests + --stacktrace + --warning-mode all + -Pkotlin.native.enableKlibsCrossCompilation=false - - name: Run all tests - run: gradle assemble allTests --stacktrace --warning-mode all +# - name: Run all tests +# run: gradle assemble allTests --stacktrace --warning-mode all -Pkotlin.native.enableKlibsCrossCompilation=false publish-snapshots: name: Publish snapshots diff --git a/build.gradle.kts b/build.gradle.kts index a71d862c..888f2a48 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -138,6 +138,9 @@ apiValidation { "love.forte.simbot.annotations.InternalSimbotAPI", "love.forte.simbot.component.onebot.common.annotations.ApiResultConstructor", "love.forte.simbot.component.onebot.common.annotations.SourceEventConstructor", + + // CustomEventResolver + "love.forte.simbot.component.onebot.v11.core.event.ExperimentalCustomEventResolverApi" ), ) diff --git a/buildSrc/src/main/kotlin/JvmConfig.kt b/buildSrc/src/main/kotlin/JvmConfig.kt index 97eb4c70..a8c358db 100644 --- a/buildSrc/src/main/kotlin/JvmConfig.kt +++ b/buildSrc/src/main/kotlin/JvmConfig.kt @@ -30,7 +30,6 @@ import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget inline fun KotlinJvmTarget.configJava(jdkVersion: Int, crossinline block: KotlinJvmTarget.() -> Unit = {}) { - withJava() compilerOptions { javaParameters.set(true) jvmTarget.set(JvmTarget.fromTarget(jdkVersion.toString())) @@ -88,12 +87,15 @@ inline fun Project.configJavaCompileWithModule( targetCompatibility = jvmVersion if (moduleName != null) { - options.compilerArgumentProviders.add( - CommandLineArgumentProvider { + options.compilerArgumentProviders.add(CommandLineArgumentProvider { + val sourceSet = sourceSets.findByName("main") ?: sourceSets.findByName("jvmMain") + if (sourceSet != null) { // Provide compiled Kotlin classes to javac – needed for Java/Kotlin mixed sources to work - listOf("--patch-module", "$moduleName=${sourceSets["main"].output.asPath}") + listOf("--patch-module", "$moduleName=${sourceSet.output.asPath}") + } else { + emptyList() } - ) + }) } block() diff --git a/buildSrc/src/main/kotlin/P.kt b/buildSrc/src/main/kotlin/P.kt index 698a1ee0..8e05f7da 100644 --- a/buildSrc/src/main/kotlin/P.kt +++ b/buildSrc/src/main/kotlin/P.kt @@ -37,8 +37,8 @@ object P { override val description: String get() = DESCRIPTION override val homepage: String get() = HOMEPAGE - const val VERSION = "1.7.0" - const val NEXT_VERSION = "1.7.1" + const val VERSION = "1.8.0" + const val NEXT_VERSION = "1.8.1" override val snapshotVersion = "$NEXT_VERSION-SNAPSHOT" override val version = if (isSnapshot()) snapshotVersion else VERSION diff --git a/buildSrc/src/main/kotlin/simbot-onebot-changelog-generator.gradle.kts b/buildSrc/src/main/kotlin/simbot-onebot-changelog-generator.gradle.kts index bd3fde75..4901297c 100644 --- a/buildSrc/src/main/kotlin/simbot-onebot-changelog-generator.gradle.kts +++ b/buildSrc/src/main/kotlin/simbot-onebot-changelog-generator.gradle.kts @@ -16,7 +16,7 @@ */ -tasks.create("createChangelog") { +tasks.register("createChangelog") { group = "documentation" doFirst { val realVersion = P.ComponentOneBot.version.toString() diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1efb0087..e0732c5b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Mar 21 17:22:56 CST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/simbot-component-onebot-common/build.gradle.kts b/simbot-component-onebot-common/build.gradle.kts index 70a7e140..902a755f 100644 --- a/simbot-component-onebot-common/build.gradle.kts +++ b/simbot-component-onebot-common/build.gradle.kts @@ -59,12 +59,6 @@ kotlin { api(kotlin("test")) } - jvmMain { - dependencies { - compileOnly(libs.simbot.api) - } - } - jvmTest.dependencies { implementation(libs.log4j.api) implementation(libs.log4j.core) diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-common/build.gradle.kts b/simbot-component-onebot-v11/simbot-component-onebot-v11-common/build.gradle.kts index d1f22dae..31e42a89 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-common/build.gradle.kts +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-common/build.gradle.kts @@ -51,8 +51,8 @@ kotlin { sourceSets { commonMain.dependencies { - implementation(project(":simbot-component-onebot-common")) - implementation(libs.simbot.common.annotations) + api(project(":simbot-component-onebot-common")) + api(libs.simbot.common.annotations) api(libs.kotlinx.serialization.core) } @@ -62,10 +62,6 @@ kotlin { api(libs.kotlinx.coroutines.test) } - jvmMain.dependencies { - compileOnly(libs.simbot.common.annotations) - } - jvmTest.dependencies { compileOnly(libs.simbot.common.annotations) implementation(libs.log4j.api) diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/api/simbot-component-onebot-v11-core.api b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/api/simbot-component-onebot-v11-core.api index 5e55e9c9..4cc210a0 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/api/simbot-component-onebot-v11-core.api +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/api/simbot-component-onebot-v11-core.api @@ -1104,7 +1104,10 @@ public abstract interface class love/forte/simbot/component/onebot/v11/core/api/ public abstract fun getAction ()Ljava/lang/String; public abstract fun getApiResultDeserializer ()Lkotlinx/serialization/DeserializationStrategy; public abstract fun getBody ()Ljava/lang/Object; + public fun getMethod ()Lio/ktor/http/HttpMethod; public abstract fun getResultDeserializer ()Lkotlinx/serialization/DeserializationStrategy; + public fun resolveUrlAction (Lio/ktor/http/URLBuilder;Ljava/util/Collection;)V + public fun resolveUrlExtensions (Lio/ktor/http/URLBuilder;)V } public final class love/forte/simbot/component/onebot/v11/core/api/OneBotApi$Actions { @@ -1161,6 +1164,10 @@ public final class love/forte/simbot/component/onebot/v11/core/api/OneBotApiExec public final synthetic fun unbox-impl ()Llove/forte/simbot/component/onebot/v11/core/api/OneBotApiExecutable; } +public final class love/forte/simbot/component/onebot/v11/core/api/OneBotApiKt { + public static final fun resolveUrl (Llove/forte/simbot/component/onebot/v11/core/api/OneBotApi;Lio/ktor/http/URLBuilder;Ljava/util/Collection;)V +} + public final class love/forte/simbot/component/onebot/v11/core/api/OneBotApiRequests { public static final fun getApiLogger ()Lorg/slf4j/Logger; public static final synthetic fun request (Llove/forte/simbot/component/onebot/v11/core/api/OneBotApi;Lio/ktor/client/HttpClient;Lio/ktor/http/Url;Ljava/lang/String;Ljava/util/Collection;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -2125,6 +2132,29 @@ public final class love/forte/simbot/component/onebot/v11/core/component/OneBot1 public static synthetic fun useOneBot11Component$default (Llove/forte/simbot/application/ApplicationFactoryConfigurer;Llove/forte/simbot/common/function/ConfigurerFunction;ILjava/lang/Object;)V } +public class love/forte/simbot/component/onebot/v11/core/event/CustomEventResolveException : java/lang/RuntimeException { + public fun ()V + public fun (Ljava/lang/String;)V + public fun (Ljava/lang/String;Ljava/lang/Throwable;)V + public fun (Ljava/lang/Throwable;)V +} + +public abstract interface class love/forte/simbot/component/onebot/v11/core/event/CustomEventResolver$Context { + public abstract fun getBot ()Llove/forte/simbot/component/onebot/v11/core/bot/OneBotBot; + public abstract fun getJson ()Lkotlinx/serialization/json/Json; + public abstract fun getRawEventResolveResult ()Llove/forte/simbot/component/onebot/v11/core/event/RawEventResolveResult; +} + +public class love/forte/simbot/component/onebot/v11/core/event/EventResolveException : java/lang/RuntimeException { + public fun ()V + public fun (Ljava/lang/String;)V + public fun (Ljava/lang/String;Ljava/lang/Throwable;)V + public fun (Ljava/lang/Throwable;)V +} + +public abstract interface annotation class love/forte/simbot/component/onebot/v11/core/event/ExperimentalCustomEventResolverApi : java/lang/annotation/Annotation { +} + public abstract interface class love/forte/simbot/component/onebot/v11/core/event/OneBotBotEvent : love/forte/simbot/component/onebot/v11/core/event/OneBotEvent, love/forte/simbot/event/BotEvent { public abstract fun getBot ()Llove/forte/simbot/component/onebot/v11/core/bot/OneBotBot; } diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/build.gradle.kts b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/build.gradle.kts index 6aa310bb..33aca71a 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/build.gradle.kts +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/build.gradle.kts @@ -105,10 +105,20 @@ kotlin { implementation(libs.log4j.slf4j2) implementation(libs.kotlinPoet) implementation(libs.kotlinx.coroutines.reactor) - api(libs.ktor.client.java) + implementation(libs.ktor.client.java) implementation(libs.ktor.server.netty) implementation(libs.ktor.server.ws) } + + appleTest.dependencies { + implementation(libs.ktor.client.darwin) + } + mingwTest.dependencies { + implementation(libs.ktor.client.winhttp) + } + linuxTest.dependencies { + implementation(libs.ktor.client.cio) + } } } diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/OneBotApi.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/OneBotApi.kt index b5cfdd59..51b94f99 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/OneBotApi.kt +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/OneBotApi.kt @@ -17,6 +17,7 @@ package love.forte.simbot.component.onebot.v11.core.api +import io.ktor.http.* import kotlinx.serialization.* import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor @@ -37,10 +38,48 @@ import love.forte.simbot.component.onebot.v11.core.api.OneBotApiResult.Companion */ public interface OneBotApi { /** - * API 的 action(要进行的动作) + * 此 API 的请求方式。 + * OneBot协议中的标准API通常均为 POST, + * 但是一些额外的扩展或自定义API可能是 GET 或其他方式。 + * @since 1.8.0 + */ + public val method: HttpMethod + get() = HttpMethod.Post + + /** + * API 的 action(要进行的动作),会通过 [resolveUrlAction] 附加在 url 中。 + * 可以重写它来改变此逻辑。 */ public val action: String + /** + * 根据 [action] 和可能额外要求的 [actionSuffixes] 构建一个完整的请求地址。 + * + * [urlBuilder] 中已经添加了基础的 `host` 等信息。 + * + * @since 1.8.0 + */ + public fun resolveUrlAction(urlBuilder: URLBuilder, actionSuffixes: Collection?) { + if (actionSuffixes?.isEmpty() != false) { + urlBuilder.appendPathSegments(action) + } else { + urlBuilder.appendPathSegments( + buildString(action.length) { + append(action) + actionSuffixes.forEach { sf -> append(sf) } + } + ) + } + } + + /** + * 对 [urlBuilder] 进行一些额外的处理,例如当method为GET时为其添加查询参数。 + * 主要面向额外扩展的自定义实现来重写此方法。 + * @since 1.8.0 + */ + public fun resolveUrlExtensions(urlBuilder: URLBuilder) { + } + /** * API的参数。 */ @@ -52,7 +91,7 @@ public interface OneBotApi { public val resultDeserializer: DeserializationStrategy /** - * 预期结果 [OneBotApi] 类型的反序列化器。 + * 预期结果 [OneBotApiResult] 类型的反序列化器。 */ public val apiResultDeserializer: DeserializationStrategy> @@ -76,6 +115,15 @@ public interface OneBotApi { } } +/** + * 使用 [OneBotApi.resolveUrlAction] 和 [OneBotApi.resolveUrlExtensions] 。 + * @since 1.8.0 + */ +public fun OneBotApi<*>.resolveUrl(urlBuilder: URLBuilder, actionSuffixes: Collection?) { + resolveUrlAction(urlBuilder, actionSuffixes) + resolveUrlExtensions(urlBuilder) +} + /** * [响应](https://github.com/botuniverse/onebot-11/blob/master/api/README.md#响应) * diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/OneBotApiRequests.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/OneBotApiRequests.kt index cd18c62c..4e32fd17 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/OneBotApiRequests.kt +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/OneBotApiRequests.kt @@ -102,19 +102,13 @@ public suspend fun OneBotApi<*>.request( accessToken: String? = null, actionSuffixes: Collection? = null, ): HttpResponse { - return client.post { + val api = this + + return client.request { + this.method = api.method url { takeFrom(host) - if (actionSuffixes?.isEmpty() != false) { - appendPathSegments(action) - } else { - appendPathSegments( - buildString(action.length) { - append(action) - actionSuffixes.forEach { sf -> append(sf) } - } - ) - } + api.resolveUrl(this, actionSuffixes) } headers { @@ -124,7 +118,7 @@ public suspend fun OneBotApi<*>.request( var jsonStr: String? = null - when (val b = this@request.body) { + when (val b = api.body) { null -> { if (GlobalOneBotApiRequestConfiguration.emptyJsonStringIfBodyNull) { setBody(EMPTY_JSON_STR) @@ -163,7 +157,7 @@ public suspend fun OneBotApi<*>.request( "API [{}] REQ ===> {}, body: {}, json: {}", action, url, - this@request.body, + api.body, jsonStr ) }.also { res -> diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/OneBotBot.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/OneBotBot.kt index d35901ed..7d742ba0 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/OneBotBot.kt +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/OneBotBot.kt @@ -43,6 +43,7 @@ import love.forte.simbot.component.onebot.v11.message.OneBotMessageContent import love.forte.simbot.event.EventResult import love.forte.simbot.message.MessageReference import love.forte.simbot.suspendrunner.ST +import org.intellij.lang.annotations.Language import kotlin.coroutines.CoroutineContext import kotlin.jvm.JvmSynthetic @@ -200,14 +201,14 @@ public interface OneBotBot : Bot, OneBotApiExecutable { * * @throws IllegalArgumentException 如果事件解析失败 */ - public fun push(rawEvent: String): Flow + public fun push(@Language("json") rawEvent: String): Flow /** * 直接推送一个外部的原始事件字符串,并在异步任务中处理事件。 * * @throws IllegalArgumentException 如果事件解析失败 */ - public fun pushAndLaunch(rawEvent: String): Job = + public fun pushAndLaunch(@Language("json") rawEvent: String): Job = push(rawEvent).launchIn(this) /** diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/OneBotBotConfiguration.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/OneBotBotConfiguration.kt index 5326636f..3674de73 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/OneBotBotConfiguration.kt +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/OneBotBotConfiguration.kt @@ -21,12 +21,17 @@ import io.ktor.client.* import io.ktor.client.engine.* import io.ktor.client.plugins.* import io.ktor.http.* +import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.overwriteWith import love.forte.simbot.common.function.ConfigurerFunction import love.forte.simbot.common.function.invokeBy import love.forte.simbot.component.onebot.common.annotations.ExperimentalOneBotAPI +import love.forte.simbot.component.onebot.v11.core.event.CustomEventResolver +import love.forte.simbot.component.onebot.v11.core.event.CustomKotlinSerializationEventResolver +import love.forte.simbot.component.onebot.v11.core.event.ExperimentalCustomEventResolverApi import love.forte.simbot.component.onebot.v11.message.segment.OneBotImage +import love.forte.simbot.event.Event import love.forte.simbot.resource.Resource import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -252,4 +257,56 @@ public class OneBotBotConfiguration { public fun defaultImageAdditionalParams(params: OneBotImage.AdditionalParams?) { defaultImageAdditionalParamsProvider = { params } } + + /** + * + * @since 1.8.0 + */ + @ExperimentalCustomEventResolverApi + internal val customEventResolvers: MutableList = mutableListOf() + + /** + * 注册一个 [CustomEventResolver]。 + * @since 1.8.0 + */ + @ExperimentalCustomEventResolverApi + public fun addCustomEventResolver(customEventResolver: CustomEventResolver) { + customEventResolvers.add(customEventResolver) + } +} + +/** + * 添加一个 [CustomKotlinSerializationEventResolver]。 + * + * @see OneBotBotConfiguration.addCustomEventResolver + * @since 1.8.0 + */ +@ExperimentalCustomEventResolverApi +public fun OneBotBotConfiguration.addCustomKotlinSerializationEventResolver( + resolver: CustomKotlinSerializationEventResolver +) { + addCustomEventResolver(resolver) +} + +/** + * 添加一个 [CustomKotlinSerializationEventResolver]。 + * 原则上通过 `postType` 和 `subType` 可以定位唯一一个事件类型。 + * + * @see OneBotBotConfiguration.addCustomEventResolver + * @since 1.8.0 + */ +@ExperimentalCustomEventResolverApi +public inline fun OneBotBotConfiguration.addCustomKotlinSerializationEventResolver( + postType: String, + subType: String, + crossinline deserializationStrategy: () -> DeserializationStrategy +) { + addCustomKotlinSerializationEventResolver { context -> + val rawEventResolveResult = context.rawEventResolveResult + if (rawEventResolveResult.postType == postType && rawEventResolveResult.subType == subType) { + deserializationStrategy() + } else { + null + } + } } diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/internal/OneBotBotImpl.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/internal/OneBotBotImpl.kt index 924dd516..7a5da119 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/internal/OneBotBotImpl.kt +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/internal/OneBotBotImpl.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject @@ -57,9 +58,7 @@ import love.forte.simbot.component.onebot.v11.core.bot.OneBotBotConfiguration import love.forte.simbot.component.onebot.v11.core.bot.OneBotBotFriendRelation import love.forte.simbot.component.onebot.v11.core.bot.OneBotBotGroupRelation import love.forte.simbot.component.onebot.v11.core.component.OneBot11Component -import love.forte.simbot.component.onebot.v11.core.event.OneBotInternalInterceptionException -import love.forte.simbot.component.onebot.v11.core.event.OneBotUnknownEvent -import love.forte.simbot.component.onebot.v11.core.event.OneBotUnsupportedEvent +import love.forte.simbot.component.onebot.v11.core.event.* import love.forte.simbot.component.onebot.v11.core.event.internal.message.* import love.forte.simbot.component.onebot.v11.core.event.internal.meta.OneBotHeartbeatEventImpl import love.forte.simbot.component.onebot.v11.core.event.internal.meta.OneBotLifecycleEventImpl @@ -69,6 +68,7 @@ import love.forte.simbot.component.onebot.v11.core.event.internal.request.OneBot import love.forte.simbot.component.onebot.v11.core.event.internal.stage.OneBotBotStartedEventImpl import love.forte.simbot.component.onebot.v11.core.internal.message.OneBotMessageContentImpl import love.forte.simbot.component.onebot.v11.core.utils.onEachErrorLog +import love.forte.simbot.component.onebot.v11.event.RawEvent import love.forte.simbot.component.onebot.v11.event.UnknownEvent import love.forte.simbot.component.onebot.v11.event.message.RawGroupMessageEvent import love.forte.simbot.component.onebot.v11.event.message.RawPrivateMessageEvent @@ -82,6 +82,7 @@ import love.forte.simbot.component.onebot.v11.event.resolveEventSubTypeFieldName import love.forte.simbot.component.onebot.v11.message.OneBotMessageContent import love.forte.simbot.event.* import love.forte.simbot.logger.LoggerFactory +import love.forte.simbot.logger.isDebugEnabled import kotlin.concurrent.Volatile import kotlin.coroutines.CoroutineContext import kotlin.math.max @@ -124,6 +125,8 @@ internal class OneBotBotImpl( override val apiClient: HttpClient private val wsClient: HttpClient? + @ExperimentalCustomEventResolverApi + internal val customEventResolvers = configuration.customEventResolvers.toList() init { apiClient = resolveHttpClient() @@ -486,8 +489,8 @@ internal class OneBotBotImpl( logger.debug("Received raw event: {}", eventRaw) - val event = kotlin.runCatching { - resolveRawEvent(eventRaw) + val event = runCatching { + resolveEvent(eventRaw) }.getOrElse { e -> val exMsg = "Failed to resolve raw event $eventRaw, " + "session and bot will be closed exceptionally" @@ -496,16 +499,17 @@ internal class OneBotBotImpl( exMsg, e ) + // 接收的事件解析出现错误, // 这应该是预期外的情况, // 直接终止 session, 但是不终止 Bot, // 只有当重连次数用尽才考虑终止 Bot。 + // TODO 终止session吗? session.closeExceptionally(ex) - throw ex } - pushEvent(resolveRawEventToEvent(eventRaw, event)) + pushEvent(event) .launchIn(this@OneBotBotImpl) } } @@ -631,7 +635,7 @@ internal class OneBotBotImpl( override fun push(rawEvent: String): Flow { val event = kotlin.runCatching { - resolveRawEvent(rawEvent) + resolveEvent(rawEvent) }.getOrElse { e -> val exMsg = "Failed to resolve raw event $rawEvent, " + "session and bot will be closed exceptionally" @@ -639,7 +643,7 @@ internal class OneBotBotImpl( throw IllegalArgumentException(exMsg, e) } - return pushEvent(resolveRawEventToEvent(rawEvent, event)) + return pushEvent(event) } internal fun pushEvent(event: Event): Flow { @@ -670,16 +674,91 @@ internal class OneBotBotImpl( "OneBotBot(uniqueId='$uniqueId', isStarted=$isStarted, isActive=$isActive)" } +/** + * 解析数据包为 Event。 + */ +@OptIn(FragileSimbotAPI::class, ExperimentalCustomEventResolverApi::class) +internal fun OneBotBotImpl.resolveEvent(text: String): Event { + val eventResolvedResult = resolveRawEvent(text) + val rawEvent = eventResolvedResult.rawEvent + + var event: Event? = null + var error: Throwable? = eventResolvedResult.reason?.let(::EventResolveException) + + if (customEventResolvers.isNotEmpty()) { + val errors = mutableListOf() + val context = CustomEventResolverContextImpl(this, OneBot11.DefaultJson, eventResolvedResult) + + for (resolver in customEventResolvers) { + runCatching { + event = resolver.resolve(context) + }.onFailure { e -> + errors.add(e) + } + + if (event != null) { + break + } + } + + if (errors.isNotEmpty()) { + val newError = CustomEventResolveException("Some errors occurred while resolving custom events.") + errors.forEach { newError.addSuppressed(it) } + error = error?.also { + it.addSuppressed(newError) + } ?: newError + } + } + + if (event == null) { + if (rawEvent != null) { + logger.debug( + "No custom event resolvers resolved this event '{}', use default resolver for raw event {}.", + text, + rawEvent + ) + event = resolveRawEventToEvent(text, rawEvent) + } else { + logger.debug("No custom event resolvers resolved this event '{}', use unknown event.", text) + event = OneBotUnknownEvent( + sourceEventRaw = text, + sourceEvent = UnknownEvent( + time = eventResolvedResult.time ?: 0L, + selfId = eventResolvedResult.selfId ?: 0L.ID, + postType = eventResolvedResult.postType, + raw = text, + rawJson = eventResolvedResult.json, + reason = error, + ) + ) + } + } + + if (error != null) { + if (logger.isDebugEnabled) { + logger.error("Something failed when resolving event", error) + } else { + logger.error("Something failed when resolving event. See DEBUG level log for more detail.", error) + } + logger.debug("Something failed when resolving event '{}'", text, error) + } + + return event +} + +@ExperimentalCustomEventResolverApi +private data class CustomEventResolverContextImpl( + override val bot: OneBotBot, + override val json: Json, + override val rawEventResolveResult: RawEventResolveResult +) : CustomEventResolver.Context /** * 解析数据包字符串为 [Event]。 */ -@OptIn(FragileSimbotAPI::class) -internal fun OneBotBotImpl.resolveRawEvent(text: String): OBRawEvent { - val obj = OneBot11.DefaultJson.decodeFromString( - JsonObject.serializer(), - text - ) +@ExperimentalCustomEventResolverApi +internal fun OneBotBotImpl.resolveRawEvent(text: String): RawEventResolveResult { + val obj = OneBot11.DefaultJson.decodeFromString(JsonObject.serializer(), text) val postType = requireNotNull(obj["post_type"]?.jsonPrimitive?.content) { "Missing required event property 'post_type'" @@ -688,20 +767,31 @@ internal fun OneBotBotImpl.resolveRawEvent(text: String): OBRawEvent { val subTypeFieldName = resolveEventSubTypeFieldName(postType) ?: "${postType}_type" val subType = obj[subTypeFieldName]?.jsonPrimitive?.content - fun toUnknown(reason: Throwable? = null): UnknownEvent { - val time = obj["time"]?.jsonPrimitive?.long ?: -1L - val selfId = obj["self_id"]?.jsonPrimitive?.long?.ID ?: 0L.ID - return UnknownEvent(time, selfId, postType, text, obj, reason) + val time = obj["time"]?.jsonPrimitive?.long + val selfId = obj["self_id"]?.jsonPrimitive?.long?.ID + + fun toResult(rawEvent: RawEvent? = null, reason: Throwable? = null): RawEventResolveResult { + return RawEventResolveResultImpl( + text = text, + json = obj, + postType = postType, + subType = subType, + time = time, + selfId = selfId, + rawEvent = rawEvent, + reason = reason, + ) } if (subType == null) { - // 一个不规则的 unknown event - return toUnknown() + return toResult() } - resolveEventSerializer(postType, subType)?.let { - return try { - OneBot11.DefaultJson.decodeFromJsonElement(it, obj) + val deserializationStrategy: DeserializationStrategy? = resolveEventSerializer(postType, subType) + return if (deserializationStrategy != null) { + try { + val decodedRawEvent = OneBot11.DefaultJson.decodeFromJsonElement(deserializationStrategy, obj) + toResult(rawEvent = decodedRawEvent) } catch (serEx: SerializationException) { logger.error( "Received raw event '{}' decode failed because of serialization: {}" + @@ -711,20 +801,10 @@ internal fun OneBotBotImpl.resolveRawEvent(text: String): OBRawEvent { serEx ) - toUnknown(serEx) - } catch (argEx: IllegalArgumentException) { - logger.error( - "Received raw event '{}' decode failed because of illegal argument: {}" + - "It will be pushed as an UnknownEvent", - text, - argEx.message, - argEx - ) - - toUnknown(argEx) + toResult(reason = serEx) } - } ?: run { - return toUnknown() + } else { + toResult() } } diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/internal/RawEventResolveResultImpl.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/internal/RawEventResolveResultImpl.kt new file mode 100644 index 00000000..5d0640c9 --- /dev/null +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/internal/RawEventResolveResultImpl.kt @@ -0,0 +1,24 @@ +package love.forte.simbot.component.onebot.v11.core.bot.internal + +import kotlinx.serialization.json.JsonObject +import love.forte.simbot.common.id.LongID +import love.forte.simbot.component.onebot.v11.core.event.ExperimentalCustomEventResolverApi +import love.forte.simbot.component.onebot.v11.core.event.RawEventResolveResult +import love.forte.simbot.component.onebot.v11.event.RawEvent + +/** + * 一个事件文本被进行解析后的主要内容。 + * + * @author ForteScarlet + */ +@ExperimentalCustomEventResolverApi +internal data class RawEventResolveResultImpl( + override val text: String, + override val json: JsonObject, + override val postType: String, + override val subType: String?, + override val time: Long?, + override val selfId: LongID?, + override val rawEvent: RawEvent?, + override val reason: Throwable? +) : RawEventResolveResult diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolveException.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolveException.kt new file mode 100644 index 00000000..c78f9757 --- /dev/null +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolveException.kt @@ -0,0 +1,17 @@ +package love.forte.simbot.component.onebot.v11.core.event + +/** + * 当 [CustomEventResolver] 发生异常时, + * 异常会被收集到 [CustomEventResolveException.suppressedExceptions] 中, + * 并被最终输出。 + * + * @since 1.8.0 + * + * @author ForteScarlet + */ +public open class CustomEventResolveException : RuntimeException { + public constructor() : super() + public constructor(cause: Throwable?) : super(cause) + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) +} diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolver.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolver.kt new file mode 100644 index 00000000..58769596 --- /dev/null +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolver.kt @@ -0,0 +1,66 @@ +package love.forte.simbot.component.onebot.v11.core.event + +import kotlinx.serialization.json.Json +import love.forte.simbot.component.onebot.v11.core.OneBot11 +import love.forte.simbot.component.onebot.v11.core.bot.OneBotBot +import love.forte.simbot.event.Event + +/** + * 自定义事件解析器。根据得到的初步解析结果,将事件内容解析为一个 [Event]。 + * + * 当存在多个自定义解析器,则会在**首次**得到非 `null` 结果时终止处理。 + * + * 如果处理链上得到的全部都为 `null`,则会在 [RawEventResolveResult.rawEvent] + * 不为 `null` 的情况下尝试解析为标准事件。如果无法解析,则最终会被解析为 + * [love.forte.simbot.component.onebot.v11.event.UnknownEvent] 和对应的 + * [OneBotUnknownEvent]。 + * + * ### 异常 + * + * 如果 [resolve] 产生异常,此异常会被暂时记录,并继续尝试使用其他解析器。 + * 如果最终所有的 [CustomEventResolver] 都无法得到有效的结果,则: + * - 如果 [RawEventResolveResult.rawEvent] 不为 `null`,则会尝试解析为标准事件。 + * - 如果解析成功,则记录的异常会以 **异常日志** 的形式输出。 + * - 如果解析失败,则会使用 [OneBotUnsupportedEvent] 进行包装,记录的异常会以 **异常日志** 的形式输出。 + * - 如果 [RawEventResolveResult.rawEvent] 为 `null`,则会使用 [OneBotUnknownEvent] 进行包装, + * 记录的异常会以被整合并填充到 [love.forte.simbot.component.onebot.v11.event.UnknownEvent.reason] 中, + * 并以 **异常日志** 的形式输出。 + * + * @see CustomEventResolveException + * @since 1.8.0 + * @author ForteScarlet + */ +@ExperimentalCustomEventResolverApi +public fun interface CustomEventResolver { + + /** + * 根据提供的内容信息,解析得到一个 [Event]。 + * + * @return 解析结果。如果跳过解析则返回 `null`。 + * @throws Exception 解析过程可能出现的异常。 + */ + @Throws(Exception::class) + public fun resolve(context: Context): Event? + + /** + * 提供给 [CustomEventResolver.resolve] 进行处理的内容。 + */ + public interface Context { + /** + * 当前的 [OneBotBot] 对象。 + */ + public val bot: OneBotBot + + /** + * 可用于解析JSON的解析器。 + * + * @see OneBot11.DefaultJson + */ + public val json: Json + + /** + * 对原始事件内容的初步解析结果。 + */ + public val rawEventResolveResult: RawEventResolveResult + } +} diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomKotlinSerializationEventResolver.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomKotlinSerializationEventResolver.kt new file mode 100644 index 00000000..6d1cda24 --- /dev/null +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomKotlinSerializationEventResolver.kt @@ -0,0 +1,24 @@ +package love.forte.simbot.component.onebot.v11.core.event + +import kotlinx.serialization.DeserializationStrategy +import love.forte.simbot.event.Event + +/** + * 基于 Kotlin Serialization 反序列化器的 [CustomEventResolver]。 + * @since 1.8.0 + * @author ForteScarlet + */ +@ExperimentalCustomEventResolverApi +public fun interface CustomKotlinSerializationEventResolver : CustomEventResolver { + + /** + * 根据 [context] 得到一个 [DeserializationStrategy]。 + */ + public fun serializer(context: CustomEventResolver.Context): DeserializationStrategy? + + override fun resolve(context: CustomEventResolver.Context): Event? { + return serializer(context)?.let { + return context.json.decodeFromJsonElement(it, context.rawEventResolveResult.json) + } + } +} diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/EventResolveException.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/EventResolveException.kt new file mode 100644 index 00000000..2cfff3e0 --- /dev/null +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/EventResolveException.kt @@ -0,0 +1,14 @@ +package love.forte.simbot.component.onebot.v11.core.event + +/** + * 事件解析异常 + * + * @since 1.8.0 + * @author ForteScarlet + */ +public open class EventResolveException : RuntimeException { + public constructor() : super() + public constructor(cause: Throwable?) : super(cause) + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) +} diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/ExperimentalCustomEventResolverApi.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/ExperimentalCustomEventResolverApi.kt new file mode 100644 index 00000000..8b444235 --- /dev/null +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/ExperimentalCustomEventResolverApi.kt @@ -0,0 +1,14 @@ +package love.forte.simbot.component.onebot.v11.core.event + +/** + * 尚在实验中的自定义事件解析器API,可能在未来进行重大改动或被移除。 + * + * @since 1.8.0 + * + * @author ForteScarlet + */ +@RequiresOptIn( + "尚在实验中的自定义事件解析器API,可能在未来进行重大改动或被移除。", + RequiresOptIn.Level.WARNING, +) +public annotation class ExperimentalCustomEventResolverApi diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/RawEventResolveResult.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/RawEventResolveResult.kt new file mode 100644 index 00000000..24ac55e1 --- /dev/null +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/event/RawEventResolveResult.kt @@ -0,0 +1,66 @@ +package love.forte.simbot.component.onebot.v11.core.event + +import kotlinx.serialization.json.JsonObject +import love.forte.simbot.common.id.LongID +import love.forte.simbot.component.onebot.v11.event.RawEvent + +/** + * 一个原始事件文本被解析后的基本结果。 + * + * @since 1.8.0 + * @author ForteScarlet + */ +@ExperimentalCustomEventResolverApi +public interface RawEventResolveResult { + /** + * 原始的事件JSON字符串文本。 + */ + public val text: String + + /** + * 经过解析后的 [JsonObject]。 + */ + public val json: JsonObject + + /** + * [json] 的 `post_type` 属性。 + * 在OneBot协议中这个属性的必须的,用于对事件进行首层分类。 + */ + public val postType: String + + /** + * [json] 的 `sub_type` 属性。 + * 在OneBot标准协议中这个属性始终存在,且获取它的 JSON KEY + * 等同于 `$postType_type`。 + * 以标准事件为例子, + * ```json + * { + * "post_type": "message", + * "message_type": "private" + * } + * ``` + * 当 `post_type` 为 `message` 时,`sub_type` 即代表 `message_type`,则其值为 `private`。 + */ + public val subType: String? + + /** + * jsonObject 的 `time` 属性。 + */ + public val time: Long? + + /** + * jsonObject 的 `self_id` 属性。是一个长整型ID。 + */ + public val selfId: LongID? + + /** + * 如果能被标准事件类型成功解析,则此处为被解析出来的标准事件,否则为 `null`。 + */ + public val rawEvent: RawEvent? + + /** + * 如果 [rawEvent] 是因为某些异常(例如序列化异常)才导致无法解析得到 `null` 的, + * 则此处为解析时的异常。 + */ + public val reason: Throwable? +} diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonTest/kotlin/love/forte/simbot/component/onebot/v11/core/api/ApiRequestTests.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonTest/kotlin/love/forte/simbot/component/onebot/v11/core/api/ApiRequestTests.kt index afbea77a..3f66b303 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonTest/kotlin/love/forte/simbot/component/onebot/v11/core/api/ApiRequestTests.kt +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonTest/kotlin/love/forte/simbot/component/onebot/v11/core/api/ApiRequestTests.kt @@ -2,8 +2,12 @@ package love.forte.simbot.component.onebot.v11.core.api import io.ktor.client.* import io.ktor.client.engine.mock.* +import io.ktor.client.statement.* +import io.ktor.http.* import kotlinx.coroutines.test.runTest +import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable import love.forte.simbot.common.id.IntID.Companion.ID import love.forte.simbot.common.id.literal import love.forte.simbot.component.onebot.common.annotations.ApiResultConstructor @@ -70,4 +74,34 @@ class ApiRequestTests { assertEquals("123", data.messageId.literal) } + @Test + fun customGetApiTest() = runTest { + @Serializable + data class CustomResult(val name: String) + class MyCustomApi : OneBotApi { + override val method: HttpMethod = HttpMethod.Get + override val action: String = "custom_action" + override val body: Any? = null + override val resultDeserializer: DeserializationStrategy = CustomResult.serializer() + override val apiResultDeserializer: DeserializationStrategy> = + OneBotApiResult.serializer(CustomResult.serializer()) + + override fun resolveUrlExtensions(urlBuilder: URLBuilder) { + urlBuilder.parameters.append("name", "forte") + } + } + + val client = createClient( + "custom_action", + respDataSer = { CustomResult.serializer() }, + respData = { CustomResult("forte") } + ) + + val resp = MyCustomApi().request(client, "http://127.0.0.1:8080/") + assertEquals(HttpMethod.Get, resp.request.method) + assertEquals("forte", resp.request.url.parameters["name"]) + + val data = MyCustomApi().requestData(client, "http://127.0.0.1:8080/") + assertEquals("forte", data.name) + } } diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonTest/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolverTests.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonTest/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolverTests.kt new file mode 100644 index 00000000..51b0b95e --- /dev/null +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonTest/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolverTests.kt @@ -0,0 +1,150 @@ +package love.forte.simbot.component.onebot.v11.core.event + +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import love.forte.simbot.annotations.ExperimentalSimbotAPI +import love.forte.simbot.application.listeners +import love.forte.simbot.common.atomic.atomic +import love.forte.simbot.common.id.ID +import love.forte.simbot.common.id.UUID +import love.forte.simbot.common.time.Timestamp +import love.forte.simbot.component.onebot.v11.core.bot.OneBotBot +import love.forte.simbot.component.onebot.v11.core.bot.OneBotBotConfiguration +import love.forte.simbot.component.onebot.v11.core.bot.addCustomKotlinSerializationEventResolver +import love.forte.simbot.component.onebot.v11.core.bot.register +import love.forte.simbot.component.onebot.v11.core.oneBot11Bots +import love.forte.simbot.component.onebot.v11.core.useOneBot11 +import love.forte.simbot.core.application.launchSimpleApplication +import love.forte.simbot.event.Event +import love.forte.simbot.event.EventListenerRegistrar +import love.forte.simbot.event.FuzzyEventTypeImplementation +import love.forte.simbot.event.process +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * + * @author ForteScarlet + */ +class CustomEventResolverTests { + /** + * postType: custom + * + * subType: test1 + * + * ```json + * { + * "post_type": "custom", + * "custom_type": "test1", + * "value": "test1" + * } + * ``` + */ + @OptIn(FuzzyEventTypeImplementation::class) + @Serializable + private data class CustomEvent1( + @SerialName("post_type") + val postType: String, + @SerialName("custom_type") + val customType: String, + val value: String + ) : Event { + override val id: ID = UUID.random() + + @OptIn(ExperimentalSimbotAPI::class) + override val time: Timestamp = Timestamp.now() + } + + private suspend inline fun bot( + eventHandle: EventListenerRegistrar.() -> Unit = {}, + config: OneBotBotConfiguration.() -> Unit = {} + ): OneBotBot { + val app = launchSimpleApplication { + useOneBot11() + } + + app.listeners { + eventHandle() + } + + app.oneBot11Bots { + return register { + botUniqueId = UUID.random().toString() + config() + } + } + } + + @OptIn(ExperimentalCustomEventResolverApi::class) + @Test + fun testCustomEventResolver() = runTest { + val count = atomic(0) + + val bot = bot({ + process { event -> + assertEquals("custom", event.postType) + assertEquals("test1", event.customType) + assertEquals("Hello, World", event.value) + count.incrementAndGet() + } + }) { + addCustomEventResolver { context -> + val rawEventResolveResult = context.rawEventResolveResult + if (rawEventResolveResult.postType == "custom" && rawEventResolveResult.subType == "test1") { + return@addCustomEventResolver context.json.decodeFromJsonElement( + CustomEvent1.serializer(), + rawEventResolveResult.json + ) + } + + null + } + } + + bot.push( + """ + { + "post_type": "custom", + "custom_type": "test1", + "value": "Hello, World" + } + """.trimIndent() + ).collect() + + assertEquals(1, count.value) + } + + @OptIn(ExperimentalCustomEventResolverApi::class) + @Test + fun testCustomEventResolverByKtxSerialization() = runTest { + val count = atomic(0) + + val bot = bot({ + process { event -> + assertEquals("custom", event.postType) + assertEquals("test1", event.customType) + assertEquals("Hello, World", event.value) + count.incrementAndGet() + } + }) { + addCustomKotlinSerializationEventResolver("custom", "test1") { + CustomEvent1.serializer() + } + } + + bot.push( + """ + { + "post_type": "custom", + "custom_type": "test1", + "value": "Hello, World" + } + """.trimIndent() + ).collect() + + assertEquals(1, count.value) + } + +} diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-event/api/simbot-component-onebot-v11-event.api b/simbot-component-onebot-v11/simbot-component-onebot-v11-event/api/simbot-component-onebot-v11-event.api index e1142e50..30a0f741 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-event/api/simbot-component-onebot-v11-event.api +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-event/api/simbot-component-onebot-v11-event.api @@ -4,6 +4,13 @@ public abstract interface class love/forte/simbot/component/onebot/v11/event/Raw public abstract fun getTime ()J } +public class love/forte/simbot/component/onebot/v11/event/RawEventDeserializationException : java/lang/RuntimeException { + public fun ()V + public fun (Ljava/lang/String;)V + public fun (Ljava/lang/String;Ljava/lang/Throwable;)V + public fun (Ljava/lang/Throwable;)V +} + public final class love/forte/simbot/component/onebot/v11/event/UnknownEvent : love/forte/simbot/component/onebot/v11/event/RawEvent { public fun equals (Ljava/lang/Object;)Z public fun getPostType ()Ljava/lang/String; diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-event/build.gradle.kts b/simbot-component-onebot-v11/simbot-component-onebot-v11-event/build.gradle.kts index 1469f2b3..2db583eb 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-event/build.gradle.kts +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-event/build.gradle.kts @@ -57,7 +57,7 @@ kotlin { implementation(libs.simbot.api) api(libs.simbot.common.annotations) api(project(":simbot-component-onebot-common")) - implementation(libs.kotlinx.serialization.json) + api(libs.kotlinx.serialization.json) api(project(":simbot-component-onebot-v11:simbot-component-onebot-v11-common")) api(project(":simbot-component-onebot-v11:simbot-component-onebot-v11-message")) diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-event/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/event/RawEventDeserializationException.kt b/simbot-component-onebot-v11/simbot-component-onebot-v11-event/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/event/RawEventDeserializationException.kt new file mode 100644 index 00000000..cbfcf8cb --- /dev/null +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-event/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/event/RawEventDeserializationException.kt @@ -0,0 +1,14 @@ +package love.forte.simbot.component.onebot.v11.event + +/** + * + * [RawEvent] 的反序列化异常。 + * @since 1.8.0 + * @author ForteScarlet + */ +public open class RawEventDeserializationException : RuntimeException { + public constructor() : super() + public constructor(cause: Throwable?) : super(cause) + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) +} diff --git a/simbot-component-onebot-v11/simbot-component-onebot-v11-message/build.gradle.kts b/simbot-component-onebot-v11/simbot-component-onebot-v11-message/build.gradle.kts index 17217c94..11a4e61a 100644 --- a/simbot-component-onebot-v11/simbot-component-onebot-v11-message/build.gradle.kts +++ b/simbot-component-onebot-v11/simbot-component-onebot-v11-message/build.gradle.kts @@ -63,8 +63,8 @@ kotlin { api(libs.simbot.common.annotations) api(libs.kotlinx.coroutines.core) + api(libs.kotlinx.serialization.json) implementation(libs.kotlinx.io.core) - implementation(libs.kotlinx.serialization.json) implementation(libs.jetbrains.annotations) }