mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-23 13:50:12 +08:00
Introduce CommandCallInterceptor
This commit is contained in:
parent
450e66ccfd
commit
136b0c11d8
@ -14,6 +14,7 @@ package net.mamoe.mirai.console.command
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.command.resolve.InterceptedReason
|
||||
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.contracts.contract
|
||||
@ -97,6 +98,21 @@ public sealed class CommandExecuteResult {
|
||||
public override val resolvedCall: ResolvedCommandCall? get() = null
|
||||
}
|
||||
|
||||
/** 没有匹配的指令 */
|
||||
public class Intercepted(
|
||||
/** 解析的 [CommandCall] (如果匹配到) */
|
||||
public override val call: CommandCall?,
|
||||
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
|
||||
public override val resolvedCall: ResolvedCommandCall?,
|
||||
/** 尝试执行的指令 (如果匹配到) */
|
||||
public override val command: Command?,
|
||||
/** 拦截原因 */
|
||||
public val reason: InterceptedReason,
|
||||
) : Failure() {
|
||||
/** 指令执行时发生的错误, 总是 `null` */
|
||||
public override val exception: Nothing? get() = null
|
||||
}
|
||||
|
||||
/** 权限不足 */
|
||||
public class PermissionDenied(
|
||||
/** 尝试执行的指令 */
|
||||
|
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.console.command.resolve
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser
|
||||
import net.mamoe.mirai.console.extensions.CommandCallInterceptorProvider
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.internal.util.UNREACHABLE_CLAUSE
|
||||
import net.mamoe.mirai.console.util.safeCast
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import org.jetbrains.annotations.Contract
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
|
||||
/**
|
||||
* 指令解析和调用拦截器. 用于在指令各解析阶段拦截或转换调用.
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCallInterceptor {
|
||||
/**
|
||||
* 在指令[语法解析][CommandCallParser]前调用.
|
||||
*
|
||||
* @return `null` 表示未处理
|
||||
*/
|
||||
public fun interceptBeforeCall(
|
||||
message: Message,
|
||||
caller: CommandSender,
|
||||
): InterceptResult<Message>? = null
|
||||
|
||||
/**
|
||||
* 在指令[语法解析][CommandCallParser]后调用.
|
||||
*
|
||||
* @return `null` 表示未处理
|
||||
*/
|
||||
public fun interceptCall(
|
||||
call: CommandCall,
|
||||
): InterceptResult<CommandCall>? = null
|
||||
|
||||
/**
|
||||
* 在指令[调用解析][CommandCallResolver]后调用.
|
||||
*
|
||||
* @return `null` 表示未处理
|
||||
*/
|
||||
public fun interceptResolvedCall(
|
||||
call: ResolvedCommandCall,
|
||||
): InterceptResult<ResolvedCommandCall>? = null
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall].
|
||||
* 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [Message]
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun Message.intercepted(caller: CommandSender): InterceptResult<Message> {
|
||||
GlobalComponentStorage.run {
|
||||
return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext ->
|
||||
val intercepted = ext.instance.interceptBeforeCall(acc, caller)
|
||||
intercepted?.fold(
|
||||
onIntercepted = { return intercepted },
|
||||
otherwise = { it }
|
||||
) ?: acc
|
||||
}.let { InterceptResult(it) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall].
|
||||
* 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [CommandCall]
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun CommandCall.intercepted(): InterceptResult<CommandCall> {
|
||||
GlobalComponentStorage.run {
|
||||
return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext ->
|
||||
val intercepted = ext.instance.interceptCall(acc)
|
||||
intercepted?.fold(
|
||||
onIntercepted = { return intercepted },
|
||||
otherwise = { it }
|
||||
) ?: acc
|
||||
}.let { InterceptResult(it) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall].
|
||||
* 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [ResolvedCommandCall]
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun ResolvedCommandCall.intercepted(): InterceptResult<ResolvedCommandCall> {
|
||||
GlobalComponentStorage.run {
|
||||
return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext ->
|
||||
val intercepted = ext.instance.interceptResolvedCall(acc)
|
||||
intercepted?.fold(
|
||||
onIntercepted = { return intercepted },
|
||||
otherwise = { it }
|
||||
) ?: acc
|
||||
}.let { InterceptResult(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [CommandCallInterceptor] 拦截结果
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public class InterceptResult<T> internal constructor(
|
||||
private val _value: Any?,
|
||||
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?,
|
||||
) {
|
||||
/**
|
||||
* 构造一个 [InterceptResult], 以 [value] 继续处理后续指令执行.
|
||||
*/
|
||||
public constructor(value: T) : this(value as Any?, null)
|
||||
|
||||
/**
|
||||
* 构造一个 [InterceptResult], 以 [原因][reason] 中断指令执行.
|
||||
*/
|
||||
public constructor(reason: InterceptedReason) : this(reason as Any?, null)
|
||||
|
||||
@get:Contract(pure = true)
|
||||
public val value: T?
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
get() {
|
||||
val value = this._value
|
||||
return if (value is InterceptedReason) null else value as T
|
||||
}
|
||||
|
||||
@get:Contract(pure = true)
|
||||
public val reason: InterceptedReason?
|
||||
get() = this._value.safeCast()
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun <T, R> InterceptResult<T>.fold(
|
||||
onIntercepted: (reason: InterceptedReason) -> R,
|
||||
otherwise: (call: T) -> R,
|
||||
): R {
|
||||
contract {
|
||||
callsInPlace(onIntercepted, InvocationKind.AT_MOST_ONCE)
|
||||
callsInPlace(otherwise, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
value?.let(otherwise)
|
||||
reason?.let(onIntercepted)
|
||||
UNREACHABLE_CLAUSE
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun <T : R, R> InterceptResult<T>.getOrElse(onIntercepted: (reason: InterceptedReason) -> R): R {
|
||||
contract { callsInPlace(onIntercepted, InvocationKind.AT_MOST_ONCE) }
|
||||
reason?.let(onIntercepted)
|
||||
return value!!
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 [InterceptedReason]
|
||||
*
|
||||
* @see InterceptedReason.create
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
public inline fun InterceptedReason(message: String): InterceptedReason = InterceptedReason.create(message)
|
||||
|
||||
/**
|
||||
* 拦截原因
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface InterceptedReason {
|
||||
public val message: String
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 创建一个 [InterceptedReason]
|
||||
*/
|
||||
public fun create(message: String): InterceptedReason = InterceptedReasonData(message)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
@Serializable
|
||||
private data class InterceptedReasonData(override val message: String) : InterceptedReason
|
@ -11,6 +11,7 @@ package net.mamoe.mirai.console.extension
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
|
||||
import net.mamoe.mirai.console.extensions.*
|
||||
import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage
|
||||
@ -128,4 +129,19 @@ public class PluginComponentStorage(
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributeCommandCallParser(provider: CommandCallResolverProvider): Unit =
|
||||
contribute(CommandCallResolverProvider, plugin, provider)
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
/** 注册一个 [CommandCallInterceptorProvider] */
|
||||
@ExperimentalCommandDescriptors
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributeCommandCallInterceptor(lazyInstance: () -> CommandCallInterceptor): Unit =
|
||||
contribute(CommandCallInterceptorProvider, plugin, CommandCallInterceptorProviderImplLazy(lazyInstance))
|
||||
|
||||
/** 注册一个 [CommandCallInterceptorProvider] */
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmName("contributeCommandCallInterceptorProvider")
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributeCommandCallParser(provider: CommandCallInterceptorProvider): Unit =
|
||||
contribute(CommandCallInterceptorProvider, plugin, provider)
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.mamoe.mirai.console.extensions
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor
|
||||
import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.InstanceExtension
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCallInterceptorProvider : InstanceExtension<CommandCallInterceptor> {
|
||||
public companion object ExtensionPoint :
|
||||
AbstractInstanceExtensionPoint<CommandCallInterceptorProvider, CommandCallInterceptor>(CommandCallInterceptorProvider::class)
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class CommandCallInterceptorProviderImpl(override val instance: CommandCallInterceptor) : CommandCallInterceptorProvider
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class CommandCallInterceptorProviderImplLazy(initializer: () -> CommandCallInterceptor) : CommandCallInterceptorProvider {
|
||||
override val instance: CommandCallInterceptor by lazy(initializer)
|
||||
}
|
@ -20,8 +20,11 @@ import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor.Companion.intercepted
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallResolver.Companion.resolve
|
||||
import net.mamoe.mirai.console.command.resolve.getOrElse
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.util.ifNull
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.event.Listener
|
||||
@ -173,15 +176,32 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons
|
||||
// Don't move into CommandManager, compilation error / VerifyError
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal suspend fun executeCommandImpl(
|
||||
message: Message,
|
||||
message0: Message,
|
||||
caller: CommandSender,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult {
|
||||
val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.UnresolvedCommand(null)
|
||||
val resolved = call.resolve().fold(
|
||||
onSuccess = { it },
|
||||
onFailure = { return it }
|
||||
) ?: return CommandExecuteResult.UnresolvedCommand(call)
|
||||
val message = message0
|
||||
.intercepted(caller)
|
||||
.getOrElse { return CommandExecuteResult.Intercepted(null, null, null, it) }
|
||||
|
||||
val call = message.asMessageChain()
|
||||
.parseCommandCall(caller)
|
||||
.ifNull { return CommandExecuteResult.UnresolvedCommand(null) }
|
||||
.let { raw ->
|
||||
raw.intercepted()
|
||||
.getOrElse { return CommandExecuteResult.Intercepted(raw, null, null, it) }
|
||||
}
|
||||
|
||||
val resolved = call
|
||||
.resolve().fold(
|
||||
onSuccess = { it },
|
||||
onFailure = { return it }
|
||||
)
|
||||
.ifNull { return CommandExecuteResult.UnresolvedCommand(call) }
|
||||
.let { raw ->
|
||||
raw.intercepted()
|
||||
.getOrElse { return CommandExecuteResult.Intercepted(call, raw, null, it) }
|
||||
}
|
||||
|
||||
val command = resolved.callee
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user