Add CommandContext and support retrieving original message chain from command, close #1835

This commit is contained in:
Him188 2022-06-15 12:39:01 +01:00
parent 586c61bbab
commit eeb361358a
15 changed files with 508 additions and 83 deletions

View File

@ -171,6 +171,11 @@ public final class net/mamoe/mirai/console/command/Command$Companion {
public final fun getAllNames (Lnet/mamoe/mirai/console/command/Command;)[Ljava/lang/String;
}
public abstract interface class net/mamoe/mirai/console/command/CommandContext {
public abstract fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain;
public abstract fun getSender ()Lnet/mamoe/mirai/console/command/CommandSender;
}
public abstract class net/mamoe/mirai/console/command/CommandExecuteResult {
public abstract fun getCall ()Lnet/mamoe/mirai/console/command/parse/CommandCall;
public abstract fun getCommand ()Lnet/mamoe/mirai/console/command/Command;
@ -571,7 +576,8 @@ public abstract class net/mamoe/mirai/console/command/RawCommand : net/mamoe/mir
public fun getPrimaryName ()Ljava/lang/String;
public fun getSecondaryNames ()[Ljava/lang/String;
public fun getUsage ()Ljava/lang/String;
public abstract fun onCommand (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun onCommand (Lnet/mamoe/mirai/console/command/CommandContext;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun onCommand (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract class net/mamoe/mirai/console/command/SimpleCommand : net/mamoe/mirai/console/command/AbstractCommand, net/mamoe/mirai/console/command/Command, net/mamoe/mirai/console/command/descriptor/CommandArgumentContextAware {
@ -855,25 +861,28 @@ public abstract interface class net/mamoe/mirai/console/command/descriptor/Comma
public abstract fun isOptional ()Z
}
public final class net/mamoe/mirai/console/command/descriptor/CommandReceiverParameter : net/mamoe/mirai/console/command/descriptor/AbstractCommandParameter, net/mamoe/mirai/console/command/descriptor/CommandParameter {
public abstract class net/mamoe/mirai/console/command/descriptor/CommandReceiverParameter : net/mamoe/mirai/console/command/descriptor/AbstractCommandParameter, net/mamoe/mirai/console/command/descriptor/CommandParameter {
public static final field Companion Lnet/mamoe/mirai/console/command/descriptor/CommandReceiverParameter$Companion;
public static final field NAME Ljava/lang/String;
public fun <init> (ZLkotlin/reflect/KType;)V
public final fun component1 ()Z
public final fun component2 ()Lkotlin/reflect/KType;
public final fun copy (ZLkotlin/reflect/KType;)Lnet/mamoe/mirai/console/command/descriptor/CommandReceiverParameter;
public static synthetic fun copy$default (Lnet/mamoe/mirai/console/command/descriptor/CommandReceiverParameter;ZLkotlin/reflect/KType;ILjava/lang/Object;)Lnet/mamoe/mirai/console/command/descriptor/CommandReceiverParameter;
public fun equals (Ljava/lang/Object;)Z
public fun getName ()Ljava/lang/String;
public fun getType ()Lkotlin/reflect/KType;
public fun hashCode ()I
public fun isOptional ()Z
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/console/command/descriptor/CommandReceiverParameter$Companion {
}
public final class net/mamoe/mirai/console/command/descriptor/CommandReceiverParameter$Context : net/mamoe/mirai/console/command/descriptor/CommandReceiverParameter {
public fun <init> (ZLkotlin/reflect/KType;)V
public synthetic fun <init> (ZLkotlin/reflect/KType;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getType ()Lkotlin/reflect/KType;
public fun isOptional ()Z
}
public final class net/mamoe/mirai/console/command/descriptor/CommandReceiverParameter$Sender : net/mamoe/mirai/console/command/descriptor/CommandReceiverParameter {
public fun <init> (ZLkotlin/reflect/KType;)V
public fun getType ()Lkotlin/reflect/KType;
public fun isOptional ()Z
}
public class net/mamoe/mirai/console/command/descriptor/CommandResolutionException : java/lang/RuntimeException {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
@ -1118,6 +1127,7 @@ public abstract class net/mamoe/mirai/console/command/java/JRawCommand : net/mam
public fun getPrimaryName ()Ljava/lang/String;
public fun getSecondaryNames ()[Ljava/lang/String;
public fun getUsage ()Ljava/lang/String;
public fun onCommand (Lnet/mamoe/mirai/console/command/CommandContext;Lnet/mamoe/mirai/message/data/MessageChain;)V
public fun onCommand (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/message/data/MessageChain;)V
protected final fun setDescription (Ljava/lang/String;)V
protected final fun setPermission (Lnet/mamoe/mirai/console/permission/Permission;)V
@ -1145,13 +1155,15 @@ public abstract interface class net/mamoe/mirai/console/command/parse/CommandArg
public abstract interface class net/mamoe/mirai/console/command/parse/CommandCall {
public abstract fun getCalleeName ()Ljava/lang/String;
public abstract fun getCaller ()Lnet/mamoe/mirai/console/command/CommandSender;
public abstract fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain;
public abstract fun getValueArguments ()Ljava/util/List;
}
public final class net/mamoe/mirai/console/command/parse/CommandCallImpl : net/mamoe/mirai/console/command/parse/CommandCall {
public fun <init> (Lnet/mamoe/mirai/console/command/CommandSender;Ljava/lang/String;Ljava/util/List;)V
public fun <init> (Lnet/mamoe/mirai/console/command/CommandSender;Ljava/lang/String;Ljava/util/List;Lnet/mamoe/mirai/message/data/MessageChain;)V
public fun getCalleeName ()Ljava/lang/String;
public fun getCaller ()Lnet/mamoe/mirai/console/command/CommandSender;
public fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain;
public fun getValueArguments ()Ljava/util/List;
}
@ -1234,6 +1246,7 @@ public abstract interface class net/mamoe/mirai/console/command/resolve/Resolved
public abstract fun getCallee ()Lnet/mamoe/mirai/console/command/Command;
public abstract fun getCalleeSignature ()Lnet/mamoe/mirai/console/command/descriptor/CommandSignature;
public abstract fun getCaller ()Lnet/mamoe/mirai/console/command/CommandSender;
public abstract fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain;
public abstract fun getRawValueArguments ()Ljava/util/List;
public abstract fun getResolvedValueArguments ()Ljava/util/List;
}
@ -1242,10 +1255,11 @@ public final class net/mamoe/mirai/console/command/resolve/ResolvedCommandCall$C
}
public final class net/mamoe/mirai/console/command/resolve/ResolvedCommandCallImpl : net/mamoe/mirai/console/command/resolve/ResolvedCommandCall {
public fun <init> (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/descriptor/CommandSignature;Ljava/util/List;Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)V
public fun <init> (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/descriptor/CommandSignature;Ljava/util/List;Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;Lnet/mamoe/mirai/message/data/MessageChain;)V
public fun getCallee ()Lnet/mamoe/mirai/console/command/Command;
public fun getCalleeSignature ()Lnet/mamoe/mirai/console/command/descriptor/CommandSignature;
public fun getCaller ()Lnet/mamoe/mirai/console/command/CommandSender;
public fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain;
public fun getRawValueArguments ()Ljava/util/List;
public fun getResolvedValueArguments ()Ljava/util/List;
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2019-2022 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/dev/LICENSE
*/
package net.mamoe.mirai.console.command
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.NotStableForInheritance
/**
* 指令执行环境
* @since 2.12
*/
@NotStableForInheritance
public interface CommandContext {
/**
* 指令发送者
*/
public val sender: CommandSender
/**
* 触发指令的原消息链包含元数据也包含指令名
*
* 示例内容`messageChainOf(MessageSource(...), PlainText("/test"), PlainText("arg1"))`
*/
public val originalMessage: MessageChain
}
internal class CommandContextImpl(
override val sender: CommandSender,
override val originalMessage: MessageChain
) : CommandContext

View File

@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
* 此源代码的使用受 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
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command
@ -66,6 +66,12 @@ import kotlin.annotation.AnnotationTarget.FUNCTION
* sendMessage("/manage list 被调用了")
* }
*
* @SubCommand
* suspend fun CommandContext.repeat() {
* // 使用 CommandContext 作为参数,
* sendMessage("/manage list 被调用了")
* }
*
* // 支持 Image 类型, 需在聊天中执行此指令.
* @SubCommand
* suspend fun UserCommandSender.test(image: Image) { // 执行 "/manage test <一张图片>" 时调用这个函数

View File

@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
* 此源代码的使用受 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
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.console.command
@ -19,7 +19,6 @@ import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.buildMessageChain
import kotlin.reflect.typeOf
/**
* 无参数解析, 接收原生参数的指令.
@ -60,7 +59,7 @@ public abstract class RawCommand(
@ExperimentalCommandDescriptors
override val overloads: List<@JvmWildcard CommandSignature> = listOf(
CommandSignatureImpl(
receiverParameter = CommandReceiverParameter(false, typeOf<CommandSender>()),
receiverParameter = CommandReceiverParameter.Context(false),
valueParameters = listOf(
AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>(
"args",
@ -70,7 +69,8 @@ public abstract class RawCommand(
) { call ->
val sender = call.caller
val arguments = call.rawValueArguments
sender.onCommand(buildMessageChain { arguments.forEach { +it.value } })
val context = CommandContextImpl(sender, call.originalMessage)
context.onCommand(buildMessageChain { arguments.forEach { +it.value } })
}
)
@ -81,7 +81,21 @@ public abstract class RawCommand(
*
* @see CommandManager.executeCommand 查看更多信息
*/
public abstract suspend fun CommandSender.onCommand(args: MessageChain)
public open suspend fun CommandSender.onCommand(args: MessageChain) {
}
/**
* 在指令被执行时调用.
*
* @param args 指令参数.
* @see CommandManager.executeCommand 查看更多信息
*
* @since 2.12
*/
public open suspend fun CommandContext.onCommand(args: MessageChain) {
return sender.onCommand(args)
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
@ -9,6 +9,7 @@
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.console.command.CommandContext
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createOptional
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createRequired
@ -122,22 +123,50 @@ public sealed class ArgumentAcceptance(
}
@ExperimentalCommandDescriptors
public data class CommandReceiverParameter<T : CommandSender>(
override val isOptional: Boolean,
override val type: KType,
public sealed class CommandReceiverParameter<T>(
) : CommandParameter<T>, AbstractCommandParameter<T>() {
override val name: String get() = NAME
init {
val classifier = type.classifier
require(classifier is KClass<*>) {
"CommandReceiverParameter.type.classifier must be KClass."
}
require(classifier.isSubclassOf(CommandSender::class)) {
"CommandReceiverParameter.type.classifier must be subclass of CommandSender."
/**
* @since 2.12
*/
@ExperimentalCommandDescriptors
public class Sender(
override val isOptional: Boolean,
override val type: KType,
) : CommandReceiverParameter<CommandSender>() {
init {
val classifier = type.classifier
require(classifier is KClass<*>) {
"CommandReceiverParameter.Sender.type.classifier must be KClass."
}
require(classifier.isSubclassOf(CommandSender::class)) {
"CommandReceiverParameter.Sender.type.classifier must be subclass of CommandSender."
}
}
}
/**
* @since 2.12
*/
@ExperimentalCommandDescriptors
public class Context(
override val isOptional: Boolean,
override val type: KType = typeOf<CommandContext>(),
) : CommandReceiverParameter<CommandContext>() {
init {
val classifier = type.classifier
require(classifier is KClass<*>) {
"CommandReceiverParameter.Context.type.classifier must be KClass."
}
require(classifier.isSubclassOf(CommandContext::class)) {
"CommandReceiverParameter.Context.type.classifier must be subclass of CommandContext."
}
}
}
override val name: String get() = NAME
public companion object {
public const val NAME: String = "<receiver>"
}
@ -221,7 +250,6 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
}
private companion object {
@OptIn(ExperimentalStdlibApi::class)
val STRING_TYPE = typeOf<String>()
}
}
@ -251,13 +279,11 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
public companion object {
@JvmStatic
public inline fun <reified T : Any> createOptional(name: String, isVararg: Boolean): UserDefinedType<T> {
@OptIn(ExperimentalStdlibApi::class)
return UserDefinedType(name, true, isVararg, typeOf<T>())
}
@JvmStatic
public inline fun <reified T : Any> createRequired(name: String, isVararg: Boolean): UserDefinedType<T> {
@OptIn(ExperimentalStdlibApi::class)
return UserDefinedType(name, false, isVararg, typeOf<T>())
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
@ -25,7 +25,7 @@ public interface CommandSignature {
* 接收者参数, [CommandSender] 子类
*/
@ConsoleExperimentalApi
public val receiverParameter: CommandReceiverParameter<out CommandSender>?
public val receiverParameter: CommandReceiverParameter<*>?
/**
* 形式 值参数.
@ -67,7 +67,7 @@ public abstract class AbstractCommandSignature : CommandSignature {
@ExperimentalCommandDescriptors
public open class CommandSignatureImpl(
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
override val receiverParameter: CommandReceiverParameter<*>?,
override val valueParameters: List<AbstractCommandValueParameter<*>>,
private val onCall: suspend CommandSignatureImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
) : CommandSignature, AbstractCommandSignature() {
@ -79,7 +79,7 @@ public open class CommandSignatureImpl(
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public open class CommandSignatureFromKFunctionImpl(
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
override val receiverParameter: CommandReceiverParameter<*>?,
override val valueParameters: List<AbstractCommandValueParameter<*>>,
override val originFunction: KFunction<*>,
private val onCall: suspend CommandSignatureFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
@ -20,7 +20,6 @@ import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.buildMessageChain
import net.mamoe.mirai.utils.runBIO
import kotlin.reflect.typeOf
/**
* Java 用户继承
@ -82,7 +81,7 @@ public abstract class JRawCommand
@ExperimentalCommandDescriptors
override val overloads: List<@JvmWildcard CommandSignature> = listOf(
CommandSignatureImpl(
receiverParameter = CommandReceiverParameter(false, typeOf<CommandSender>()),
receiverParameter = CommandReceiverParameter.Context(false),
valueParameters = listOf(
AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>(
"args",
@ -92,7 +91,12 @@ public abstract class JRawCommand
) { call ->
val sender = call.caller
val arguments = call.rawValueArguments
runBIO { onCommand(sender, buildMessageChain { arguments.forEach { +it.value } }) }
runBIO {
onCommand(
CommandContextImpl(sender, call.originalMessage),
buildMessageChain { arguments.forEach { +it.value } }
)
}
}
)
@ -105,4 +109,16 @@ public abstract class JRawCommand
* @since 2.8
*/
public open fun onCommand(sender: CommandSender, args: MessageChain) {}
/**
* 在指令被执行时调用.
*
* @param args 指令参数.
*
* @see CommandManager.executeCommand 查看更多信息
* @since 2.12
*/
public open fun onCommand(context: CommandContext, args: MessageChain) {
onCommand(context.sender, args)
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
@ -50,6 +50,12 @@ public interface CommandCall {
*/
public val valueArguments: List<CommandValueArgument>
/**
* Original message
* @since 2.12
*/
public val originalMessage: MessageChain
// maybe add contextual arguments, i.e. from MessageMetadata
}
@ -58,4 +64,5 @@ public class CommandCallImpl(
override val caller: CommandSender,
override val calleeName: String,
override val valueArguments: List<CommandValueArgument>,
override val originalMessage: MessageChain,
) : CommandCall

View File

@ -35,7 +35,8 @@ public object SpaceSeparatedCommandCallParser : CommandCallParser {
return CommandCallImpl(
caller = caller,
calleeName = flatten.first().content,
valueArguments = flatten.drop(1).map(::DefaultCommandValueArgument)
valueArguments = flatten.drop(1).map(::DefaultCommandValueArgument),
originalMessage = message
)
}
}

View File

@ -52,7 +52,8 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
callee,
signature.signature,
signature.zippedArguments.map { it.second },
context ?: EmptyCommandArgumentContext
context ?: EmptyCommandArgumentContext,
call.originalMessage,
)
)
}
@ -101,14 +102,24 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
): ResolveData? {
val signature = this
val receiverParameter = signature.receiverParameter
if (receiverParameter?.type?.classifierAsKClass()?.isInstance(caller) == false) {
errorSink.reportUnmatched(
UnmatchedCommandSignature(
signature,
FailureReason.InapplicableReceiverArgument(receiverParameter, caller)
)
)// not compatible receiver
return null
if (receiverParameter != null) {
when (receiverParameter) {
is CommandReceiverParameter.Context -> {
// accepts any sender
}
is CommandReceiverParameter.Sender -> {
if (!receiverParameter.type.classifierAsKClass().isInstance(caller)) {
errorSink.reportUnmatched(
UnmatchedCommandSignature(
signature,
FailureReason.InapplicableReceiverArgument(receiverParameter, caller)
)
)// not compatible receiver
return null
}
}
}
}
val valueParameters = signature.valueParameters

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
@ -20,6 +20,7 @@ import net.mamoe.mirai.console.command.parse.mapToTypeOrNull
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.cast
import net.mamoe.mirai.message.data.MessageChain
/**
* The resolved [CommandCall].
@ -60,6 +61,11 @@ public interface ResolvedCommandCall {
@ConsoleExperimentalApi
public val resolvedValueArguments: List<ResolvedCommandValueArgument<*>>
/**
* @since 2.12
*/
public val originalMessage: MessageChain
public companion object
}
@ -94,6 +100,7 @@ public class ResolvedCommandCallImpl(
override val calleeSignature: CommandSignature,
override val rawValueArguments: List<CommandValueArgument>,
private val context: CommandArgumentContext,
override val originalMessage: MessageChain,
) : ResolvedCommandCall {
override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy {
calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) ->

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
@ -19,10 +19,7 @@ import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.buildMessageChain
import net.mamoe.mirai.utils.runBIO
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.KVisibility
import kotlin.reflect.*
import kotlin.reflect.full.*
@ -130,7 +127,7 @@ public class IllegalCommandDeclarationException : Exception {
@OptIn(ExperimentalCommandDescriptors::class)
internal class CommandReflector(
val command: Command,
val annotationResolver: SubCommandAnnotationResolver,
private val annotationResolver: SubCommandAnnotationResolver,
) {
@Suppress("NOTHING_TO_INLINE")
@ -143,8 +140,11 @@ internal class CommandReflector(
private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.hasAnnotation(command, this)
private fun KFunction<*>.checkExtensionReceiver() {
this.extensionReceiverParameter?.let { receiver ->
if (receiver.type.classifierAsKClassOrNull()?.isSubclassOf(CommandSender::class) != true) {
illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender.")
val classifier = receiver.type.classifierAsKClassOrNull()
if (classifier != null) {
if (!classifier.isSubclassOf(CommandSender::class) && !classifier.isSubclassOf(CommandContext::class)) {
illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender nor CommandContext.")
}
}
}
}
@ -279,8 +279,8 @@ internal class CommandReflector(
var receiverParameter = function.extensionReceiverParameter
if (receiverParameter == null && valueParameters.isNotEmpty()) {
val valueFirstParameter = valueParameters[0]
if (valueFirstParameter.type.classifierAsKClassOrNull()
?.isSubclassOf(CommandSender::class) == true
val classifier = valueFirstParameter.type.classifierAsKClassOrNull()
if (classifier != null && isAcceptableReceiverType(classifier)
) {
receiverParameter = valueFirstParameter
valueParameters.removeAt(0)
@ -317,14 +317,22 @@ internal class CommandReflector(
}
if (receiverParameter != null) {
check(receiverParameter.type.classifierAsKClass().isInstance(call.caller)) {
"Bad command call resolved. " +
"Function expects receiver parameter ${receiverParameter.type} whereas actual is ${call.caller::class}."
val receiverType = receiverParameter.type.classifierAsKClass()
if (receiverType.isSubclassOf(CommandContext::class)) {
args[receiverParameter] = CommandContextImpl(call.caller, call.originalMessage)
} else {
check(receiverType.isInstance(call.caller)) {
"Bad command call resolved. " +
"Function expects receiver parameter ${receiverParameter.type} whereas actual is ${call.caller::class}."
}
args[receiverParameter] = call.caller
}
args[receiverParameter] = call.caller
}
// #341
// mirai-console#341
if (function.isSuspend) {
function.callSuspendBy(args)
} else {
@ -334,18 +342,29 @@ internal class CommandReflector(
}.toList()
}
private fun isAcceptableReceiverType(classifier: KClass<Any>) =
classifier.isSubclassOf(CommandSender::class) || classifier.isSubclassOf(CommandContext::class)
@Suppress("SameParameterValue")
private fun <K, V> createMapEntry(key: K, value: V) = object : Map.Entry<K, V> {
override val key: K get() = key
override val value: V get() = value
}
private fun KParameter.toCommandReceiverParameter(): CommandReceiverParameter<out CommandSender> {
private fun KParameter.toCommandReceiverParameter(): CommandReceiverParameter<*> {
check(!this.isVararg) { "Receiver cannot be vararg." }
check(
this.type.classifierAsKClass().isSubclassOf(CommandSender::class)
) { "Receiver must be subclass of CommandSender" }
return CommandReceiverParameter(this.type.isMarkedNullable, this.type)
val classifier = type.classifierAsKClass()
return when {
classifier.isSubclassOf(CommandSender::class) -> {
CommandReceiverParameter.Sender(this.type.isMarkedNullable, this.type)
}
classifier.isSubclassOf(CommandContext::class) -> {
CommandReceiverParameter.Context(this.type.isMarkedNullable, this.type)
}
else -> {
throw IllegalArgumentException("Receiver must be subclass of CommandSender or CommandContext")
}
}
}
private fun createStringConstantParameterForName(

View File

@ -16,4 +16,8 @@ import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest
internal abstract class AbstractCommandTest : AbstractConsoleInstanceTest() {
val dataScope get() = DataScope as ConsoleDataScopeImpl
val consoleSender get() = ConsoleCommandSender
open val sender: CommandSender get() = ConsoleCommandSender
open val owner: CommandOwner get() = ConsoleCommandOwner
}

View File

@ -0,0 +1,263 @@
/*
* Copyright 2019-2022 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/dev/LICENSE
*/
@file:OptIn(ExperimentalCommandDescriptors::class)
@file:Suppress("unused", "UNUSED_PARAMETER")
package net.mamoe.mirai.console.command
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.Testing
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.java.JCompositeCommand
import net.mamoe.mirai.console.command.java.JRawCommand
import net.mamoe.mirai.console.command.java.JSimpleCommand
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.safeCast
import org.apache.commons.lang3.ArrayUtils.isSameType
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.TestFactory
import kotlin.test.assertEquals
internal class CommandContextTest : AbstractCommandTest() {
private class MyMetadata : MessageMetadata {
override fun hashCode(): Int = javaClass.hashCode()
override fun equals(other: Any?): Boolean = isSameType(this, other)
override fun toString(): String = "MyMetadata"
companion object Key : AbstractMessageKey<MyMetadata>({ it.safeCast() })
}
///////////////////////////////////////////////////////////////////////////
// RawCommand
///////////////////////////////////////////////////////////////////////////
@TestFactory
fun `can execute with sender`(): List<DynamicTest> {
return listOf(
object : RawCommand(owner, "test") {
override suspend fun CommandContext.onCommand(args: MessageChain) {
Testing.ok(args)
}
} to "/test foo",
object : SimpleCommand(owner, "test") {
@Handler
fun CommandContext.foo(arg: MessageChain) {
Testing.ok(arg)
}
} to "/test foo",
object : CompositeCommand(owner, "test") {
@SubCommand
fun CommandContext.sub(arg: MessageChain) {
Testing.ok(arg)
}
} to "/test sub foo",
object : JRawCommand(owner, "test") {
override fun onCommand(context: CommandContext, args: MessageChain) {
Testing.ok(args)
}
} to "/test foo",
object : JSimpleCommand(owner, "test") {
@Handler
fun foo(context: CommandContext, arg: MessageChain) {
Testing.ok(arg)
}
} to "/test foo",
object : JCompositeCommand(owner, "test") {
@SubCommand
fun sub(context: CommandContext, arg: MessageChain) {
Testing.ok(arg)
}
} to "/test sub foo",
).map { (instance, cmd) ->
DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) {
runBlocking {
instance.withRegistration {
assertEquals(
messageChainOf(PlainText("foo")),
Testing.withTesting {
assertSuccess(sender.executeCommand(cmd, checkPermission = false))
}
)
}
}
}
}
}
@TestFactory
fun `RawCommand can execute and get original chain`(): List<DynamicTest> {
return listOf(
object : RawCommand(owner, "test") {
override suspend fun CommandContext.onCommand(args: MessageChain) {
Testing.ok(originalMessage)
}
} to "/test foo",
object : SimpleCommand(owner, "test") {
@Handler
fun CommandContext.foo(arg: MessageChain) {
Testing.ok(originalMessage)
}
} to "/test foo",
object : CompositeCommand(owner, "test") {
@SubCommand
fun CommandContext.sub(arg: MessageChain) {
Testing.ok(originalMessage)
}
} to "/test sub foo",
object : JRawCommand(owner, "test") {
override fun onCommand(context: CommandContext, args: MessageChain) {
Testing.ok(context.originalMessage)
}
} to "/test foo",
object : JSimpleCommand(owner, "test") {
@Handler
fun foo(context: CommandContext, arg: MessageChain) {
Testing.ok(context.originalMessage)
}
} to "/test foo",
object : JCompositeCommand(owner, "test") {
@SubCommand
fun sub(context: CommandContext, arg: MessageChain) {
Testing.ok(context.originalMessage)
}
} to "/test sub foo",
).map { (instance, cmd) ->
DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) {
runBlocking {
instance.withRegistration {
assertEquals(
cmd,
Testing.withTesting<MessageChain> {
assertSuccess(sender.executeCommand(cmd, checkPermission = false))
}.contentToString()
)
}
}
}
}
}
@TestFactory
fun `can execute and get metadata`(): List<DynamicTest> {
val metadata = MyMetadata()
return listOf(
object : RawCommand(owner, "test") {
override suspend fun CommandContext.onCommand(args: MessageChain) {
Testing.ok(originalMessage[MyMetadata])
}
} to messageChainOf(PlainText("/test"), metadata, PlainText("foo")),
object : SimpleCommand(owner, "test") {
@Handler
fun CommandContext.foo(arg: MessageChain) {
Testing.ok(originalMessage[MyMetadata])
}
} to messageChainOf(PlainText("/test"), metadata, PlainText("foo")),
object : CompositeCommand(owner, "test") {
@SubCommand
fun CommandContext.sub(arg: MessageChain) {
Testing.ok(originalMessage[MyMetadata])
}
} to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")),
object : JRawCommand(owner, "test") {
override fun onCommand(context: CommandContext, args: MessageChain) {
Testing.ok(context.originalMessage[MyMetadata])
}
} to messageChainOf(PlainText("/test"), metadata, PlainText("foo")),
object : JSimpleCommand(owner, "test") {
@Handler
fun foo(context: CommandContext, arg: MessageChain) {
Testing.ok(context.originalMessage[MyMetadata])
}
} to messageChainOf(PlainText("/test"), metadata, PlainText("foo")),
object : JCompositeCommand(owner, "test") {
@SubCommand
fun sub(context: CommandContext, arg: MessageChain) {
Testing.ok(context.originalMessage[MyMetadata])
}
} to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")),
).map { (instance, cmd) ->
DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) {
runBlocking {
instance.withRegistration {
assertEquals(
metadata,
Testing.withTesting {
assertSuccess(CommandManager.executeCommand(sender, cmd, checkPermission = false))
}
)
}
}
}
}
}
@TestFactory
fun `RawCommand can execute and get chain including metadata`(): List<DynamicTest> {
val metadata = MyMetadata()
return listOf(
object : RawCommand(owner, "test") {
override suspend fun CommandContext.onCommand(args: MessageChain) {
Testing.ok(originalMessage)
}
} to messageChainOf(PlainText("/test"), metadata, PlainText("foo")),
object : SimpleCommand(owner, "test") {
@Handler
fun CommandContext.foo(arg: MessageChain) {
Testing.ok(originalMessage)
}
} to messageChainOf(PlainText("/test"), metadata, PlainText("foo")),
object : CompositeCommand(owner, "test") {
@SubCommand
fun CommandContext.sub(arg: MessageChain) {
Testing.ok(originalMessage)
}
} to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")),
object : JRawCommand(owner, "test") {
override fun onCommand(context: CommandContext, args: MessageChain) {
Testing.ok(context.originalMessage)
}
} to messageChainOf(PlainText("/test"), metadata, PlainText("foo")),
object : JSimpleCommand(owner, "test") {
@Handler
fun foo(context: CommandContext, arg: MessageChain) {
Testing.ok(context.originalMessage)
}
} to messageChainOf(PlainText("/test"), metadata, PlainText("foo")),
object : JCompositeCommand(owner, "test") {
@SubCommand
fun sub(context: CommandContext, arg: MessageChain) {
Testing.ok(context.originalMessage)
}
} to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")),
).map { (instance, cmd) ->
DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) {
runBlocking {
instance.withRegistration {
assertEquals(
cmd,
Testing.withTesting {
assertSuccess(CommandManager.executeCommand(sender, cmd, checkPermission = false))
}
)
}
}
}
}
}
}

View File

@ -156,7 +156,7 @@ class TestTemporalArgCommand : CompositeCommand(owner, "testtemporal") {
}
private val sender get() = ConsoleCommandSender
internal val owner get() = ConsoleCommandOwner
private val owner get() = ConsoleCommandOwner
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
@OptIn(ExperimentalCommandDescriptors::class)