From 2f87344a5ef27a609c223dd069edf62323f45369 Mon Sep 17 00:00:00 2001 From: Aliorpse Date: Fri, 3 Apr 2026 17:11:21 +0800 Subject: [PATCH 1/3] refactor!: migrate to context parameters --- detekt.yml | 3 + gradle/libs.versions.toml | 2 +- saltify-core/build.gradle.kts | 4 + .../ContextParametersMigrationNeeded.kt | 7 - .../saltify/builtin/plugin/CommandHelp.kt | 1 + .../saltify/builtin/plugin/DefaultLogging.kt | 6 +- .../context/ApplicationExecutionContext.kt | 10 ++ .../saltify/context/EventExecutionContext.kt | 15 +++ .../saltify/dsl/SaltifyCommandContext.kt | 124 ++++-------------- .../saltify/dsl/SaltifyPluginContext.kt | 83 +----------- .../entity/CommandRequirementContext.kt | 7 + .../SaltifyCommandRequirementContext.kt | 7 - .../saltify/extension/ApplicationExtension.kt | 51 ++++--- .../extension/CommandContextExtension.kt | 13 +- .../saltify/extension/EventExtension.kt | 77 +++++++++-- .../extension/PluginContextExtension.kt | 1 - .../saltify/extension/RequirementExtension.kt | 16 +-- .../coroutine/RunCatchingToExceptionFlow.kt | 8 +- .../kotlin/org/ntqqrev/saltify/PluginTest.kt | 10 +- saltify-docs/content/docs-core/command.md | 2 + 20 files changed, 194 insertions(+), 253 deletions(-) delete mode 100644 saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/annotation/ContextParametersMigrationNeeded.kt create mode 100644 saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/ApplicationExecutionContext.kt create mode 100644 saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/EventExecutionContext.kt create mode 100644 saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementContext.kt delete mode 100644 saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/SaltifyCommandRequirementContext.kt delete mode 100644 saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/PluginContextExtension.kt diff --git a/detekt.yml b/detekt.yml index c15ddd5..2f0dff8 100644 --- a/detekt.yml +++ b/detekt.yml @@ -3,3 +3,6 @@ buildUponDefaultConfig: true style: WildcardImport: active: false + MaxLineLength: + active: true + maxLineLength: 150 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd87b5d..c0c843b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.3.20" +kotlin = "2.4.0-Beta1" coroutines = "1.10.2" serialization = "1.10.0" ktor = "3.4.2" diff --git a/saltify-core/build.gradle.kts b/saltify-core/build.gradle.kts index dd83861..5d02fa2 100644 --- a/saltify-core/build.gradle.kts +++ b/saltify-core/build.gradle.kts @@ -39,6 +39,10 @@ kotlin { } } + compilerOptions { + freeCompilerArgs.add("-Xcontext-parameters") + } + explicitApi() } diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/annotation/ContextParametersMigrationNeeded.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/annotation/ContextParametersMigrationNeeded.kt deleted file mode 100644 index 38c471d..0000000 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/annotation/ContextParametersMigrationNeeded.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.ntqqrev.saltify.annotation - -/** - * 由这个注解标注的定义会在将来 Context Parameters 稳定后迁移到 extension 软件包下。 - */ -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) -internal annotation class ContextParametersMigrationNeeded diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/CommandHelp.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/CommandHelp.kt index 55bdc10..245486c 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/CommandHelp.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/CommandHelp.kt @@ -6,6 +6,7 @@ import org.ntqqrev.saltify.core.text import org.ntqqrev.saltify.dsl.SaltifyPlugin import org.ntqqrev.saltify.entity.RegisteredCommandInfo import org.ntqqrev.saltify.entity.RegisteredSubCommandInfo +import org.ntqqrev.saltify.extension.command import org.ntqqrev.saltify.extension.respond /** diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/DefaultLogging.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/DefaultLogging.kt index 6cfb85a..f1dc894 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/DefaultLogging.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/DefaultLogging.kt @@ -5,6 +5,8 @@ import kotlinx.coroutines.launch import org.ntqqrev.milky.Event import org.ntqqrev.milky.IncomingMessage import org.ntqqrev.saltify.dsl.SaltifyPlugin +import org.ntqqrev.saltify.context.event +import org.ntqqrev.saltify.extension.on import org.ntqqrev.saltify.extension.plainText import org.ntqqrev.saltify.model.EventConnectionState import org.ntqqrev.saltify.model.SaltifyComponentType @@ -53,8 +55,8 @@ public val defaultLogging: SaltifyPlugin = SaltifyPlugin("default-logging" } } - // 收到消息日志 - on { event -> + // 消息日志 + on { when (val data = event.data) { is IncomingMessage.Group -> logger.debug( diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/ApplicationExecutionContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/ApplicationExecutionContext.kt new file mode 100644 index 0000000..afc3a7a --- /dev/null +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/ApplicationExecutionContext.kt @@ -0,0 +1,10 @@ +package org.ntqqrev.saltify.context + +import org.ntqqrev.saltify.core.SaltifyApplication + +public open class ApplicationExecutionContext( + public open val client: SaltifyApplication +) + +context(ctx: ApplicationExecutionContext) +public val client: SaltifyApplication get() = ctx.client diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/EventExecutionContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/EventExecutionContext.kt new file mode 100644 index 0000000..afb0b7c --- /dev/null +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/EventExecutionContext.kt @@ -0,0 +1,15 @@ +package org.ntqqrev.saltify.context + +import org.ntqqrev.milky.Event +import org.ntqqrev.saltify.core.SaltifyApplication + +public open class EventExecutionContext( + public open val event: T, + public open val client: SaltifyApplication +) + +context(ctx: EventExecutionContext) +public val event: T get() = ctx.event + +context(ctx: EventExecutionContext) +public val client: SaltifyApplication get() = ctx.client diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyCommandContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyCommandContext.kt index eb4e24f..014d090 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyCommandContext.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyCommandContext.kt @@ -1,41 +1,26 @@ package org.ntqqrev.saltify.dsl import io.ktor.util.logging.* -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.withTimeoutOrNull import org.ntqqrev.milky.Event -import org.ntqqrev.milky.IncomingMessage -import org.ntqqrev.milky.OutgoingSegment -import org.ntqqrev.saltify.annotation.ContextParametersMigrationNeeded import org.ntqqrev.saltify.annotation.SaltifyDsl import org.ntqqrev.saltify.core.SaltifyApplication -import org.ntqqrev.saltify.core.recallGroupMessage -import org.ntqqrev.saltify.core.recallPrivateMessage -import org.ntqqrev.saltify.entity.SaltifyCommandRequirementContext -import org.ntqqrev.saltify.extension.respond +import org.ntqqrev.saltify.entity.CommandRequirementContext +import org.ntqqrev.saltify.context.EventExecutionContext import org.ntqqrev.saltify.model.CommandError import org.ntqqrev.saltify.model.CommandRequirement -import org.ntqqrev.saltify.model.milky.SendMessageOutput import kotlin.reflect.KClass -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds @SaltifyDsl public class SaltifyCommandContext internal constructor() { internal val subCommands = mutableListOf>() internal val parameters = mutableListOf>() - internal var executionBlock: (suspend SaltifyCommandExecutionContext.() -> Unit)? = null - - /** - * 指令的描述信息。 - */ public var description: String = "" - internal var groupExecutionBlock: (suspend SaltifyCommandExecutionContext.() -> Unit)? = null - internal var privateExecutionBlock: (suspend SaltifyCommandExecutionContext.() -> Unit)? = null - internal var failureBlock: (suspend SaltifyCommandExecutionContext.(CommandError) -> Unit)? = null - internal var requirementBlock: (SaltifyCommandRequirementContext.() -> CommandRequirement)? = null + + internal var executionBlock: (suspend CommandExecutionContext.() -> Unit)? = null + internal var groupExecutionBlock: (suspend CommandExecutionContext.() -> Unit)? = null + internal var privateExecutionBlock: (suspend CommandExecutionContext.() -> Unit)? = null + internal var failureBlock: (suspend CommandExecutionContext.(CommandError) -> Unit)? = null + internal var requirementBlock: (CommandRequirementContext.() -> CommandRequirement)? = null /** * 注册一个子指令。 @@ -47,128 +32,69 @@ public class SaltifyCommandContext internal constructor() { /** * 定义指令执行要求。若不满足,静默返回。 */ - public fun require(block: SaltifyCommandRequirementContext.() -> CommandRequirement) { + public fun require(block: CommandRequirementContext.() -> CommandRequirement) { this.requirementBlock = block } /** - * 定义一个指令参数。请搭配 [SaltifyCommandExecutionContext.capture] 使用。 + * 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。 */ public fun parameter( type: KClass, name: String, description: String = "" - ): SaltifyCommandParamDef { - return SaltifyCommandParamDef(type, name, description).also { parameters.add(it) } - } + ): SaltifyCommandParamDef = + SaltifyCommandParamDef(type, name, description).also { parameters.add(it) } /** - * 定义一个贪婪字符串参数。该参数会捕获剩余的**所有**文本内容。请搭配 [SaltifyCommandExecutionContext.capture] 使用。 + * 定义一个贪婪字符串参数。该参数会捕获剩余的**所有**文本内容。请搭配 [CommandExecutionContext.value] 使用。 */ public fun greedyStringParameter( name: String, description: String = "" - ): SaltifyCommandParamDef { - return SaltifyCommandParamDef(String::class, name, description, isGreedy = true).also { parameters.add(it) } - } + ): SaltifyCommandParamDef = + SaltifyCommandParamDef(String::class, name, description, isGreedy = true).also { parameters.add(it) } /** * 设置通用的指令执行逻辑。 */ - public fun onExecute(block: suspend SaltifyCommandExecutionContext.() -> Unit) { + public fun onExecute(block: suspend CommandExecutionContext.() -> Unit) { executionBlock = block } /** * 设置仅在群聊中触发的执行逻辑。优先级高于 [onExecute],定义后在群聊不会使用 [onExecute]。 */ - public fun onGroupExecute(block: suspend SaltifyCommandExecutionContext.() -> Unit) { + public fun onGroupExecute(block: suspend CommandExecutionContext.() -> Unit) { groupExecutionBlock = block } /** - * 设置仅在私聊中触发的执行逻辑。优先级高于 [onExecute],定义后在群聊不会使用 [onExecute]。 + * 设置仅在私聊中触发的执行逻辑。优先级高于 [onExecute],定义后在私聊不会使用 [onExecute]。 */ - public fun onPrivateExecute(block: suspend SaltifyCommandExecutionContext.() -> Unit) { + public fun onPrivateExecute(block: suspend CommandExecutionContext.() -> Unit) { privateExecutionBlock = block } /** * 当指令**解析**失败时执行的逻辑。 */ - public fun onFailure(block: suspend SaltifyCommandExecutionContext.(CommandError) -> Unit) { + public fun onFailure(block: suspend CommandExecutionContext.(CommandError) -> Unit) { failureBlock = block } } -public class SaltifyCommandExecutionContext( - public val client: SaltifyApplication, - public val event: Event.MessageReceive, +public class CommandExecutionContext( + public override val event: Event.MessageReceive, + public override val client: SaltifyApplication, commandName: String, private val argumentMap: Map, Any?> -) { +) : EventExecutionContext(event, client) { public val logger: Logger = KtorSimpleLogger("Saltify/cmd:$commandName") - /** - * 获取已解析的参数值。你可能更需要的是 [SaltifyCommandParamDef.value]。 - */ - @Suppress("UNCHECKED_CAST") - public fun capture(capturer: SaltifyCommandParamDef): T { - val result = argumentMap[capturer] - return (result as? ParameterParseResult.Success)?.value - ?: error("Parameter ${capturer.name} accessed without validation") - } - - /** - * 获取已解析的参数值。这是 [capture] 的简写形式。 - */ - @ContextParametersMigrationNeeded @Suppress("UNCHECKED_CAST") public val SaltifyCommandParamDef.value: T - get() = capture(this) - - /** - * 响应指令。 - */ - public suspend fun respond( - block: MutableList.() -> Unit - ): SendMessageOutput = event.respond(client, block) - - /** - * 响应指令,并在指定延迟后撤回消息。 - */ - @ContextParametersMigrationNeeded - public suspend inline fun respondWithRecall( - delay: Duration, - noinline block: MutableList.() -> Unit - ) { - val output = respond(block) - delay(delay) - when (val data = event.data) { - is IncomingMessage.Group -> client.recallGroupMessage(data.peerId, output.messageSeq) - else -> client.recallPrivateMessage(data.peerId, output.messageSeq) - } - } - - /** - * 获取由指令触发者发送的下一条消息事件。超时返回 null。 - */ - public suspend fun awaitNextMessage(timeout: Duration = 30.seconds): Event.MessageReceive? { - val messageFlow = client.eventFlow.filterIsInstance() - - return withTimeoutOrNull(timeout) { - messageFlow.first { nextEvent -> - when (val contextData = event.data) { - is IncomingMessage.Group -> { - nextEvent.data is IncomingMessage.Group && - (nextEvent.data as IncomingMessage.Group).group.groupId == contextData.group.groupId && - nextEvent.senderId == event.senderId - } - else -> nextEvent.senderId == event.senderId - } - } - } - } + get() = (argumentMap[this] as? ParameterParseResult.Success)?.value!! } /** diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyPluginContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyPluginContext.kt index 9bad0df..058bd90 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyPluginContext.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyPluginContext.kt @@ -2,36 +2,21 @@ package org.ntqqrev.saltify.dsl import io.ktor.util.logging.* import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import org.ntqqrev.milky.Event -import org.ntqqrev.milky.IncomingMessage -import org.ntqqrev.milky.OutgoingSegment -import org.ntqqrev.saltify.annotation.ContextParametersMigrationNeeded import org.ntqqrev.saltify.annotation.SaltifyDsl import org.ntqqrev.saltify.core.SaltifyApplication -import org.ntqqrev.saltify.core.recallGroupMessage -import org.ntqqrev.saltify.core.recallPrivateMessage -import org.ntqqrev.saltify.core.text -import org.ntqqrev.saltify.entity.SaltifyBotConfig -import org.ntqqrev.saltify.extension.command -import org.ntqqrev.saltify.extension.on -import org.ntqqrev.saltify.extension.regex -import org.ntqqrev.saltify.extension.respond -import org.ntqqrev.saltify.model.milky.SendMessageOutput -import kotlin.time.Duration +import org.ntqqrev.saltify.context.ApplicationExecutionContext @SaltifyDsl public class SaltifyPluginContext internal constructor( pluginName: String, - public val client: SaltifyApplication, + public override val client: SaltifyApplication, @PublishedApi internal val pluginScope: CoroutineScope -) : CoroutineScope by pluginScope { +) : CoroutineScope by pluginScope, ApplicationExecutionContext(client) { + public val logger: Logger = KtorSimpleLogger("Saltify/plugin:$pluginName") + internal val onStartHooks = mutableListOf Unit>() internal val onStopHooks = mutableListOf<() -> Unit>() - public val logger: Logger = KtorSimpleLogger("Saltify/plugin:$pluginName") - /** * 插件被加载,即 [SaltifyApplication.Companion.invoke] 后执行的逻辑。 */ @@ -45,64 +30,6 @@ public class SaltifyPluginContext internal constructor( public fun onStop(block: () -> Unit) { onStopHooks.add(block) } - - /** - * 注册一个事件监听器。 - */ - public inline fun on( - crossinline block: suspend SaltifyApplication.(event: T) -> Unit - ): Job = client.on(pluginScope, block) - - /** - * 注册一个消息正则匹配监听器。 - */ - public inline fun regex( - regex: String, - crossinline block: suspend SaltifyApplication.( - event: Event.MessageReceive, - matches: Sequence - ) -> Unit - ): Job = client.regex(regex, pluginScope, block) - - /** - * 注册一个指令。 - */ - public fun command( - name: String, - prefix: String = SaltifyBotConfig.commandPrefix, - block: SaltifyCommandContext.() -> Unit - ): Job = client.command(name, prefix, pluginScope, block) - - /** - * 响应事件。 - */ - public suspend fun Event.MessageReceive.respond( - block: MutableList.() -> Unit - ): SendMessageOutput = respond(client, block) - - /** - * 响应事件。这是用于返回纯文本的简写。 - */ - @ContextParametersMigrationNeeded - public suspend fun Event.MessageReceive.respond( - text: Any? - ): SendMessageOutput = respond { text(text.toString()) } - - /** - * 响应事件,并在指定延迟后撤回消息。 - */ - @ContextParametersMigrationNeeded - public suspend inline fun Event.MessageReceive.respondWithRecall( - delay: Duration, - noinline block: MutableList.() -> Unit - ) { - val output = respond(block) - delay(delay) - when (data) { - is IncomingMessage.Group -> client.recallGroupMessage(peerId, output.messageSeq) - else -> client.recallPrivateMessage(peerId, output.messageSeq) - } - } } /** diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementContext.kt new file mode 100644 index 0000000..87de7a6 --- /dev/null +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementContext.kt @@ -0,0 +1,7 @@ +package org.ntqqrev.saltify.entity + +import org.ntqqrev.saltify.dsl.CommandExecutionContext + +public class CommandRequirementContext( + public val context: CommandExecutionContext +) diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/SaltifyCommandRequirementContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/SaltifyCommandRequirementContext.kt deleted file mode 100644 index 0bbfedb..0000000 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/SaltifyCommandRequirementContext.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.ntqqrev.saltify.entity - -import org.ntqqrev.saltify.dsl.SaltifyCommandExecutionContext - -public class SaltifyCommandRequirementContext( - public val context: SaltifyCommandExecutionContext -) diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/ApplicationExtension.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/ApplicationExtension.kt index 661182d..6ad25ae 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/ApplicationExtension.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/ApplicationExtension.kt @@ -2,7 +2,7 @@ package org.ntqqrev.saltify.extension import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.launch import org.ntqqrev.milky.Event import org.ntqqrev.milky.IncomingMessage @@ -10,12 +10,16 @@ import org.ntqqrev.milky.IncomingSegment import org.ntqqrev.saltify.core.SaltifyApplication import org.ntqqrev.saltify.dsl.ParameterParseResult import org.ntqqrev.saltify.dsl.SaltifyCommandContext -import org.ntqqrev.saltify.dsl.SaltifyCommandExecutionContext +import org.ntqqrev.saltify.dsl.CommandExecutionContext import org.ntqqrev.saltify.dsl.SaltifyCommandParamDef import org.ntqqrev.saltify.entity.RegisteredCommandInfo import org.ntqqrev.saltify.entity.RegisteredSubCommandInfo import org.ntqqrev.saltify.entity.SaltifyBotConfig -import org.ntqqrev.saltify.entity.SaltifyCommandRequirementContext +import org.ntqqrev.saltify.entity.CommandRequirementContext +import org.ntqqrev.saltify.context.ApplicationExecutionContext +import org.ntqqrev.saltify.context.EventExecutionContext +import org.ntqqrev.saltify.context.client +import org.ntqqrev.saltify.context.event import org.ntqqrev.saltify.model.CommandError import org.ntqqrev.saltify.model.SaltifyComponentType import org.ntqqrev.saltify.util.coroutine.runCatchingToExceptionFlow @@ -26,15 +30,18 @@ import kotlin.time.Clock /** * 注册一个事件监听器。 */ -public inline fun SaltifyApplication.on( - scope: CoroutineScope = extensionScope, - crossinline block: suspend SaltifyApplication.(event: T) -> Unit +context(_: ApplicationExecutionContext) +public inline fun on( + scope: CoroutineScope = client.extensionScope, + crossinline block: suspend context(EventExecutionContext) () -> Unit ): Job = scope.launch { - eventFlow - .filter { it is T } + client.eventFlow + .filterIsInstance() .collect { launch { - runCatchingToExceptionFlow { block(it as T) } + runCatchingToExceptionFlow { + context(EventExecutionContext(it, client)) { block() } + } } } } @@ -42,19 +49,20 @@ public inline fun SaltifyApplication.on( /** * 注册一个消息正则匹配监听器。 */ -public inline fun SaltifyApplication.regex( +context(_: ApplicationExecutionContext) +public inline fun regex( regex: String, - scope: CoroutineScope = extensionScope, - crossinline block: suspend SaltifyApplication.(event: Event.MessageReceive, matches: Sequence) -> Unit + scope: CoroutineScope = client.extensionScope, + crossinline block: suspend context(EventExecutionContext) (matches: Sequence) -> Unit ): Job { val regex = Regex(regex) - return on(scope) { event -> + return on(scope) { val text = event.segments.filterIsInstance() .joinToString("") { it.text } val matches = regex.findAll(text) - if (matches.any()) block(event, matches) + if (matches.any()) block(matches) } } @@ -63,17 +71,18 @@ private val spaceRegex = Regex("\\s+") /** * 注册一个指令。 */ -public fun SaltifyApplication.command( +context(_: ApplicationExecutionContext) +public fun command( name: String, prefix: String = SaltifyBotConfig.commandPrefix, - scope: CoroutineScope = extensionScope, + scope: CoroutineScope = client.extensionScope, builder: SaltifyCommandContext.() -> Unit ): Job { val rootDsl = SaltifyCommandContext().apply(builder) val component = scope.coroutineContext.saltifyComponent val pluginName = if (component?.type == SaltifyComponentType.Plugin) component.name else null - commandRegistry.add( + client.commandRegistry.add( RegisteredCommandInfo( name = name, prefix = prefix, @@ -86,7 +95,7 @@ public fun SaltifyApplication.command( ) ) - return on(scope) { event -> + return on(scope) { val rawText = event.segments.filterIsInstance() .joinToString("") { it.text } .trim() @@ -94,7 +103,7 @@ public fun SaltifyApplication.command( val tokens = if (rawText.isEmpty()) emptyList() else rawText.split(spaceRegex) if (tokens.isEmpty() || tokens[0] != "$prefix$name") return@on - executeCommand(rootDsl, tokens.drop(1), this, event, name) + executeCommand(rootDsl, tokens.drop(1), client, event, name) } } @@ -106,10 +115,10 @@ private suspend fun executeCommand( name: String ) { val argumentMap = mutableMapOf, ParameterParseResult>() - val execution = SaltifyCommandExecutionContext(client, event, name, argumentMap) + val execution = CommandExecutionContext(event, client, name, argumentMap) dsl.requirementBlock?.let { block -> - val requirement = SaltifyCommandRequirementContext(execution).block() + val requirement = CommandRequirementContext(execution).block() if (!requirement.satisfies()) return } diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/CommandContextExtension.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/CommandContextExtension.kt index f6bb1c8..5b41d2f 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/CommandContextExtension.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/CommandContextExtension.kt @@ -1,22 +1,13 @@ package org.ntqqrev.saltify.extension -import org.ntqqrev.saltify.core.text import org.ntqqrev.saltify.dsl.SaltifyCommandContext -import org.ntqqrev.saltify.dsl.SaltifyCommandExecutionContext +import org.ntqqrev.saltify.dsl.CommandExecutionContext import org.ntqqrev.saltify.dsl.SaltifyCommandParamDef -import org.ntqqrev.saltify.model.milky.SendMessageOutput /** - * 定义一个指令参数。请搭配 [SaltifyCommandExecutionContext.capture] 使用。 + * 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。 */ public inline fun SaltifyCommandContext.parameter( name: String, description: String = "" ): SaltifyCommandParamDef = parameter(T::class, name, description) - -/** - * 响应事件。这是用于返回纯文本的简写。 - */ -public suspend inline fun SaltifyCommandExecutionContext.respond( - text: Any? -): SendMessageOutput = respond { text(text.toString()) } diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/EventExtension.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/EventExtension.kt index c2831c8..ae72eb9 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/EventExtension.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/EventExtension.kt @@ -1,36 +1,89 @@ package org.ntqqrev.saltify.extension +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withTimeoutOrNull import org.ntqqrev.milky.Event import org.ntqqrev.milky.IncomingMessage import org.ntqqrev.milky.OutgoingSegment -import org.ntqqrev.saltify.core.SaltifyApplication +import org.ntqqrev.saltify.core.recallGroupMessage +import org.ntqqrev.saltify.core.recallPrivateMessage import org.ntqqrev.saltify.core.sendGroupMessage import org.ntqqrev.saltify.core.sendPrivateMessage import org.ntqqrev.saltify.core.text -import org.ntqqrev.saltify.dsl.SaltifyPluginContext +import org.ntqqrev.saltify.context.EventExecutionContext import org.ntqqrev.saltify.model.milky.SendMessageOutput +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds /** - * 响应事件。鉴于 Context Parameter 尚未完善,这里需要手动传 client。建议使用 [SaltifyPluginContext.respond]。 + * 响应事件。 */ -public suspend fun Event.MessageReceive.respond( - client: SaltifyApplication, +context(ctx: EventExecutionContext) +public suspend fun respond( block: MutableList.() -> Unit -): SendMessageOutput = when (data) { +): SendMessageOutput = when (ctx.event.data) { is IncomingMessage.Group -> { - val output = client.sendGroupMessage(peerId, block) + val output = ctx.client.sendGroupMessage(ctx.event.peerId, block) SendMessageOutput(output.messageSeq, output.time) } else -> { - val output = client.sendPrivateMessage(peerId, block) + val output = ctx.client.sendPrivateMessage(ctx.event.peerId, block) SendMessageOutput(output.messageSeq, output.time) } } /** - * 响应事件。这是用于返回纯文本的简写。鉴于 Context Parameter 尚未完善,这里需要手动传 client。 + * 响应事件。这是用于返回纯文本的简写形式。 */ -public suspend inline fun Event.MessageReceive.respond( - client: SaltifyApplication, +context(_: EventExecutionContext) +public suspend inline fun respond( text: Any? -): SendMessageOutput = respond(client) { text(text.toString()) } +): SendMessageOutput = respond { text(text.toString()) } + +/** + * 响应事件,并在指定延迟后撤回消息。 + */ +context(ctx: EventExecutionContext) +public suspend inline fun respondWithRecall( + delay: Duration, + noinline block: MutableList.() -> Unit +) { + val output = respond(block) + delay(delay) + when (val data = ctx.event.data) { + is IncomingMessage.Group -> ctx.client.recallGroupMessage(data.peerId, output.messageSeq) + else -> ctx.client.recallPrivateMessage(data.peerId, output.messageSeq) + } +} + +/** + * 响应事件,并在指定延迟后撤回消息。这是用于返回纯文本的简写形式。 + */ +context(_: EventExecutionContext) +public suspend inline fun respondWithRecall( + delay: Duration, + text: Any? +): Unit = respondWithRecall(delay) { text(text.toString()) } + +/** + * 获取由事件触发者发送的下一条消息事件。超时返回 null。 + */ +context(ctx: EventExecutionContext) +public suspend fun awaitNextMessage(timeout: Duration = 30.seconds): Event.MessageReceive? { + val messageFlow = ctx.client.eventFlow.filterIsInstance() + + return withTimeoutOrNull(timeout) { + messageFlow.first { nextEvent -> + when (val contextData = ctx.event.data) { + is IncomingMessage.Group -> { + nextEvent.data is IncomingMessage.Group && + (nextEvent.data as IncomingMessage.Group).group.groupId == contextData.group.groupId && + nextEvent.senderId == ctx.event.senderId + } + else -> nextEvent.senderId == ctx.event.senderId + } + } + } +} diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/PluginContextExtension.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/PluginContextExtension.kt deleted file mode 100644 index 1ef17a9..0000000 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/PluginContextExtension.kt +++ /dev/null @@ -1 +0,0 @@ -package org.ntqqrev.saltify.extension diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/RequirementExtension.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/RequirementExtension.kt index 28d250d..359c9b4 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/RequirementExtension.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/RequirementExtension.kt @@ -2,41 +2,41 @@ package org.ntqqrev.saltify.extension import org.ntqqrev.milky.IncomingMessage import org.ntqqrev.milky.IncomingSegment -import org.ntqqrev.saltify.entity.SaltifyCommandRequirementContext +import org.ntqqrev.saltify.entity.CommandRequirementContext import org.ntqqrev.saltify.model.CommandRequirement import org.ntqqrev.saltify.model.PermissionLevel -public fun SaltifyCommandRequirementContext.user(vararg targetId: Long): CommandRequirement = +public fun CommandRequirementContext.user(vararg targetId: Long): CommandRequirement = CommandRequirement { context.event.senderId in targetId } -public fun SaltifyCommandRequirementContext.group(vararg targetId: Long): CommandRequirement = +public fun CommandRequirementContext.group(vararg targetId: Long): CommandRequirement = CommandRequirement { ((context.event.data as? IncomingMessage.Group)?.group?.groupId ?: return@CommandRequirement false) in targetId } -public fun SaltifyCommandRequirementContext.perm(targetLevel: PermissionLevel): CommandRequirement = +public fun CommandRequirementContext.perm(targetLevel: PermissionLevel): CommandRequirement = CommandRequirement { permissionLevelOf(context.event.senderId) >= targetLevel } -public val SaltifyCommandRequirementContext.isGroupAdmin: CommandRequirement +public val CommandRequirementContext.isGroupAdmin: CommandRequirement get() = CommandRequirement { val data = context.event.data as? IncomingMessage.Group data?.groupMember?.role == "admin" } -public val SaltifyCommandRequirementContext.isGroupOwner: CommandRequirement +public val CommandRequirementContext.isGroupOwner: CommandRequirement get() = CommandRequirement { val data = context.event.data as? IncomingMessage.Group data?.groupMember?.role == "owner" } -public val SaltifyCommandRequirementContext.isGroupAdminOrOwner: CommandRequirement +public val CommandRequirementContext.isGroupAdminOrOwner: CommandRequirement get() = isGroupAdmin or isGroupOwner -public val SaltifyCommandRequirementContext.isMention: CommandRequirement +public val CommandRequirementContext.isMention: CommandRequirement get() = CommandRequirement { val segment = context.event.segments.filterIsInstance() segment.isNotEmpty() && segment.any { it.userId == context.event.senderId } diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/util/coroutine/RunCatchingToExceptionFlow.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/util/coroutine/RunCatchingToExceptionFlow.kt index 3ea5020..f906741 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/util/coroutine/RunCatchingToExceptionFlow.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/util/coroutine/RunCatchingToExceptionFlow.kt @@ -2,13 +2,15 @@ package org.ntqqrev.saltify.util.coroutine import kotlinx.coroutines.CancellationException import kotlinx.coroutines.currentCoroutineContext -import org.ntqqrev.saltify.core.SaltifyApplication +import org.ntqqrev.saltify.context.ApplicationExecutionContext +import org.ntqqrev.saltify.context.client import kotlin.coroutines.CoroutineContext /** * 执行代码块,并将可能的异常发送到全局异常流。 */ -public suspend inline fun SaltifyApplication.runCatchingToExceptionFlow( +context(_: ApplicationExecutionContext) +public suspend inline fun runCatchingToExceptionFlow( context: CoroutineContext? = null, crossinline block: suspend () -> Unit ) { @@ -16,6 +18,6 @@ public suspend inline fun SaltifyApplication.runCatchingToExceptionFlow( block() }.onFailure { throwable -> if (throwable is CancellationException) throw throwable - exceptionHandlerProvider.exceptionFlow.tryEmit((context ?: currentCoroutineContext()) to throwable) + client.exceptionHandlerProvider.exceptionFlow.tryEmit((context ?: currentCoroutineContext()) to throwable) } } diff --git a/saltify-core/src/jvmTest/kotlin/org/ntqqrev/saltify/PluginTest.kt b/saltify-core/src/jvmTest/kotlin/org/ntqqrev/saltify/PluginTest.kt index a85650b..06a5d60 100644 --- a/saltify-core/src/jvmTest/kotlin/org/ntqqrev/saltify/PluginTest.kt +++ b/saltify-core/src/jvmTest/kotlin/org/ntqqrev/saltify/PluginTest.kt @@ -8,11 +8,15 @@ import org.ntqqrev.saltify.builtin.plugin.defaultLogging import org.ntqqrev.saltify.core.SaltifyApplication import org.ntqqrev.saltify.core.getLoginInfo import org.ntqqrev.saltify.dsl.SaltifyPlugin +import org.ntqqrev.saltify.extension.awaitNextMessage +import org.ntqqrev.saltify.extension.command import org.ntqqrev.saltify.extension.parameter import org.ntqqrev.saltify.extension.plainText +import org.ntqqrev.saltify.extension.regex import org.ntqqrev.saltify.extension.respond import org.ntqqrev.saltify.model.EventConnectionType import kotlin.test.Test +import kotlin.time.Duration.Companion.milliseconds class PluginTest { @Test @@ -40,7 +44,7 @@ class PluginTest { }.start() client.connectEvent() - delay(60000L) + delay(60000.milliseconds) client.disconnectEvent() client.close() } @@ -59,8 +63,8 @@ class PluginTest { } // regex test - regex("""BV1\w{9}""") { event, matches -> - event.respond(matches.joinToString { it.value }) + regex("""BV1\w{9}""") { matches -> + respond(matches.joinToString { it.value }) } // config test diff --git a/saltify-docs/content/docs-core/command.md b/saltify-docs/content/docs-core/command.md index 550b302..d6c6369 100644 --- a/saltify-docs/content/docs-core/command.md +++ b/saltify-docs/content/docs-core/command.md @@ -8,6 +8,8 @@ Saltify 提供了一套类型安全的指令构建 DSL,支持参数解析、 ```kotlin client.command("order", prefix = "/") { + description = "创建一个订单" + // 定义一个 Int 类型的参数 val id = parameter("id") // 定义一个贪婪字符串参数,即将之后所有内容视为一个参数 From 136041f4679a431834ee79e1ac23c7c08c5c38cc Mon Sep 17 00:00:00 2001 From: Aliorpse Date: Fri, 3 Apr 2026 17:51:33 +0800 Subject: [PATCH 2/3] refactor!: rename some unreasonable class names --- .../saltify/builtin/plugin/CommandHelp.kt | 8 ++--- .../saltify/builtin/plugin/DefaultLogging.kt | 2 +- .../saltify/context/EventExecutionContext.kt | 15 -------- .../saltify/core/SaltifyApplication.kt | 4 +-- .../saltify/dsl/SaltifyCommandContext.kt | 10 +++--- .../saltify/dsl/SaltifyPluginContext.kt | 4 +-- ...tContext.kt => CommandRequirementMatch.kt} | 2 +- ...redCommandInfo.kt => RegisteredCommand.kt} | 8 ++--- .../env/ApplicationEnvironment.kt} | 6 ++-- .../saltify/entity/env/EventEnvironment.kt | 15 ++++++++ .../saltify/extension/ApplicationExtension.kt | 34 +++++++++---------- .../saltify/extension/EventExtension.kt | 12 +++---- .../saltify/extension/RequirementExtension.kt | 16 ++++----- .../coroutine/RunCatchingToExceptionFlow.kt | 6 ++-- 14 files changed, 71 insertions(+), 71 deletions(-) delete mode 100644 saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/EventExecutionContext.kt rename saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/{CommandRequirementContext.kt => CommandRequirementMatch.kt} (78%) rename saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/{RegisteredCommandInfo.kt => RegisteredCommand.kt} (67%) rename saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/{context/ApplicationExecutionContext.kt => entity/env/ApplicationEnvironment.kt} (56%) create mode 100644 saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/env/EventEnvironment.kt diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/CommandHelp.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/CommandHelp.kt index 245486c..40bb2a8 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/CommandHelp.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/CommandHelp.kt @@ -4,8 +4,8 @@ import org.ntqqrev.saltify.core.forward import org.ntqqrev.saltify.core.node import org.ntqqrev.saltify.core.text import org.ntqqrev.saltify.dsl.SaltifyPlugin -import org.ntqqrev.saltify.entity.RegisteredCommandInfo -import org.ntqqrev.saltify.entity.RegisteredSubCommandInfo +import org.ntqqrev.saltify.entity.RegisteredCommand +import org.ntqqrev.saltify.entity.RegisteredSubCommand import org.ntqqrev.saltify.extension.command import org.ntqqrev.saltify.extension.respond @@ -50,7 +50,7 @@ public val commandHelp: SaltifyPlugin = SaltifyPlugin("command-help") { private fun buildCommandGroupText( pluginName: String?, - commands: List + commands: List ): String = buildString { if (pluginName != null) { appendLine("插件 $pluginName: ") @@ -82,7 +82,7 @@ private fun buildCommandGroupText( }.trimEnd() private fun StringBuilder.appendSubCommand( - sub: RegisteredSubCommandInfo, + sub: RegisteredSubCommand, parentPath: String, depth: Int ) { diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/DefaultLogging.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/DefaultLogging.kt index f1dc894..db31990 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/DefaultLogging.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/builtin/plugin/DefaultLogging.kt @@ -5,7 +5,7 @@ import kotlinx.coroutines.launch import org.ntqqrev.milky.Event import org.ntqqrev.milky.IncomingMessage import org.ntqqrev.saltify.dsl.SaltifyPlugin -import org.ntqqrev.saltify.context.event +import org.ntqqrev.saltify.entity.env.event import org.ntqqrev.saltify.extension.on import org.ntqqrev.saltify.extension.plainText import org.ntqqrev.saltify.model.EventConnectionState diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/EventExecutionContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/EventExecutionContext.kt deleted file mode 100644 index afb0b7c..0000000 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/EventExecutionContext.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.ntqqrev.saltify.context - -import org.ntqqrev.milky.Event -import org.ntqqrev.saltify.core.SaltifyApplication - -public open class EventExecutionContext( - public open val event: T, - public open val client: SaltifyApplication -) - -context(ctx: EventExecutionContext) -public val event: T get() = ctx.event - -context(ctx: EventExecutionContext) -public val client: SaltifyApplication get() = ctx.client diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/core/SaltifyApplication.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/core/SaltifyApplication.kt index 3d35d42..d63e0ee 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/core/SaltifyApplication.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/core/SaltifyApplication.kt @@ -19,7 +19,7 @@ import org.ntqqrev.saltify.annotation.WithApiExtension import org.ntqqrev.saltify.dsl.SaltifyPluginContext import org.ntqqrev.saltify.dsl.config.SaltifyApplicationConfig import org.ntqqrev.saltify.entity.InstalledPlugin -import org.ntqqrev.saltify.entity.RegisteredCommandInfo +import org.ntqqrev.saltify.entity.RegisteredCommand import org.ntqqrev.saltify.exception.ApiCallException import org.ntqqrev.saltify.model.EventConnectionState import org.ntqqrev.saltify.model.EventConnectionType @@ -100,7 +100,7 @@ public sealed class SaltifyApplication(internal val config: SaltifyApplicationCo private val loadedPlugins = mutableListOf() - internal val commandRegistry: MutableList = mutableListOf() + internal val commandRegistry: MutableList = mutableListOf() @PublishedApi internal val httpClient: HttpClient = HttpClient { diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyCommandContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyCommandContext.kt index 014d090..0a3d79a 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyCommandContext.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyCommandContext.kt @@ -4,8 +4,8 @@ import io.ktor.util.logging.* import org.ntqqrev.milky.Event import org.ntqqrev.saltify.annotation.SaltifyDsl import org.ntqqrev.saltify.core.SaltifyApplication -import org.ntqqrev.saltify.entity.CommandRequirementContext -import org.ntqqrev.saltify.context.EventExecutionContext +import org.ntqqrev.saltify.entity.CommandRequirementMatch +import org.ntqqrev.saltify.entity.env.EventEnvironment import org.ntqqrev.saltify.model.CommandError import org.ntqqrev.saltify.model.CommandRequirement import kotlin.reflect.KClass @@ -20,7 +20,7 @@ public class SaltifyCommandContext internal constructor() { internal var groupExecutionBlock: (suspend CommandExecutionContext.() -> Unit)? = null internal var privateExecutionBlock: (suspend CommandExecutionContext.() -> Unit)? = null internal var failureBlock: (suspend CommandExecutionContext.(CommandError) -> Unit)? = null - internal var requirementBlock: (CommandRequirementContext.() -> CommandRequirement)? = null + internal var requirementBlock: (CommandRequirementMatch.() -> CommandRequirement)? = null /** * 注册一个子指令。 @@ -32,7 +32,7 @@ public class SaltifyCommandContext internal constructor() { /** * 定义指令执行要求。若不满足,静默返回。 */ - public fun require(block: CommandRequirementContext.() -> CommandRequirement) { + public fun require(block: CommandRequirementMatch.() -> CommandRequirement) { this.requirementBlock = block } @@ -89,7 +89,7 @@ public class CommandExecutionContext( public override val client: SaltifyApplication, commandName: String, private val argumentMap: Map, Any?> -) : EventExecutionContext(event, client) { +) : EventEnvironment(event, client) { public val logger: Logger = KtorSimpleLogger("Saltify/cmd:$commandName") @Suppress("UNCHECKED_CAST") diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyPluginContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyPluginContext.kt index 058bd90..3f1f369 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyPluginContext.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/SaltifyPluginContext.kt @@ -4,14 +4,14 @@ import io.ktor.util.logging.* import kotlinx.coroutines.CoroutineScope import org.ntqqrev.saltify.annotation.SaltifyDsl import org.ntqqrev.saltify.core.SaltifyApplication -import org.ntqqrev.saltify.context.ApplicationExecutionContext +import org.ntqqrev.saltify.entity.env.ApplicationEnvironment @SaltifyDsl public class SaltifyPluginContext internal constructor( pluginName: String, public override val client: SaltifyApplication, @PublishedApi internal val pluginScope: CoroutineScope -) : CoroutineScope by pluginScope, ApplicationExecutionContext(client) { +) : CoroutineScope by pluginScope, ApplicationEnvironment(client) { public val logger: Logger = KtorSimpleLogger("Saltify/plugin:$pluginName") internal val onStartHooks = mutableListOf Unit>() diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementMatch.kt similarity index 78% rename from saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementContext.kt rename to saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementMatch.kt index 87de7a6..497ca3e 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementContext.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/CommandRequirementMatch.kt @@ -2,6 +2,6 @@ package org.ntqqrev.saltify.entity import org.ntqqrev.saltify.dsl.CommandExecutionContext -public class CommandRequirementContext( +public class CommandRequirementMatch( public val context: CommandExecutionContext ) diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/RegisteredCommandInfo.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/RegisteredCommand.kt similarity index 67% rename from saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/RegisteredCommandInfo.kt rename to saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/RegisteredCommand.kt index ac59b15..1667092 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/RegisteredCommandInfo.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/RegisteredCommand.kt @@ -5,21 +5,21 @@ import org.ntqqrev.saltify.dsl.SaltifyCommandParamDef /** * 子指令的注册信息 */ -public data class RegisteredSubCommandInfo( +public data class RegisteredSubCommand( val name: String, val description: String, val parameters: List>, - val subCommands: List = emptyList() + val subCommands: List = emptyList() ) /** * 已注册指令的信息 */ -public data class RegisteredCommandInfo( +public data class RegisteredCommand( val name: String, val prefix: String, val description: String, val parameters: List>, - val subCommands: List, + val subCommands: List, val pluginName: String? ) diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/ApplicationExecutionContext.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/env/ApplicationEnvironment.kt similarity index 56% rename from saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/ApplicationExecutionContext.kt rename to saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/env/ApplicationEnvironment.kt index afc3a7a..19a438b 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/context/ApplicationExecutionContext.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/env/ApplicationEnvironment.kt @@ -1,10 +1,10 @@ -package org.ntqqrev.saltify.context +package org.ntqqrev.saltify.entity.env import org.ntqqrev.saltify.core.SaltifyApplication -public open class ApplicationExecutionContext( +public open class ApplicationEnvironment( public open val client: SaltifyApplication ) -context(ctx: ApplicationExecutionContext) +context(ctx: ApplicationEnvironment) public val client: SaltifyApplication get() = ctx.client diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/env/EventEnvironment.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/env/EventEnvironment.kt new file mode 100644 index 0000000..bdf7068 --- /dev/null +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/entity/env/EventEnvironment.kt @@ -0,0 +1,15 @@ +package org.ntqqrev.saltify.entity.env + +import org.ntqqrev.milky.Event +import org.ntqqrev.saltify.core.SaltifyApplication + +public open class EventEnvironment( + public open val event: T, + public override val client: SaltifyApplication +) : ApplicationEnvironment(client) + +context(ctx: EventEnvironment) +public val event: T get() = ctx.event + +context(ctx: EventEnvironment) +public val client: SaltifyApplication get() = ctx.client diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/ApplicationExtension.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/ApplicationExtension.kt index 6ad25ae..c9e7620 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/ApplicationExtension.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/ApplicationExtension.kt @@ -12,14 +12,14 @@ import org.ntqqrev.saltify.dsl.ParameterParseResult import org.ntqqrev.saltify.dsl.SaltifyCommandContext import org.ntqqrev.saltify.dsl.CommandExecutionContext import org.ntqqrev.saltify.dsl.SaltifyCommandParamDef -import org.ntqqrev.saltify.entity.RegisteredCommandInfo -import org.ntqqrev.saltify.entity.RegisteredSubCommandInfo +import org.ntqqrev.saltify.entity.RegisteredCommand +import org.ntqqrev.saltify.entity.RegisteredSubCommand import org.ntqqrev.saltify.entity.SaltifyBotConfig -import org.ntqqrev.saltify.entity.CommandRequirementContext -import org.ntqqrev.saltify.context.ApplicationExecutionContext -import org.ntqqrev.saltify.context.EventExecutionContext -import org.ntqqrev.saltify.context.client -import org.ntqqrev.saltify.context.event +import org.ntqqrev.saltify.entity.CommandRequirementMatch +import org.ntqqrev.saltify.entity.env.ApplicationEnvironment +import org.ntqqrev.saltify.entity.env.EventEnvironment +import org.ntqqrev.saltify.entity.env.client +import org.ntqqrev.saltify.entity.env.event import org.ntqqrev.saltify.model.CommandError import org.ntqqrev.saltify.model.SaltifyComponentType import org.ntqqrev.saltify.util.coroutine.runCatchingToExceptionFlow @@ -30,17 +30,17 @@ import kotlin.time.Clock /** * 注册一个事件监听器。 */ -context(_: ApplicationExecutionContext) +context(_: ApplicationEnvironment) public inline fun on( scope: CoroutineScope = client.extensionScope, - crossinline block: suspend context(EventExecutionContext) () -> Unit + crossinline block: suspend context(EventEnvironment) () -> Unit ): Job = scope.launch { client.eventFlow .filterIsInstance() .collect { launch { runCatchingToExceptionFlow { - context(EventExecutionContext(it, client)) { block() } + context(EventEnvironment(it, client)) { block() } } } } @@ -49,11 +49,11 @@ public inline fun on( /** * 注册一个消息正则匹配监听器。 */ -context(_: ApplicationExecutionContext) +context(_: ApplicationEnvironment) public inline fun regex( regex: String, scope: CoroutineScope = client.extensionScope, - crossinline block: suspend context(EventExecutionContext) (matches: Sequence) -> Unit + crossinline block: suspend context(EventEnvironment) (matches: Sequence) -> Unit ): Job { val regex = Regex(regex) @@ -71,7 +71,7 @@ private val spaceRegex = Regex("\\s+") /** * 注册一个指令。 */ -context(_: ApplicationExecutionContext) +context(_: ApplicationEnvironment) public fun command( name: String, prefix: String = SaltifyBotConfig.commandPrefix, @@ -83,7 +83,7 @@ public fun command( val component = scope.coroutineContext.saltifyComponent val pluginName = if (component?.type == SaltifyComponentType.Plugin) component.name else null client.commandRegistry.add( - RegisteredCommandInfo( + RegisteredCommand( name = name, prefix = prefix, description = rootDsl.description, @@ -118,7 +118,7 @@ private suspend fun executeCommand( val execution = CommandExecutionContext(event, client, name, argumentMap) dsl.requirementBlock?.let { block -> - val requirement = CommandRequirementContext(execution).block() + val requirement = CommandRequirementMatch(execution).block() if (!requirement.satisfies()) return } @@ -172,8 +172,8 @@ private suspend fun executeCommand( execution.logger.info("seq=${event.messageSeq} 处理完成, 用时 ${Clock.System.now() - startInstant}") } -private fun SaltifyCommandContext.toSubCommandInfo(name: String): RegisteredSubCommandInfo = - RegisteredSubCommandInfo( +private fun SaltifyCommandContext.toSubCommandInfo(name: String): RegisteredSubCommand = + RegisteredSubCommand( name = name, description = description, parameters = parameters.toList(), diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/EventExtension.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/EventExtension.kt index ae72eb9..f131f6d 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/EventExtension.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/EventExtension.kt @@ -12,7 +12,7 @@ import org.ntqqrev.saltify.core.recallPrivateMessage import org.ntqqrev.saltify.core.sendGroupMessage import org.ntqqrev.saltify.core.sendPrivateMessage import org.ntqqrev.saltify.core.text -import org.ntqqrev.saltify.context.EventExecutionContext +import org.ntqqrev.saltify.entity.env.EventEnvironment import org.ntqqrev.saltify.model.milky.SendMessageOutput import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -20,7 +20,7 @@ import kotlin.time.Duration.Companion.seconds /** * 响应事件。 */ -context(ctx: EventExecutionContext) +context(ctx: EventEnvironment) public suspend fun respond( block: MutableList.() -> Unit ): SendMessageOutput = when (ctx.event.data) { @@ -37,7 +37,7 @@ public suspend fun respond( /** * 响应事件。这是用于返回纯文本的简写形式。 */ -context(_: EventExecutionContext) +context(_: EventEnvironment) public suspend inline fun respond( text: Any? ): SendMessageOutput = respond { text(text.toString()) } @@ -45,7 +45,7 @@ public suspend inline fun respond( /** * 响应事件,并在指定延迟后撤回消息。 */ -context(ctx: EventExecutionContext) +context(ctx: EventEnvironment) public suspend inline fun respondWithRecall( delay: Duration, noinline block: MutableList.() -> Unit @@ -61,7 +61,7 @@ public suspend inline fun respondWithRecall( /** * 响应事件,并在指定延迟后撤回消息。这是用于返回纯文本的简写形式。 */ -context(_: EventExecutionContext) +context(_: EventEnvironment) public suspend inline fun respondWithRecall( delay: Duration, text: Any? @@ -70,7 +70,7 @@ public suspend inline fun respondWithRecall( /** * 获取由事件触发者发送的下一条消息事件。超时返回 null。 */ -context(ctx: EventExecutionContext) +context(ctx: EventEnvironment) public suspend fun awaitNextMessage(timeout: Duration = 30.seconds): Event.MessageReceive? { val messageFlow = ctx.client.eventFlow.filterIsInstance() diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/RequirementExtension.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/RequirementExtension.kt index 359c9b4..9af12ba 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/RequirementExtension.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/RequirementExtension.kt @@ -2,41 +2,41 @@ package org.ntqqrev.saltify.extension import org.ntqqrev.milky.IncomingMessage import org.ntqqrev.milky.IncomingSegment -import org.ntqqrev.saltify.entity.CommandRequirementContext +import org.ntqqrev.saltify.entity.CommandRequirementMatch import org.ntqqrev.saltify.model.CommandRequirement import org.ntqqrev.saltify.model.PermissionLevel -public fun CommandRequirementContext.user(vararg targetId: Long): CommandRequirement = +public fun CommandRequirementMatch.user(vararg targetId: Long): CommandRequirement = CommandRequirement { context.event.senderId in targetId } -public fun CommandRequirementContext.group(vararg targetId: Long): CommandRequirement = +public fun CommandRequirementMatch.group(vararg targetId: Long): CommandRequirement = CommandRequirement { ((context.event.data as? IncomingMessage.Group)?.group?.groupId ?: return@CommandRequirement false) in targetId } -public fun CommandRequirementContext.perm(targetLevel: PermissionLevel): CommandRequirement = +public fun CommandRequirementMatch.perm(targetLevel: PermissionLevel): CommandRequirement = CommandRequirement { permissionLevelOf(context.event.senderId) >= targetLevel } -public val CommandRequirementContext.isGroupAdmin: CommandRequirement +public val CommandRequirementMatch.isGroupAdmin: CommandRequirement get() = CommandRequirement { val data = context.event.data as? IncomingMessage.Group data?.groupMember?.role == "admin" } -public val CommandRequirementContext.isGroupOwner: CommandRequirement +public val CommandRequirementMatch.isGroupOwner: CommandRequirement get() = CommandRequirement { val data = context.event.data as? IncomingMessage.Group data?.groupMember?.role == "owner" } -public val CommandRequirementContext.isGroupAdminOrOwner: CommandRequirement +public val CommandRequirementMatch.isGroupAdminOrOwner: CommandRequirement get() = isGroupAdmin or isGroupOwner -public val CommandRequirementContext.isMention: CommandRequirement +public val CommandRequirementMatch.isMention: CommandRequirement get() = CommandRequirement { val segment = context.event.segments.filterIsInstance() segment.isNotEmpty() && segment.any { it.userId == context.event.senderId } diff --git a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/util/coroutine/RunCatchingToExceptionFlow.kt b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/util/coroutine/RunCatchingToExceptionFlow.kt index f906741..2c80f7c 100644 --- a/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/util/coroutine/RunCatchingToExceptionFlow.kt +++ b/saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/util/coroutine/RunCatchingToExceptionFlow.kt @@ -2,14 +2,14 @@ package org.ntqqrev.saltify.util.coroutine import kotlinx.coroutines.CancellationException import kotlinx.coroutines.currentCoroutineContext -import org.ntqqrev.saltify.context.ApplicationExecutionContext -import org.ntqqrev.saltify.context.client +import org.ntqqrev.saltify.entity.env.ApplicationEnvironment +import org.ntqqrev.saltify.entity.env.client import kotlin.coroutines.CoroutineContext /** * 执行代码块,并将可能的异常发送到全局异常流。 */ -context(_: ApplicationExecutionContext) +context(_: ApplicationEnvironment) public suspend inline fun runCatchingToExceptionFlow( context: CoroutineContext? = null, crossinline block: suspend () -> Unit From 1679dfdc37b9c30509c69b117743bedb48a0660f Mon Sep 17 00:00:00 2001 From: Aliorpse Date: Sun, 5 Apr 2026 12:26:10 +0800 Subject: [PATCH 3/3] build: fix ksp generated codes not being analysed in linux --- saltify-core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saltify-core/build.gradle.kts b/saltify-core/build.gradle.kts index 5d02fa2..17eab20 100644 --- a/saltify-core/build.gradle.kts +++ b/saltify-core/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { kotlin { sourceSets { commonMain { - kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") + kotlin.srcDir("./build/generated/ksp/metadata/commonMain/kotlin") dependencies { implementation(libs.ktor.client.core) implementation(libs.ktor.client.content.negotiation)