Introduce CommandCallInterceptor

This commit is contained in:
Him188 2020-11-17 09:34:47 +08:00
parent 450e66ccfd
commit 136b0c11d8
5 changed files with 272 additions and 6 deletions

View File

@ -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(
/** 尝试执行的指令 */

View File

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

View File

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

View File

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

View File

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