Command resolving

This commit is contained in:
Him188 2020-10-18 12:26:54 +08:00
parent ccc9128023
commit df461290c0
24 changed files with 527 additions and 377 deletions

View File

@ -11,15 +11,16 @@
package net.mamoe.mirai.console.command
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.java.JCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
* 指令
@ -30,6 +31,8 @@ import net.mamoe.mirai.message.data.MessageChain
* @see CompositeCommand 复合指令
* @see SimpleCommand 简单的, 支持参数自动解析的指令
*
* @see CommandArgumentContextAware
*
* @see JCommand Java 用户添加协程帮助的 [Command]
*/
public interface Command {
@ -48,6 +51,13 @@ public interface Command {
@ResolveContext(COMMAND_NAME)
public val secondaryNames: Array<out String>
/**
*
*/
@ConsoleExperimentalApi("Property name is experimental")
@ExperimentalCommandDescriptors
public val overloads: List<CommandSignatureVariant>
/**
* 用法说明, 用于发送给用户. [usage] 一般包含 [description].
*/
@ -80,16 +90,6 @@ public interface Command {
*/
public val owner: CommandOwner
/**
* 在指令被执行时调用.
*
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
*
* @see CommandManager.executeCommand 查看更多信息
*/
@JvmBlockingBridge
public suspend fun CommandSender.onCommand(args: MessageChain)
public companion object {
/**
@ -116,12 +116,3 @@ public interface Command {
}
}
}
/**
* 调用 [Command.onCommand]
* @see Command.onCommand
*/
@JvmSynthetic
public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit =
sender.onCommand(args)

View File

@ -12,6 +12,8 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import kotlin.contracts.contract
@ -21,6 +23,8 @@ import kotlin.contracts.contract
*
* @see CommandExecuteStatus
*/
@ConsoleExperimentalApi("Not yet implemented")
@ExperimentalCommandDescriptors
public sealed class CommandExecuteResult {
/** 指令最终执行状态 */
public abstract val status: CommandExecuteStatus

View File

@ -8,16 +8,26 @@
*/
@file:Suppress(
"NOTHING_TO_INLINE", "unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE",
"NOTHING_TO_INLINE", "unused",
"MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME"
)
@file:JvmName("CommandManagerKt")
package net.mamoe.mirai.console.command
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
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.command.parse.CommandCallParser.Companion.parseCommandCall
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall.Companion.call
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.executeCommand
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
import net.mamoe.mirai.message.data.*
/**
@ -101,19 +111,45 @@ public interface CommandManager {
* 注意: 字符串与消息元素之间不需要空格, 会被强制分割. "bar[mirai:image:]" 会被分割为 "bar" [Image] 类型的消息元素.
* 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand]
*
* ### 未来的扩展
* 在将来, 参数语法分析过程可能会被扩展, 允许插件自定义处理方式, 因此可能不会简单地使用空格分隔.
* ### 扩展
* 参数语法分析过程可能会被扩展, 插件可以自定义处理方式, 因此可能不会简单地使用空格分隔.
*
* @param message 一条完整的指令. "/managers add 123456.123456"
* @param checkPermission `true` 时检查权限
*
* @see CommandCallParser
* @see CommandCallResolver
*
* @return 执行结果
*/
@JvmBlockingBridge
public suspend fun CommandSender.executeCommand(
// @JvmBlockingBridge
@OptIn(ExperimentalCommandDescriptors::class)
public suspend fun executeCommand(
caller: CommandSender,
message: Message,
checkPermission: Boolean = true,
): CommandExecuteResult
): CommandExecuteResult {
val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.CommandNotFound("")
val resolved = call.resolve() ?: return CommandExecuteResult.CommandNotFound(call.calleeName)
val command = resolved.callee
if (checkPermission && !command.permission.testPermission(caller)) {
return CommandExecuteResult.PermissionDenied(command, call.calleeName)
}
return kotlin.runCatching {
resolved.call()
}.fold(
onSuccess = {
CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain)
},
onFailure = {
CommandExecuteResult.ExecutionFailed(it, resolved.callee, call.calleeName, EmptyMessageChain)
}
)
}
/**
* 解析并执行一个指令
@ -124,35 +160,33 @@ public interface CommandManager {
* @return 执行结果
* @see executeCommand
*/
@JvmBlockingBridge
// @JvmBlockingBridge
public suspend fun CommandSender.executeCommand(
message: String,
checkPermission: Boolean = true,
): CommandExecuteResult = executeCommand(PlainText(message).asMessageChain(), checkPermission)
): CommandExecuteResult = executeCommand(this, PlainText(message).asMessageChain(), checkPermission)
@JvmName("resolveCall")
@ExperimentalCommandDescriptors
public fun CommandCall.resolve(): ResolvedCommandCall? {
GlobalComponentStorage.run {
CommandCallResolverProvider.useExtensions { provider ->
provider.instance.resolve(this@resolve)?.let { return it }
}
}
return null
}
/**
* 执行一个确切的指令
* @see executeCommand 获取更多信息
* [指令名称][commandName] 匹配对应的 [Command].
*
* #### 实现细节
* - [commandName] 带有 [commandPrefix] 时可以匹配到所有指令
* - [commandName] 不带有 [commandPrefix] 时只能匹配到 [Command.prefixOptional] 的指令
*
* @param commandName 可能带有或不带有 [commandPrefix].
*/
@JvmBlockingBridge
@JvmName("executeCommand")
public suspend fun Command.execute(
sender: CommandSender,
arguments: Message = EmptyMessageChain,
checkPermission: Boolean = true,
): CommandExecuteResult
/**
* 执行一个确切的指令
* @see executeCommand 获取更多信息
*/
@JvmBlockingBridge
@JvmName("executeCommand")
public suspend fun Command.execute(
sender: CommandSender,
arguments: String = "",
checkPermission: Boolean = true,
): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission)
public fun matchCommand(commandName: String): Command?
public companion object INSTANCE : CommandManager by CommandManagerImpl {
// TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191
@ -167,52 +201,37 @@ public interface CommandManager {
override val allRegisteredCommands: List<Command>
get() = CommandManagerImpl.allRegisteredCommands
override suspend fun Command.execute(
sender: CommandSender,
arguments: Message,
checkPermission: Boolean,
): CommandExecuteResult =
CommandManagerImpl.run { execute(sender, arguments = arguments, checkPermission = checkPermission) }
override suspend fun CommandSender.executeCommand(
message: String,
checkPermission: Boolean,
): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) }
override suspend fun Command.execute(
sender: CommandSender,
arguments: String,
checkPermission: Boolean,
): CommandExecuteResult = CommandManagerImpl.run { execute(sender, arguments, checkPermission) }
override suspend fun CommandSender.executeCommand(
message: Message,
checkPermission: Boolean,
): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) }
/**
* 执行一个确切的指令
* @see execute 获取更多信息
*/
public suspend fun CommandSender.execute(
command: Command,
arguments: Message,
checkPermission: Boolean = true,
): CommandExecuteResult {
return command.execute(this, arguments, checkPermission)
}
/**
* 执行一个确切的指令
* @see execute 获取更多信息
*/
public suspend fun CommandSender.execute(
command: Command,
arguments: String,
checkPermission: Boolean = true,
): CommandExecuteResult {
return command.execute(this, arguments, checkPermission)
}
}
}
/**
* 执行一个确切的指令
* @see executeCommand 获取更多信息
*/
// @JvmBlockingBridge
// @JvmName("executeCommand")
public suspend fun Command.execute(
sender: CommandSender,
arguments: String = "",
checkPermission: Boolean = true,
): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission)
/**
* 执行一个确切的指令
* @see executeCommand 获取更多信息
*/
// @JvmBlockingBridge
// @JvmName("executeCommand")
public suspend fun Command.execute(
sender: CommandSender,
arguments: Message = EmptyMessageChain,
checkPermission: Boolean = true,
): CommandExecuteResult {
// TODO: 2020/10/18 net.mamoe.mirai.console.command.CommandManager.execute
val chain = buildMessageChain {
append(this@execute.primaryName)
append(' ')
append(arguments)
}
return CommandManager.executeCommand(sender, chain, checkPermission)
}

View File

@ -20,7 +20,6 @@ import kotlinx.coroutines.launch
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender
import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender
import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender

View File

@ -124,13 +124,26 @@ public abstract class CompositeCommand(
@Target(AnnotationTarget.VALUE_PARAMETER)
protected annotation class Name(val value: String)
public final override suspend fun CommandSender.onCommand(args: MessageChain) {
matchSubCommand(args)?.parseAndExecute(this, args, true) ?: kotlin.run {
defaultSubCommand.onCommand(this, args)
@OptIn(ExperimentalCommandDescriptors::class)
override val overloads: List<CommandSignatureVariant> by lazy {
subCommands.flatMap { desc ->
desc.bakedSubNames.map { names ->
CommandSignatureVariantImpl(
valueParameters =
names.mapIndexed { index, s -> CommandValueParameter.StringConstant("p$index", s) } + desc.params.map {
CommandValueParameter.UserDefinedType(it.name, null,
isOptional = false,
isVararg = false,
type = it.type)
},
onCall = { resolvedCommandCall ->
desc.onCommand(resolvedCommandCall.caller, resolvedCommandCall.resolvedValueArguments.drop(names.size))
}
)
}
}
}
protected override suspend fun CommandSender.onDefault(rawArgs: MessageChain) {
sendMessage(usage)
}

View File

@ -11,14 +11,18 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariantImpl
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.java.JRawCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageChainBuilder
/**
* 无参数解析, 接收原生参数的指令.
@ -52,6 +56,15 @@ public abstract class RawCommand(
) : Command {
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
@ExperimentalCommandDescriptors
override val overloads: List<CommandSignatureVariant> = listOf(
CommandSignatureVariantImpl(listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))) { call ->
val sender = call.caller
val arguments = call.rawValueArguments
sender.onCommand(arguments.mapTo(MessageChainBuilder()) { it.value }.build())
}
)
/**
* 在指令被执行时调用.
*
@ -59,7 +72,7 @@ public abstract class RawCommand(
*
* @see CommandManager.execute 查看更多信息
*/
public abstract override suspend fun CommandSender.onCommand(args: MessageChain)
public abstract suspend fun CommandSender.onCommand(args: MessageChain)
}

View File

@ -61,6 +61,14 @@ public abstract class SimpleCommand(
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
CommandArgumentContextAware {
@ExperimentalCommandDescriptors
override val overloads: List<CommandSignatureVariant> = listOf(
CommandSignatureVariantImpl(listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))) { call ->
val sender = call.caller
subCommands.single().onCommand(sender, call.resolvedValueArguments)
}
)
/**
* 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖.
*/
@ -76,10 +84,6 @@ public abstract class SimpleCommand(
*/
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
public final override suspend fun CommandSender.onCommand(args: MessageChain) {
subCommands.single().parseAndExecute(this, args, false)
}
internal override fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) {
super.checkSubCommand(subCommands)
check(subCommands.size == 1) { "There can only be exactly one function annotated with Handler at this moment as overloading is not yet supported." }

View File

@ -9,19 +9,39 @@
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createOptional
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createRequired
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.safeCast
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf
@ExperimentalCommandDescriptors
public interface CommandDescriptor {
public val overloads: List<CommandSignatureVariant>
}
/**
* @see CommandSignatureVariantImpl
*/
@ExperimentalCommandDescriptors
public interface CommandSignatureVariant {
public val valueParameters: List<CommandValueParameter<*>>
@JvmBlockingBridge
public suspend fun call(resolvedCommandCall: ResolvedCommandCall)
}
@ExperimentalCommandDescriptors
public class CommandSignatureVariantImpl(
override val valueParameters: List<CommandValueParameter<*>>,
private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
) : CommandSignatureVariant {
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
return onCall(resolvedCommandCall)
}
}
@ -43,23 +63,85 @@ public interface ICommandParameter<T : Any?> {
* Reified type of [T]
*/
public val type: KType
public val isVararg: Boolean
public fun accepts(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): Boolean =
accepting(argument, commandArgumentContext).isAcceptable
public fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance
}
@ExperimentalCommandDescriptors
public sealed class ArgumentAcceptance(
/**
* Higher means more acceptable
*/
@ConsoleExperimentalApi
public val acceptanceLevel: Int,
) {
public object Direct : ArgumentAcceptance(Int.MAX_VALUE)
public class WithTypeConversion(
public val typeVariant: TypeVariant<*>,
) : ArgumentAcceptance(20)
public class WithContextualConversion(
public val parser: CommandValueArgumentParser<*>,
) : ArgumentAcceptance(10)
public class ResolutionAmbiguity(
public val candidates: List<TypeVariant<*>>,
) : ArgumentAcceptance(0)
public object Impossible : ArgumentAcceptance(-1)
public companion object {
@JvmStatic
public val ArgumentAcceptance.isAcceptable: Boolean
get() = acceptanceLevel > 0
@JvmStatic
public val ArgumentAcceptance.isNotAcceptable: Boolean
get() = acceptanceLevel <= 0
}
}
@ExperimentalCommandDescriptors
public sealed class CommandValueParameter<T> : ICommandParameter<T> {
init {
@Suppress("LeakingThis")
internal fun validate() { // // TODO: 2020/10/18 net.mamoe.mirai.console.command.descriptor.CommandValueParameter.validate$mirai_console_mirai_console_main
require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) {
"defaultValue is not instance of type"
}
}
public override fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance {
val expectingType = this.type
if (argument.type.isSubtypeOf(expectingType)) return ArgumentAcceptance.Direct
argument.typeVariants.associateWith { typeVariant ->
if (typeVariant.outType.isSubtypeOf(expectingType)) {
// TODO: 2020/10/11 resolution ambiguity
return ArgumentAcceptance.WithTypeConversion(typeVariant)
}
}
expectingType.classifierAsKClassOrNull()?.let { commandArgumentContext?.get(it) }?.let { parser ->
return ArgumentAcceptance.WithContextualConversion(parser)
}
return ArgumentAcceptance.Impossible
}
public class StringConstant(
public override val name: String,
public val expectingValue: String,
) : CommandValueParameter<String>() {
public override val type: KType get() = STRING_TYPE
public override val defaultValue: Nothing? get() = null
public override val isOptional: Boolean get() = false
public override val isVararg: Boolean get() = false
private companion object {
@OptIn(ExperimentalStdlibApi::class)
@ -67,23 +149,28 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> {
}
}
/**
* @see createOptional
* @see createRequired
*/
public class UserDefinedType<T>(
public override val name: String,
public override val defaultValue: T?,
public override val isOptional: Boolean,
public override val isVararg: Boolean,
public override val type: KType,
) : CommandValueParameter<T>() {
public companion object {
@JvmStatic
public inline fun <reified T : Any> createOptional(name: String, defaultValue: T): UserDefinedType<T> {
public inline fun <reified T : Any> createOptional(name: String, isVararg: Boolean, defaultValue: T): UserDefinedType<T> {
@OptIn(ExperimentalStdlibApi::class)
return UserDefinedType(name, defaultValue, true, typeOf<T>())
return UserDefinedType(name, defaultValue, true, isVararg, typeOf<T>())
}
@JvmStatic
public inline fun <reified T : Any> createRequired(name: String): UserDefinedType<T> {
public inline fun <reified T : Any> createRequired(name: String, isVararg: Boolean): UserDefinedType<T> {
@OptIn(ExperimentalStdlibApi::class)
return UserDefinedType(name, null, false, typeOf<T>())
return UserDefinedType(name, null, false, isVararg, typeOf<T>())
}
}
}

View File

@ -9,13 +9,8 @@
package net.mamoe.mirai.console.command.java
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
* Java 用户添加协程帮助的 [Command].
@ -24,17 +19,7 @@ import net.mamoe.mirai.message.data.MessageChain
*
* @see Command
*/
@ConsoleExperimentalApi("Not yet supported")
public interface JCommand : Command {
public override suspend fun CommandSender.onCommand(args: MessageChain) {
withContext(Dispatchers.IO) { onCommand(this@onCommand, args) }
}
/**
* 在指令被执行时调用.
*
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
*
* @see CommandManager.executeCommand 查看更多信息
*/
public fun onCommand(sender: CommandSender, args: MessageChain) // overrides blocking bridge
// TODO: 2020/10/18 JCommand
}

View File

@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
* 复合指令. 指令注册时候会通过反射构造指令解析器.
@ -68,6 +69,7 @@ import net.mamoe.mirai.console.permission.Permission
*
* @see buildCommandArgumentContext
*/
@ConsoleExperimentalApi("Not yet supported")
public abstract class JCompositeCommand
@JvmOverloads constructor(
owner: CommandOwner,

View File

@ -9,16 +9,15 @@
package net.mamoe.mirai.console.command.java
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
* Java 用户继承
@ -46,6 +45,7 @@ import net.mamoe.mirai.message.data.SingleMessage
*
* @see JRawCommand
*/
@ConsoleExperimentalApi("Not yet supported")
public abstract class JRawCommand
@JvmOverloads constructor(
/**
@ -72,19 +72,4 @@ public abstract class JRawCommand
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
public final override var prefixOptional: Boolean = false
protected set
/**
* 在指令被执行时调用.
*
* @param args 指令参数. 数组元素类型可能是 [SingleMessage] [String]. 且已经以 ' ' 分割.
*
* @see CommandManager.execute 查看更多信息
*/
@Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("onCommand")
public abstract fun onCommand(sender: CommandSender, args: MessageChain)
public final override suspend fun CommandSender.onCommand(args: MessageChain) {
withContext(Dispatchers.IO) { onCommand(this@onCommand, args) }
}
}

View File

@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
* Java 实现:
@ -42,6 +43,7 @@ import net.mamoe.mirai.console.permission.Permission
* @see SimpleCommand
* @see [CommandManager.executeCommand]
*/
@ConsoleExperimentalApi("Not yet supported")
public abstract class JSimpleCommand(
owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String,

View File

@ -14,6 +14,8 @@ import net.mamoe.mirai.message.data.MessageChain
*
* @see CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall]
* @see CommandCallParserProvider The extension point
*
* @see SpaceSeparatedCommandCallParser
*/
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
@ -24,7 +26,7 @@ public interface CommandCallParser {
*
* @return `null` if unable to parse (i.e. due to syntax errors).
*/
public fun parse(sender: CommandSender, message: MessageChain): CommandCall?
public fun parse(caller: CommandSender, message: MessageChain): CommandCall?
public companion object {
/**

View File

@ -14,6 +14,7 @@ import net.mamoe.mirai.console.command.descriptor.MessageContentTypeVariant
import net.mamoe.mirai.console.command.descriptor.NoValueArgumentMappingException
import net.mamoe.mirai.console.command.descriptor.TypeVariant
import net.mamoe.mirai.message.data.MessageContent
import kotlin.reflect.KType
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf
@ -34,6 +35,7 @@ public interface CommandArgument
*/
@ExperimentalCommandDescriptors
public interface CommandValueArgument : CommandArgument {
public val type: KType
public val value: RawCommandArgument
public val typeVariants: List<TypeVariant<*>>
}
@ -45,6 +47,8 @@ public interface CommandValueArgument : CommandArgument {
public data class InvariantCommandValueArgument(
public override val value: RawCommandArgument,
) : CommandValueArgument {
@OptIn(ExperimentalStdlibApi::class)
override val type: KType = typeOf<MessageContent>()
override val typeVariants: List<TypeVariant<*>> = listOf(MessageContentTypeVariant)
}
@ -57,10 +61,14 @@ public fun <T> CommandValueArgument.mapValue(typeVariant: TypeVariant<T>): T = t
public inline fun <reified T> CommandValueArgument.mapToType(): T =
mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>())
@OptIn(ExperimentalStdlibApi::class)
@ExperimentalCommandDescriptors
public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
public fun <T> CommandValueArgument.mapToType(type: KType): T =
mapToTypeOrNull(type) ?: throw NoValueArgumentMappingException(this, type)
@ExperimentalCommandDescriptors
public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType): T? {
@OptIn(ExperimentalStdlibApi::class)
val expectingType = typeOf<T>()
val result = typeVariants
.filter { it.outType.isSubtypeOf(expectingType) }
.also {
@ -71,5 +79,12 @@ public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
acc
else typeVariant
}
@Suppress("UNCHECKED_CAST")
return result.mapValue(value) as T
}
@ExperimentalCommandDescriptors
public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
@OptIn(ExperimentalStdlibApi::class)
return mapToTypeOrNull(typeOf<T>())
}

View File

@ -0,0 +1,23 @@
package net.mamoe.mirai.console.command.parse
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageContent
import net.mamoe.mirai.message.data.content
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public object SpaceSeparatedCommandCallParser : CommandCallParser {
override fun parse(caller: CommandSender, message: MessageChain): CommandCall? {
val flatten = message.flattenCommandComponents().filterIsInstance<MessageContent>()
if (flatten.isEmpty()) return null
return CommandCallImpl(
caller = caller,
calleeName = flatten.first().content,
valueArguments = flatten.drop(1).map(::InvariantCommandValueArgument)
)
}
}

View File

@ -0,0 +1,121 @@
package net.mamoe.mirai.console.command.resolve
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.cast
import net.mamoe.mirai.console.util.safeCast
import java.util.*
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public object BuiltInCommandCallResolver : CommandCallResolver {
override fun resolve(call: CommandCall): ResolvedCommandCall? {
val callee = CommandManager.matchCommand(call.calleeName) ?: return null
val valueArguments = call.valueArguments
val context = callee.safeCast<CommandArgumentContextAware>()?.context
val signature = resolveImpl(callee, valueArguments, context) ?: return null
return ResolvedCommandCallImpl(call.caller, callee, signature, call.valueArguments)
}
private data class ResolveData(
val variant: CommandSignatureVariant,
val argumentAcceptances: List<ArgumentAcceptanceWithIndex>,
)
private data class ArgumentAcceptanceWithIndex(
val index: Int,
val acceptance: ArgumentAcceptance,
)
private fun resolveImpl(
callee: Command,
valueArguments: List<CommandValueArgument>,
context: CommandArgumentContext?,
): CommandSignatureVariant? {
callee.overloads
.mapNotNull l@{ signature ->
val zipped = signature.valueParameters.zip(valueArguments)
if (signature.valueParameters.drop(zipped.size).any { !it.isOptional }) return@l null // not enough args
ResolveData(signature, zipped.mapIndexed { index, (parameter, argument) ->
val accepting = parameter.accepting(argument, context)
if (accepting.isNotAcceptable) {
return@l null // argument type not assignable
}
ArgumentAcceptanceWithIndex(index, accepting)
})
}
.also { result -> result.singleOrNull()?.let { return it.variant } }
.takeLongestMatches()
.ifEmpty { return null }
.also { result -> result.singleOrNull()?.let { return it.variant } }
// take single ArgumentAcceptance.Direct
.also { list ->
val candidates = list
.flatMap { phase ->
phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct }.map { phase to it }
}
candidates.singleOrNull()?.let { return it.first.variant } // single Direct
if (candidates.distinctBy { it.second.index }.size != candidates.size) {
// Resolution ambiguity
/*
open class A
open class AA: A()
open class C
open class CC: C()
fun foo(a: A, c: CC) = 1
fun foo(a: AA, c: C) = 1
*/
// The call is foo(AA(), C()) or foo(A(), CC())
return null
}
}
return null
}
/*
open class A
open class B : A()
open class C : A()
open class D : C()
open class BB : B()
fun foo(a: A, c: C) = 1
//fun foo(a: A, c: A) = 1
//fun foo(a: A, c: C, def: Int = 0) = 1
fun foo(a: B, c: C, d: D) = ""
fun foo(b: BB, a: A, d: C) = 1.0
fun main() {
val a = foo(D(), D()) // int
val b = foo(A(), C()) // int
val d = foo(BB(), c = C(), D()) // string
}
*/
private fun List<ResolveData>.takeLongestMatches(): List<ResolveData> {
if (isEmpty()) return emptyList()
return associateByTo(TreeMap(Comparator.reverseOrder())) { it.variant.valueParameters.size }.let { m ->
val firstKey = m.keys.first().cast<Int>()
m.filter { it.key == firstKey }.map { it.value.cast() }
}
}
}

View File

@ -11,8 +11,6 @@ package net.mamoe.mirai.console.command.resolve
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
/**
* The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered []
@ -20,17 +18,4 @@ import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
@ExperimentalCommandDescriptors
public interface CommandCallResolver {
public fun resolve(call: CommandCall): ResolvedCommandCall?
public companion object {
@JvmStatic
@JvmName("resolveCall")
public fun CommandCall.resolve(): ResolvedCommandCall? {
GlobalComponentStorage.run {
CommandCallResolverProvider.useExtensions { provider ->
provider.instance.resolve(this@resolve)?.let { return it }
}
}
return null
}
}
}

View File

@ -7,19 +7,23 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command.resolve;
package net.mamoe.mirai.console.command.resolve
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.descriptor.CommandDescriptor
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.command.parse.mapToType
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.LazyThreadSafetyMode.PUBLICATION
/**
* The resolved [CommandCall].
*
* @see ResolvedCommandCallImpl
*/
@ExperimentalCommandDescriptors
public interface ResolvedCommandCall {
@ -31,17 +35,41 @@ public interface ResolvedCommandCall {
public val callee: Command
/**
* The callee [CommandDescriptor], specifically a sub command from [CompositeCommand]
*/
public val calleeDescriptor: CommandDescriptor
/**
* The callee [CommandSignatureVariant]
* The callee [CommandSignatureVariant], specifically a sub command from [CompositeCommand]
*/
public val calleeSignature: CommandSignatureVariant
/**
* Original arguments
*/
public val rawValueArguments: List<CommandValueArgument>
/**
* Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index.
*/
public val valueArguments: List<CommandValueArgument>
@ConsoleExperimentalApi
public val resolvedValueArguments: List<Any?>
public companion object {
@JvmStatic
@ExperimentalCommandDescriptors
public suspend fun ResolvedCommandCall.call() {
return this.calleeSignature.call(this)
}
}
}
@ExperimentalCommandDescriptors
public class ResolvedCommandCallImpl(
override val caller: CommandSender,
override val callee: Command,
override val calleeSignature: CommandSignatureVariant,
override val rawValueArguments: List<CommandValueArgument>,
) : ResolvedCommandCall {
override val resolvedValueArguments: List<Any?> by lazy(PUBLICATION) {
calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) ->
argument.mapToType(parameter.type)
// TODO: 2020/10/17 consider vararg and optional
}
}
}

View File

@ -16,17 +16,15 @@ import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.Command.Companion.allNames
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageContent
import net.mamoe.mirai.message.data.asMessageChain
import net.mamoe.mirai.message.data.content
import net.mamoe.mirai.utils.MiraiLogger
import java.util.concurrent.locks.ReentrantLock
internal object CommandManagerImpl : CommandManager, CoroutineScope by CoroutineScope(MiraiConsole.job) {
internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiConsole.childScope("CommandManagerImpl") {
private val logger: MiraiLogger by lazy {
MiraiConsole.createLogger("command")
}
@ -48,11 +46,11 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
/**
* 从原始的 command 中解析出 Command 对象
*/
internal fun matchCommand(rawCommand: String): Command? {
if (rawCommand.startsWith(commandPrefix)) {
return requiredPrefixCommandMap[rawCommand.substringAfter(commandPrefix).toLowerCase()]
override fun matchCommand(commandName: String): Command? {
if (commandName.startsWith(commandPrefix)) {
return requiredPrefixCommandMap[commandName.substringAfter(commandPrefix).toLowerCase()]
}
return optionalPrefixCommandMap[rawCommand.toLowerCase()]
return optionalPrefixCommandMap[commandName.toLowerCase()]
}
internal val commandListener: Listener<MessageEvent> by lazy {
@ -65,7 +63,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
) {
val sender = this.toCommandSender()
when (val result = sender.executeCommand(message)) {
when (val result = executeCommand(sender, message)) {
is CommandExecuteResult.PermissionDenied -> {
if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) {
sender.sendMessage("权限不足")
@ -152,44 +150,4 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
}
override fun Command.isRegistered(): Boolean = this in _registeredCommands
override suspend fun Command.execute(
sender: CommandSender,
arguments: Message,
checkPermission: Boolean
): CommandExecuteResult {
return sender.executeCommandInternal(
this,
arguments.flattenCommandComponents(),
primaryName,
checkPermission
)
}
override suspend fun Command.execute(
sender: CommandSender,
arguments: String,
checkPermission: Boolean
): CommandExecuteResult {
return sender.executeCommandInternal(
this,
arguments.flattenCommandComponents(),
primaryName,
checkPermission
)
}
override suspend fun CommandSender.executeCommand(
message: Message,
checkPermission: Boolean
): CommandExecuteResult {
val msg = message.asMessageChain().filterIsInstance<MessageContent>()
if (msg.isEmpty()) return CommandExecuteResult.CommandNotFound("")
return executeCommandInternal(msg, msg[0].content.substringBefore(' '), checkPermission)
}
override suspend fun CommandSender.executeCommand(message: String, checkPermission: Boolean): CommandExecuteResult {
if (message.isBlank()) return CommandExecuteResult.CommandNotFound("")
return executeCommandInternal(message, message.substringBefore(' '), checkPermission)
}
}

View File

@ -13,8 +13,8 @@ package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.KType
/*
internal fun Parameter.toCommandParam(): CommandParameter<*> {
@ -39,10 +39,10 @@ internal data class CommandParameter<T : Any>(
/**
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析.
*/
val type: KClass<T>, // exact type
val type: KType, // exact type
val parameter: KParameter, // source parameter
) {
constructor(name: String, type: KClass<T>, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this(
constructor(name: String, type: KType, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this(
name, type, parameter
) {
this._overrideParser = parser

View File

@ -12,12 +12,12 @@
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.buildMessageChain
import kotlin.reflect.KAnnotatedElement
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
@ -89,6 +89,21 @@ internal abstract class AbstractReflectionCommand
}
@OptIn(ExperimentalCommandDescriptors::class)
private fun <T : Any> CommandParameter<T>.toCommandValueParameter(): CommandValueParameter<T> {
return CommandValueParameter.UserDefinedType<T>(name, null, false, false, type)
}
@OptIn(ExperimentalCommandDescriptors::class)
override val overloads: List<CommandSignatureVariant> by lazy {
subCommands.map { desc ->
CommandSignatureVariantImpl(desc.params.map { it.toCommandValueParameter() }) { call ->
desc.onCommand(call.caller, call.resolvedValueArguments)
}
}
}
interface SubCommandAnnotationResolver {
fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Boolean
fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String>
@ -132,27 +147,12 @@ internal abstract class AbstractReflectionCommand
val params: Array<CommandParameter<*>>,
val description: String,
val permission: Permission,
val onCommand: suspend (sender: CommandSender, parsedArgs: Map<KParameter, Any?>) -> Boolean,
val onCommand: suspend (sender: CommandSender, parsedArgs: List<Any?>) -> Boolean,
val context: CommandArgumentContext,
val argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?>,
) {
val usage: String = createUsage(this@AbstractReflectionCommand)
internal suspend fun parseAndExecute(
sender: CommandSender,
argsWithSubCommandNameNotRemoved: MessageChain,
removeSubName: Boolean,
) {
val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) 1 else 0)
if (!this.permission.testPermission(sender)) {
sender.sendMessage(usage) // TODO: 2020/8/26 #127
return
}
if (args == null || !onCommand(sender, args)) {
sender.sendMessage(usage)
}
}
private fun KParameter.isOptional(): Boolean {
return isOptional || this.type.isMarkedNullable
}
@ -163,49 +163,6 @@ internal abstract class AbstractReflectionCommand
@JvmField
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
private fun parseArgs(sender: CommandSender, rawArgs: MessageChain, offset: Int): MutableMap<KParameter, Any?>? {
if (rawArgs.size < offset + minimalArgumentsSize)
return null
//require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
return argumentBuilder(sender).also { result ->
params.forEachIndexed { index, parameter ->
val rawArg = rawArgs.getOrNull(offset + index)
result[parameter.parameter] = when (rawArg) {
null -> {
val p = parameter.parameter
when {
p.isOptional -> return@forEachIndexed
p.type.isMarkedNullable -> {
result[parameter.parameter] = null
return@forEachIndexed
}
else -> null
}
}
is PlainText -> context[parameter.type]?.parse(rawArg.content, sender)
is MessageContent -> context[parameter.type]?.parse(rawArg, sender)
else -> throw IllegalArgumentException("Illegal Message kind: ${rawArg.kClassQualifiedNameOrTip}")
} ?: error("Cannot find a parser for $rawArg")
}
}
}
}
/**
* @param rawArgs 元素类型必须为 [SingleMessage] [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException]
*/
internal fun matchSubCommand(rawArgs: MessageChain): SubCommandDescriptor? {
val maxCount = rawArgs.size
var cur = 0
bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) ->
if (name.size != cur) {
if (cur++ == maxCount) return null
}
if (name.contentEqualsOffset(rawArgs, length = cur)) {
return descriptor
}
}
return null
}
}
@ -349,23 +306,23 @@ internal fun AbstractReflectionCommand.createSubCommand(
// if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
val paramName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown"
CommandParameter(
CommandParameter<Any>(
paramName,
(param.type.classifier as? KClass<*>)
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"),
param.type,
param
)
}.toTypedArray()
// TODO: 2020/09/19 检查 optional/nullable 是否都在最后
@Suppress("UNCHECKED_CAST")
return SubCommandDescriptor(
commandName,
params,
params as Array<CommandParameter<*>>,
subDescription, // overridePermission?.value
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
onCommand = { sender: CommandSender, args: Map<KParameter, Any?> ->
val result = function.callSuspendBy(args)
onCommand = { _: CommandSender, args ->
val result = function.callSuspendBy(parameters.zip(args).toMap())
checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" }

View File

@ -1,67 +0,0 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.asMessageChain
@JvmSynthetic
@Throws(CommandExecutionException::class)
internal suspend fun CommandSender.executeCommandInternal(
command: Command,
args: MessageChain,
commandName: String,
checkPermission: Boolean,
): CommandExecuteResult {
if (checkPermission && !command.permission.testPermission(this)) {
return CommandExecuteResult.PermissionDenied(command, commandName)
}
kotlin.runCatching {
command.onCommand(this, args)
}.fold(
onSuccess = {
return CommandExecuteResult.Success(
commandName = commandName,
command = command,
args = args
)
},
onFailure = { exception ->
return if (exception is IllegalArgumentException) CommandExecuteResult.IllegalArgument(
commandName = commandName,
command = command,
exception = exception,
args = args
) else CommandExecuteResult.ExecutionFailed(
commandName = commandName,
command = command,
exception = exception,
args = args
)
}
)
}
@JvmSynthetic
internal suspend fun CommandSender.executeCommandInternal(
messages: Any,
commandName: String,
checkPermission: Boolean,
): CommandExecuteResult {
val command =
CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
val args = messages.flattenCommandComponents()
return executeCommandInternal(command, args.drop(1).asMessageChain(), commandName, checkPermission)
}

View File

@ -0,0 +1,25 @@
/*
* 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
*/
package net.mamoe.mirai.console.util
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageContent
@ConsoleExperimentalApi
public object MessageUtils {
@JvmStatic
public fun MessageChain.messageContentsSequence(): Sequence<MessageContent> = asSequence().filterIsInstance<MessageContent>()
@JvmStatic
public fun MessageChain.firstContent(): MessageContent = messageContentsSequence().first()
@JvmStatic
public fun MessageChain.firstContentOrNull(): MessageContent? = messageContentsSequence().firstOrNull()
}

View File

@ -16,7 +16,6 @@ import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.Testing
import net.mamoe.mirai.console.Testing.withTesting
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands