Use MessageChain for arguments for executing commands

This commit is contained in:
Him188 2020-09-04 11:44:56 +08:00
parent be9b0293bd
commit 4566403ece
10 changed files with 52 additions and 46 deletions

View File

@ -16,6 +16,7 @@ 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.register
import net.mamoe.mirai.console.command.java.JCommand import net.mamoe.mirai.console.command.java.JCommand
import net.mamoe.mirai.console.internal.command.isValidSubName import net.mamoe.mirai.console.internal.command.isValidSubName
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.SingleMessage
/** /**
@ -71,7 +72,7 @@ public interface Command {
* @see CommandManager.executeCommand 查看更多信息 * @see CommandManager.executeCommand 查看更多信息
*/ */
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun CommandSender.onCommand(args: Array<out Any>) public suspend fun CommandSender.onCommand(args: MessageChain)
public companion object { public companion object {
/** /**
@ -84,7 +85,7 @@ public interface Command {
} }
@JvmSynthetic @JvmSynthetic
public suspend inline fun Command.onCommand(sender: CommandSender, args: Array<out Any>): Unit = public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit =
sender.run { onCommand(args) } sender.run { onCommand(args) }
/** /**

View File

@ -13,6 +13,7 @@ package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import kotlin.contracts.contract import kotlin.contracts.contract
/** /**
@ -34,7 +35,7 @@ public sealed class CommandExecuteResult {
public abstract val commandName: String? public abstract val commandName: String?
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ /** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
public abstract val args: Array<out Any>? public abstract val args: MessageChain?
// abstract val to allow smart casting // abstract val to allow smart casting
@ -45,7 +46,7 @@ public sealed class CommandExecuteResult {
/** 尝试执行的指令名 */ /** 尝试执行的指令名 */
public override val commandName: String, public override val commandName: String,
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ /** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
public override val args: Array<out Any> public override val args: MessageChain
) : CommandExecuteResult() { ) : CommandExecuteResult() {
/** 指令执行时发生的错误, 总是 `null` */ /** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null public override val exception: Nothing? get() = null
@ -63,7 +64,7 @@ public sealed class CommandExecuteResult {
/** 尝试执行的指令名 */ /** 尝试执行的指令名 */
public override val commandName: String, public override val commandName: String,
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ /** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
public override val args: Array<out Any> public override val args: MessageChain
) : CommandExecuteResult() { ) : CommandExecuteResult() {
/** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */ /** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.EXECUTION_EXCEPTION public override val status: CommandExecuteStatus get() = CommandExecuteStatus.EXECUTION_EXCEPTION

View File

@ -21,6 +21,7 @@ import net.mamoe.mirai.console.command.description.*
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.message.data.MessageChain
import kotlin.annotation.AnnotationRetention.RUNTIME import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.FUNCTION import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -116,14 +117,14 @@ public abstract class CompositeCommand(
@Target(AnnotationTarget.VALUE_PARAMETER) @Target(AnnotationTarget.VALUE_PARAMETER)
protected annotation class Name(val value: String) protected annotation class Name(val value: String)
public final override suspend fun CommandSender.onCommand(args: Array<out Any>) { public final override suspend fun CommandSender.onCommand(args: MessageChain) {
matchSubCommand(args)?.parseAndExecute(this, args, true) ?: kotlin.run { matchSubCommand(args)?.parseAndExecute(this, args, true) ?: kotlin.run {
defaultSubCommand.onCommand(this, args) defaultSubCommand.onCommand(this, args)
} }
} }
protected override suspend fun CommandSender.onDefault(rawArgs: Array<out Any>) { protected override suspend fun CommandSender.onDefault(rawArgs: MessageChain) {
sendMessage(usage) sendMessage(usage)
} }

View File

@ -14,7 +14,7 @@ package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute 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.executeCommand
import net.mamoe.mirai.console.command.java.JRawCommand import net.mamoe.mirai.console.command.java.JRawCommand
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.MessageChain
/** /**
* 无参数解析, 接收原生参数的指令. * 无参数解析, 接收原生参数的指令.
@ -47,11 +47,11 @@ public abstract class RawCommand(
/** /**
* 在指令被执行时调用. * 在指令被执行时调用.
* *
* @param args 指令参数. 数组元素类型可能是 [SingleMessage] [String]. 且已经以 ' ' 分割. * @param args 指令参数.
* *
* @see CommandManager.execute 查看更多信息 * @see CommandManager.execute 查看更多信息
*/ */
public abstract override suspend fun CommandSender.onCommand(args: Array<out Any>) public abstract override suspend fun CommandSender.onCommand(args: MessageChain)
} }

View File

@ -22,6 +22,7 @@ import net.mamoe.mirai.console.command.description.*
import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.command.java.JSimpleCommand
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
import net.mamoe.mirai.message.data.MessageChain
/** /**
* 简单的, 支持参数自动解析的指令. * 简单的, 支持参数自动解析的指令.
@ -71,7 +72,7 @@ public abstract class SimpleCommand(
*/ */
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
public final override suspend fun CommandSender.onCommand(args: Array<out Any>) { public final override suspend fun CommandSender.onCommand(args: MessageChain) {
subCommands.single().parseAndExecute(this, args, false) subCommands.single().parseAndExecute(this, args, false)
} }
@ -81,7 +82,7 @@ public abstract class SimpleCommand(
} }
@Deprecated("prohibited", level = DeprecationLevel.HIDDEN) @Deprecated("prohibited", level = DeprecationLevel.HIDDEN)
internal override suspend fun CommandSender.onDefault(rawArgs: Array<out Any>) { internal override suspend fun CommandSender.onDefault(rawArgs: MessageChain) {
sendMessage(usage) sendMessage(usage)
} }

View File

@ -15,6 +15,7 @@ import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.SingleMessage
/** /**
@ -25,7 +26,7 @@ import net.mamoe.mirai.message.data.SingleMessage
* @see Command * @see Command
*/ */
public interface JCommand : Command { public interface JCommand : Command {
public override suspend fun CommandSender.onCommand(args: Array<out Any>) { public override suspend fun CommandSender.onCommand(args: MessageChain) {
withContext(Dispatchers.IO) { onCommand(this@onCommand, args) } withContext(Dispatchers.IO) { onCommand(this@onCommand, args) }
} }
@ -36,5 +37,5 @@ public interface JCommand : Command {
* *
* @see CommandManager.executeCommand 查看更多信息 * @see CommandManager.executeCommand 查看更多信息
*/ */
public fun onCommand(sender: CommandSender, args: Array<out Any>) // overrides bridge public fun onCommand(sender: CommandSender, args: MessageChain) // overrides bridge
} }

View File

@ -11,10 +11,10 @@ package net.mamoe.mirai.console.command.java
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
import net.mamoe.mirai.console.command.CommandPermission import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.message.data.SingleMessage
/** /**
* Java 用户继承 * Java 用户继承
@ -76,9 +76,9 @@ public abstract class JRawCommand(
*/ */
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("onCommand") @JvmName("onCommand")
public abstract fun onCommand(sender: CommandSender, args: Array<out Any>) public abstract fun onCommand(sender: CommandSender, args: MessageChain)
public final override suspend fun CommandSender.onCommand(args: Array<out Any>) { public final override suspend fun CommandSender.onCommand(args: MessageChain) {
withContext(Dispatchers.IO) { onCommand(this@onCommand, args) } withContext(Dispatchers.IO) { onCommand(this@onCommand, args) }
} }
} }

View File

@ -147,7 +147,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
): CommandExecuteResult { ): CommandExecuteResult {
return sender.executeCommandInternal( return sender.executeCommandInternal(
this, this,
arguments.flattenCommandComponents().toTypedArray(), arguments.flattenCommandComponents(),
primaryName, primaryName,
checkPermission checkPermission
) )
@ -160,7 +160,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
): CommandExecuteResult { ): CommandExecuteResult {
return sender.executeCommandInternal( return sender.executeCommandInternal(
this, this,
arguments.flattenCommandComponents().toTypedArray(), arguments.flattenCommandComponents(),
primaryName, primaryName,
checkPermission checkPermission
) )

View File

@ -15,8 +15,10 @@ import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.description.CommandArgumentContext import net.mamoe.mirai.console.command.description.CommandArgumentContext
import net.mamoe.mirai.console.command.description.CommandArgumentContextAware import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.buildMessageChain
import kotlin.reflect.KAnnotatedElement import kotlin.reflect.KAnnotatedElement
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KFunction import kotlin.reflect.KFunction
@ -65,13 +67,13 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
return _usage return _usage
} }
abstract suspend fun CommandSender.onDefault(rawArgs: Array<out Any>) abstract suspend fun CommandSender.onDefault(rawArgs: MessageChain)
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy { internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
DefaultSubCommandDescriptor( DefaultSubCommandDescriptor(
"", "",
permission, permission,
onCommand = { sender: CommandSender, args: Array<out Any> -> onCommand = { sender: CommandSender, args: MessageChain ->
sender.onDefault(args) sender.onDefault(args)
} }
) )
@ -116,7 +118,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
internal class DefaultSubCommandDescriptor( internal class DefaultSubCommandDescriptor(
val description: String, val description: String,
val permission: CommandPermission, val permission: CommandPermission,
val onCommand: suspend (sender: CommandSender, rawArgs: Array<out Any>) -> Unit val onCommand: suspend (sender: CommandSender, rawArgs: MessageChain) -> Unit
) )
internal inner class SubCommandDescriptor( internal inner class SubCommandDescriptor(
@ -130,7 +132,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
val usage: String = createUsage(this@AbstractReflectionCommand) val usage: String = createUsage(this@AbstractReflectionCommand)
internal suspend fun parseAndExecute( internal suspend fun parseAndExecute(
sender: CommandSender, sender: CommandSender,
argsWithSubCommandNameNotRemoved: Array<out Any>, argsWithSubCommandNameNotRemoved: MessageChain,
removeSubName: Boolean removeSubName: Boolean
) { ) {
val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) names.size else 0) val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) names.size else 0)
@ -145,7 +147,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
@JvmField @JvmField
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray() internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
private fun parseArgs(sender: CommandSender, rawArgs: Array<out Any>, offset: Int): Array<out Any>? { private fun parseArgs(sender: CommandSender, rawArgs: MessageChain, offset: Int): Array<out Any>? {
if (rawArgs.size < offset + this.params.size) if (rawArgs.size < offset + this.params.size)
return null return null
//require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" } //require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
@ -154,9 +156,8 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
val param = params[index] val param = params[index]
val rawArg = rawArgs[offset + index] val rawArg = rawArgs[offset + index]
when (rawArg) { when (rawArg) {
is String -> context[param.type]?.parse(rawArg, sender) is PlainText -> context[param.type]?.parse(rawArg.content, sender)
is SingleMessage -> context[param.type]?.parse(rawArg, sender) else -> context[param.type]?.parse(rawArg, sender)
else -> throw IllegalArgumentException("Illegal argument type: ${rawArg::class.qualifiedName}")
} ?: error("Cannot find a parser for $rawArg") } ?: error("Cannot find a parser for $rawArg")
} }
} }
@ -165,7 +166,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
/** /**
* @param rawArgs 元素类型必须为 [SingleMessage] [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException] * @param rawArgs 元素类型必须为 [SingleMessage] [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException]
*/ */
internal fun matchSubCommand(rawArgs: Array<out Any>): SubCommandDescriptor? { internal fun matchSubCommand(rawArgs: MessageChain): SubCommandDescriptor? {
val maxCount = rawArgs.size val maxCount = rawArgs.size
var cur = 0 var cur = 0
bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) -> bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) ->
@ -180,7 +181,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
} }
} }
internal fun <T> Array<T>.contentEqualsOffset(other: Array<out Any>, length: Int): Boolean { internal fun <T> Array<T>.contentEqualsOffset(other: MessageChain, length: Int): Boolean {
repeat(length) { index -> repeat(length) { index ->
if (!other[index].toString().equals(this[index].toString(), ignoreCase = true)) { if (!other[index].toString().equals(this[index].toString(), ignoreCase = true)) {
return false return false
@ -193,18 +194,16 @@ internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toChar
internal fun String.isValidSubName(): Boolean = ILLEGAL_SUB_NAME_CHARS.none { it in this } internal fun String.isValidSubName(): Boolean = ILLEGAL_SUB_NAME_CHARS.none { it in this }
internal fun String.bakeSubName(): Array<String> = split(' ').filterNot { it.isBlank() }.toTypedArray() internal fun String.bakeSubName(): Array<String> = split(' ').filterNot { it.isBlank() }.toTypedArray()
internal fun Any.flattenCommandComponents(): ArrayList<Any> { internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain {
val list = ArrayList<Any>() when (this@flattenCommandComponents) {
when (this) { is PlainText -> this@flattenCommandComponents.content.splitToSequence(' ').filterNot { it.isBlank() }
is PlainText -> this.content.splitToSequence(' ').filterNot { it.isBlank() } .forEach { +it }
.forEach { list.add(it) } is CharSequence -> this@flattenCommandComponents.splitToSequence(' ').filterNot { it.isBlank() }.forEach { +it }
is CharSequence -> this.splitToSequence(' ').filterNot { it.isBlank() }.forEach { list.add(it) } is SingleMessage -> +(this@flattenCommandComponents)
is SingleMessage -> list.add(this) is Array<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
is Array<*> -> this.forEach { if (it != null) list.addAll(it.flattenCommandComponents()) } is Iterable<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
is Iterable<*> -> this.forEach { if (it != null) list.addAll(it.flattenCommandComponents()) } else -> add(this@flattenCommandComponents.toString())
else -> list.add(this.toString())
} }
return list
} }
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean = internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =

View File

@ -12,6 +12,8 @@ package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.asMessageChain
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -144,7 +146,7 @@ internal inline fun <reified T> List<T>.dropToTypedArray(n: Int): Array<T> = Arr
@Throws(CommandExecutionException::class) @Throws(CommandExecutionException::class)
internal suspend fun CommandSender.executeCommandInternal( internal suspend fun CommandSender.executeCommandInternal(
command: Command, command: Command,
args: Array<out Any>, args: MessageChain,
commandName: String, commandName: String,
checkPermission: Boolean checkPermission: Boolean
): CommandExecuteResult { ): CommandExecuteResult {
@ -182,7 +184,7 @@ internal suspend fun CommandSender.executeCommandInternal(
): CommandExecuteResult { ): CommandExecuteResult {
val command = val command =
CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName) CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
val args = messages.flattenCommandComponents().dropToTypedArray(1) val args = messages.flattenCommandComponents()
return executeCommandInternal(command, args, commandName, checkPermission) return executeCommandInternal(command, args.drop(1).asMessageChain(), commandName, checkPermission)
} }