Documentations and several improvements

This commit is contained in:
Him188 2020-08-23 02:25:42 +08:00
parent 0a7097b354
commit 775b888273
17 changed files with 506 additions and 79 deletions

View File

@ -16,8 +16,9 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.CommandManagerImpl.allRegisteredCommands
import net.mamoe.mirai.console.command.CommandManagerImpl.register
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.message.nextMessageOrNull

View File

@ -12,7 +12,7 @@
package net.mamoe.mirai.console.command
import net.mamoe.kjbb.JvmBlockingBridge
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.internal.command.isValidSubName
import net.mamoe.mirai.message.data.SingleMessage
@ -53,14 +53,17 @@ public interface Command {
public val prefixOptional: Boolean
/**
* 指令拥有者, 对于插件的指令通常是 [PluginCommandOwner]
* 指令拥有者.
* @see CommandOwner
*/
public val owner: CommandOwner
/**
* 在指令被执行时调用.
*
* @param args 指令参数. 数组元素类型可能是 [SingleMessage] [String]. 且已经以 ' ' 分割.
*
* @see CommandManager.execute
* @see CommandManager.executeCommand 查看更多信息
*/
@JvmBlockingBridge
public suspend fun CommandSender.onCommand(args: Array<out Any>)
@ -81,16 +84,23 @@ public suspend inline fun Command.onCommand(sender: CommandSender, args: Array<o
/**
* [Command] 的基础实现
*
* @see SimpleCommand
* @see CompositeCommand
* @see RawCommand
*/
public abstract class AbstractCommand @JvmOverloads constructor(
public final override val owner: CommandOwner,
/** 指令拥有者. */
public override val owner: CommandOwner,
vararg names: String,
description: String = "<no description available>",
public final override val permission: CommandPermission = CommandPermission.Default,
public final override val prefixOptional: Boolean = false
/** 指令权限 */
public override val permission: CommandPermission = CommandPermission.Default,
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
public override val prefixOptional: Boolean = false
) : Command {
public final override val description: String = description.trimIndent()
public final override val names: Array<out String> =
public override val description: String = description.trimIndent()
public override val names: Array<out String> =
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list ->
list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") }
}.toTypedArray()

View File

@ -18,6 +18,10 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
* [CommandManager.executeCommand] , [Command.onCommand] 抛出异常时包装的异常.
*/
public class CommandExecutionException(
/**
* 执行者
*/
public val sender: CommandSender,
/**
* 执行过程发生异常的指令
*/

View File

@ -16,9 +16,15 @@
package net.mamoe.mirai.console.command
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.SingleMessage
/**
* 指令管理器
*/
public interface CommandManager {
/**
* 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
@ -79,6 +85,7 @@ public interface CommandManager {
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
* @see executeCommand
*/
@JvmBlockingBridge
@Throws(CommandExecutionException::class)
@ -89,6 +96,7 @@ public interface CommandManager {
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
* @see executeCommand
*/
@JvmBlockingBridge
@Throws(CommandExecutionException::class)
@ -97,6 +105,16 @@ public interface CommandManager {
/**
* 解析并执行一个指令, 获取详细的指令参数等信息
*
* 执行过程中产生的异常将不会直接抛出, 而会包装为 [CommandExecuteResult.ExecutionFailed]
*
* ### 指令解析流程
* 1. [messages] 的第一个消息元素的 [内容][Message.contentToString] 被作为指令名, 在已注册指令列表中搜索. (包含 [Command.prefixOptional] 相关的处理)
* 2. 参数语法分析.
* 在当前的实现下, [messages] 被以空格和 [SingleMessage] 分割.
* "MessageChain("foo bar", [Image], " test")" 被分割为 "foo", "bar", [Image], "test".
* 注意: 字符串与消息元素之间不需要空格, 会被强制分割. "bar[mirai:image:]" 会被分割为 "bar" [Image] 类型的消息元素.
* 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand]
*
* @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString]
*
* @return 执行结果

View File

@ -18,6 +18,10 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
* 总是作为 [CommandExecutionException.cause].
*/
public class CommandPermissionDeniedException(
/**
* 执行者
*/
public val commandSender: CommandSender,
/**
* 执行过程发生异常的指令
*/

View File

@ -24,10 +24,34 @@ import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.PlainText
/**
* 指令发送者
* 指令发送者.
*
* ### 获得指令发送者
* - [MessageEvent.toCommandSender]
* - [FriendMessageEvent.toCommandSender]
* - [GroupMessageEvent.toCommandSender]
* - [TempMessageEvent.toCommandSender]
*
* - [Member.asCommandSender]
* - [Friend.asCommandSender]
* - [User.asCommandSender]
*
* ### 子类型
*
* 当真实收到由用户执行的指令时:
* - 若用户在群内指令执行, 对应 [CommandSender] [MemberCommandSenderOnMessage]
* - 若用户在私聊环境内指令执行, 对应 [CommandSender] [FriendCommandSenderOnMessage]
* - 若用户在临时会话内指令执行, 对应 [CommandSender] [TempCommandSenderOnMessage]
*
* 当指令由其他插件主动执行时, 插件应使用 [toCommandSender] [asCommandSender], 因此
* - 若用户在群内指令执行, 对应 [CommandSender] [MemberCommandSender]
* - 若用户在私聊环境内指令执行, 对应 [CommandSender] [FriendCommandSender]
* - 若用户在临时会话内指令执行, 对应 [CommandSender] [TempCommandSender]
*
* @see ConsoleCommandSender 控制台
* @see UserCommandSender [User] ([群成员][Member], [好友][Friend])
* @see toCommandSender
* @see asCommandSender
*/
@Suppress("FunctionName")
public interface CommandSender {
@ -109,10 +133,16 @@ public abstract class ConsoleCommandSender internal constructor() : CommandSende
}
}
@ConsoleExperimentalAPI
public fun FriendMessageEvent.toCommandSender(): FriendCommandSenderOnMessage = FriendCommandSenderOnMessage(this)
@ConsoleExperimentalAPI
public fun GroupMessageEvent.toCommandSender(): MemberCommandSenderOnMessage = MemberCommandSenderOnMessage(this)
@ConsoleExperimentalAPI
public fun TempMessageEvent.toCommandSender(): TempCommandSenderOnMessage = TempCommandSenderOnMessage(this)
@ConsoleExperimentalAPI
public fun MessageEvent.toCommandSender(): CommandSenderOnMessage<*> = when (this) {
is FriendMessageEvent -> toCommandSender()
is GroupMessageEvent -> toCommandSender()
@ -120,9 +150,13 @@ public fun MessageEvent.toCommandSender(): CommandSenderOnMessage<*> = when (thi
else -> throw IllegalArgumentException("unsupported MessageEvent: ${this::class.qualifiedNameOrTip}")
}
@ConsoleExperimentalAPI
public fun Member.asCommandSender(): MemberCommandSender = MemberCommandSender(this)
@ConsoleExperimentalAPI
public fun Friend.asCommandSender(): FriendCommandSender = FriendCommandSender(this)
@ConsoleExperimentalAPI
public fun User.asCommandSender(): UserCommandSender {
return when (this) {
is Friend -> this.asCommandSender()

View File

@ -26,7 +26,51 @@ import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.reflect.KClass
/**
* 复合指令.
* 复合指令. 指令注册时候会通过反射构造指令解析器.
*
* 示例:
* ```
* @OptIn(ConsoleExperimentalAPI::class)
* object MyCompositeCommand : CompositeCommand(
* MyPluginMain, "manage", // "manage" 是主指令名
* description = "示例指令", permission = MyCustomPermission,
* // prefixOptional = true // 还有更多参数可填, 此处忽略
* ) {
*
* // [参数智能解析]
* //
* // 在控制台执行 "/manage <群号>.<群员> <持续时间>",
* // 或在聊天群内发送 "/manage <@一个群员> <持续时间>",
* // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>",
* // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>"
* // 时调用这个函数
* @SubCommand
* suspend fun CommandSender.mute(target: Member, duration: Int) { // 通过 /manage mute <target> <duration> 调用
* sendMessage("/manage mute 被调用了, 参数为: $target, $duration")
*
* val result = kotlin.runCatching {
* target.mute(duration).toString()
* }.getOrElse {
* it.stackTraceToString()
* } // 失败时返回堆栈信息
*
* sendMessage("结果: $result")
* }
*
* @SubCommand
* suspend fun CommandSender.list() { // 执行 "/manage list" 时调用这个函数
* sendMessage("/manage list 被调用了")
* }
*
* // 支持 Image 类型, 需在聊天中执行此指令.
* @SubCommand
* suspend fun CommandSender.test(image: Image) { // 执行 "/manage test <一张图片>" 时调用这个函数
* sendMessage("/manage image 被调用了, 图片是 ${image.imageId}")
* }
* }
* ```
*
* @see buildCommandArgumentContext
*/
@ConsoleExperimentalAPI
public abstract class CompositeCommand @JvmOverloads constructor(
@ -80,3 +124,73 @@ public abstract class CompositeCommand @JvmOverloads constructor(
internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver
get() = CompositeCommandSubCommandAnnotationResolver
}
/**
* 复合指令. 指令注册时候会通过反射构造指令解析器.
*
* 示例:
* ```
* public final class MyCompositeCommand extends CompositeCommand {
* public static final MyCompositeCommand INSTANCE = new MyCompositeCommand();
*
* public MyCompositeCommand() {
* super(MyPluginMain.INSTANCE, "manage") // "manage" 是主指令名
* }
*
* // [参数智能解析]
* //
* //
* // 在控制台执行 "/manage <群号>.<群员> <持续时间>",
* // 或在聊天群内发送 "/manage <@一个群员> <持续时间>",
* // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>",
* // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>"
* // 时调用这个函数
* @SubCommand
* public void mute(CommandSender sender, Member target, int duration) { // 通过 /manage mute <target> <duration> 调用.
* sender.sendMessage("/manage mute 被调用了, 参数为: " + target + ", " + duration);
*
*
* String result;
* try {
* result = target.mute(duration).toString();
* } catch(Exception e) {
* result = ExceptionsKt.stackTraceToString(e);
* }
*
* sender.sendMessage("结果: " + result)
* }
*
* @SubCommand
* public void list(CommandSender sender) { // 执行 "/manage list" 时调用这个方法
* sender.sendMessage("/manage list 被调用了")
* }
*
* // 支持 Image 类型, 需在聊天中执行此指令.
* @SubCommand
* public void test(CommandSender sender, Image image) { // 执行 "/manage test <一张图片>" 时调用这个方法
* sender.sendMessage("/manage image 被调用了, 图片是 " + image.imageId)
* }
* }
* ```
*
* @see buildCommandArgumentContext
*/
@ConsoleExperimentalAPI
public abstract class JCompositeCommand(
owner: CommandOwner,
vararg names: String
) : CompositeCommand(owner, *names) {
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
public final override var description: String = "<no descriptions given>"
protected set
/** 指令权限 */
public final override var permission: CommandPermission = CommandPermission.Default
protected set
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
public final override var prefixOptional: Boolean = false
protected set
}

View File

@ -7,18 +7,118 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.command
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.message.data.SingleMessage
/**
* 无参数解析, 接收原生参数的指令.
*
* ### 指令执行流程
* [CommandManager.executeCommand] 所述第 3 , [RawCommand] 不会对参数做任何解析.
*
* @see JRawCommand Java 用户继承.
*
* @see SimpleCommand 简单指令
* @see CompositeCommand 复合指令
*/
public abstract class RawCommand @JvmOverloads constructor(
/**
* 指令拥有者.
* @see CommandOwner
*/
public override val owner: CommandOwner,
/** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */
public override vararg val names: String,
/** 用法说明, 用于发送给用户 */
public override val usage: String = "<no usages given>",
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
public override val description: String = "<no descriptions given>",
/** 指令权限 */
public override val permission: CommandPermission = CommandPermission.Default,
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
public override val prefixOptional: Boolean = false
) : Command {
/**
* 在指令被执行时调用.
*
* @param args 指令参数. 数组元素类型可能是 [SingleMessage] [String]. 且已经以 ' ' 分割.
*
* @see CommandManager.execute 查看更多信息
*/
public abstract override suspend fun CommandSender.onCommand(args: Array<out Any>)
}
/**
* Java 用户继承
*
* 请在构造时设置相关属性.
*
* ```java
* public final class MyCommand extends JRawCommand {
* public static final MyCommand INSTANCE = new MyCommand();
* private MyCommand () {
* super(MyPluginMain.INSTANCE, "test")
* // 可选设置如下属性
* setUsage("/test")
* setDescription("这是一个测试指令")
* setPermission(CommandPermission.Operator.INSTANCE)
* setPrefixOptional(true)
* }
*
* @Override
* public void onCommand(@NotNull CommandSender sender, @NotNull args: Object[]) {
* // 处理指令
* }
* }
* ```
*
* @see RawCommand
*/
public abstract class JRawCommand(
/**
* 指令拥有者.
* @see CommandOwner
*/
public override val owner: CommandOwner,
/** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */
public override vararg val names: String
) : Command {
/** 用法说明, 用于发送给用户 */
public override var usage: String = "<no usages given>"
protected set
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
public final override var description: String = "<no descriptions given>"
protected set
/** 指令权限 */
public final override var permission: CommandPermission = CommandPermission.Default
protected set
/** 为 `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: Array<out Any>)
public final override suspend fun CommandSender.onCommand(args: Array<out Any>) {
withContext(Dispatchers.IO) { onCommand(this@onCommand, args) }
}
}

View File

@ -17,19 +17,23 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.description.*
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
/**
* 简单指令. 参数支持自动解析. [CommandArgumentParser]
* 简单的, 支持参数自动解析的指令.
*
* 要查看指令解析流程, 参考 [CommandManager.executeCommand]
* 要查看参数解析方式, 参考 [CommandArgumentParser]
*
* Kotlin 实现:
* ```
* object MySimpleCommand : SimpleCommand(
* MyPlugin, "tell",
* description = "Message somebody",
* usage = "/tell <target> <message>"
* usage = "/tell <target> <message>" // usage 如不设置则自动根据带有 @Handler 的方法生成
* ) {
* @Handler
* suspend fun CommandSender.onCommand(target: User, message: String) {
@ -38,18 +42,8 @@ import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotatio
* }
* ```
*
* Java 实现:
* ```java
* public final class MySimpleCommand extends SimpleCommand {
* private MySimpleCommand() {
* super(MyPlugin.INSTANCE, new String[]{ "tell" }, "Message somebody", "/tell <target> <message>")
* }
* @Handler
* public void onCommand(CommandSender sender, User target, String message) {
* target.sendMessage(message)
* }
* }
* ```
* @see JSimpleCommand Java 实现
* @see [CommandManager.executeCommand]
*/
public abstract class SimpleCommand @JvmOverloads constructor(
owner: CommandOwner,
@ -69,7 +63,7 @@ public abstract class SimpleCommand @JvmOverloads constructor(
*/
protected annotation class Handler
public final 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>) {
subCommands.single().parseAndExecute(this, args, false)
@ -86,3 +80,40 @@ public abstract class SimpleCommand @JvmOverloads constructor(
internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver
get() = SimpleCommandSubCommandAnnotationResolver
}
/**
* Java 实现:
* ```java
* public final class MySimpleCommand extends JSimpleCommand {
* private MySimpleCommand() {
* super(MyPlugin.INSTANCE, "tell")
* // 可选设置如下属性
* setDescription("这是一个测试指令")
* setUsage("/tell <target> <message>") // 如不设置则自动根据带有 @Handler 的方法生成
* setPermission(CommandPermission.Operator.INSTANCE)
* setPrefixOptional(true)
* }
*
* @Handler
* public void onCommand(CommandSender sender, User target, String message) {
* target.sendMessage(message)
* }
* }
* ```
*
* @see SimpleCommand
* @see [CommandManager.executeCommand]
*/
public abstract class JSimpleCommand(
owner: CommandOwner,
vararg names: String
) : SimpleCommand(owner, *names) {
public override var description: String = super.description
protected set
public override var permission: CommandPermission = super.permission
protected set
public override var prefixOptional: Boolean = super.prefixOptional
protected set
public override var context: CommandArgumentContext = super.context
protected set
}

View File

@ -92,7 +92,7 @@ public object BooleanArgumentParser : InternalCommandArgumentParserExtensions<Bo
/**
* 根据 [Bot.id] 解析一个登录后的 [Bot]
*/
public object ExistBotArgumentParser : InternalCommandArgumentParserExtensions<Bot> {
public object ExistingBotArgumentParser : InternalCommandArgumentParserExtensions<Bot> {
public override fun parse(raw: String, sender: CommandSender): Bot =
if (raw == "~") sender.inferBotOrFail()
else raw.findBotOrFail()
@ -108,7 +108,7 @@ public object ExistBotArgumentParser : InternalCommandArgumentParserExtensions<B
*
* 当只登录了一个 [Bot] , 无需上述 `botId` 参数即可
*/
public object ExistFriendArgumentParser : InternalCommandArgumentParserExtensions<Friend> {
public object ExistingFriendArgumentParser : InternalCommandArgumentParserExtensions<Friend> {
private val syntax = """
- `botId.friendId`
- `botId.friendNick` (模糊搜索, 寻找最优匹配)
@ -153,7 +153,7 @@ public object ExistFriendArgumentParser : InternalCommandArgumentParserExtension
*
* 当只登录了一个 [Bot] , 无需上述 `botId` 参数即可
*/
public object ExistGroupArgumentParser : InternalCommandArgumentParserExtensions<Group> {
public object ExistingGroupArgumentParser : InternalCommandArgumentParserExtensions<Group> {
private val syntax = """
- `botId.groupId`
- `~` (指代指令调用人自己所在群. 仅群聊天环境下)

View File

@ -26,15 +26,18 @@ import kotlin.reflect.full.isSubclassOf
/**
* [CommandArgumentParser] 的集合, 用于 [CompositeCommand] [SimpleCommand].
* 指令参数环境, [CommandArgumentParser] 的集合, 用于 [CompositeCommand] [SimpleCommand].
*
* 在指令解析时, 总是从 [CommandArgumentContextAware.context] 搜索相关解析器
*
* 要构造 [CommandArgumentContext], 参考 [buildCommandArgumentContext]
*
* @see SimpleCommandArgumentContext 简单实现
* @see EmptyCommandArgumentContext 空实现, 类似 [emptyList]
* @see CommandArgumentContext.EMPTY 空实现的另一种获取方式.
*
* @see CommandArgumentContext.Builtins 内建 [CommandArgumentParser]
*
* @see CommandArgumentContext DSL
* @see buildCommandArgumentContext DSL 构造
*/
public interface CommandArgumentContext {
/**
@ -62,7 +65,7 @@ public interface CommandArgumentContext {
/**
* 内建的默认 [CommandArgumentParser]
*/
public object Builtins : CommandArgumentContext by (CommandArgumentContext {
public object Builtins : CommandArgumentContext by (buildCommandArgumentContext {
Int::class with IntArgumentParser
Byte::class with ByteArgumentParser
Short::class with ShortArgumentParser
@ -73,14 +76,14 @@ public interface CommandArgumentContext {
Float::class with FloatArgumentParser
Member::class with ExistMemberArgumentParser
Group::class with ExistGroupArgumentParser
Friend::class with ExistFriendArgumentParser
Bot::class with ExistBotArgumentParser
Group::class with ExistingGroupArgumentParser
Friend::class with ExistingFriendArgumentParser
Bot::class with ExistingBotArgumentParser
})
}
/**
* 拥有 [CommandArgumentContext] 的类
* 拥有 [buildCommandArgumentContext] 的类
*
* @see SimpleCommand
* @see CompositeCommand
@ -95,7 +98,7 @@ public interface CommandArgumentContextAware {
public object EmptyCommandArgumentContext : CommandArgumentContext by SimpleCommandArgumentContext(listOf())
/**
* 合并两个 [CommandArgumentContext], [replacer] 将会替换 [this] 中重复的 parser.
* 合并两个 [buildCommandArgumentContext], [replacer] 将会替换 [this] 中重复的 parser.
*/
public operator fun CommandArgumentContext.plus(replacer: CommandArgumentContext): CommandArgumentContext {
if (replacer == EmptyCommandArgumentContext) return this
@ -125,9 +128,9 @@ public operator fun CommandArgumentContext.plus(replacer: List<ParserPair<*>>):
}
/**
* 自定义 [CommandArgumentContext]
* 自定义 [buildCommandArgumentContext]
*
* @see CommandArgumentContext
* @see buildCommandArgumentContext
*/
@Suppress("UNCHECKED_CAST")
public class SimpleCommandArgumentContext(
@ -140,10 +143,11 @@ public class SimpleCommandArgumentContext(
}
/**
* 构建一个 [CommandArgumentContext].
* 构建一个 [buildCommandArgumentContext].
*
* Kotlin 实现:
* ```
* CommandArgumentContext {
* val context = buildCommandArgumentContext {
* Int::class with IntArgParser
* Member::class with ExistMemberArgParser
* Group::class with { s: String, sender: CommandSender ->
@ -155,22 +159,47 @@ public class SimpleCommandArgumentContext(
* }
* ```
*
* Java 实现:
* ```java
* CommandArgumentContext context =
* new CommandArgumentContextBuilder()
* .add(clazz1, parser1)
* .add(String.class, new CommandArgumentParser<String>() {
* public String parse(String raw, CommandSender sender) {
* // ...
* }
* })
* // 更多 add
* .build()
* ```
*
* @see CommandArgumentContextBuilder
* @see CommandArgumentContext
* @see buildCommandArgumentContext
*/
@Suppress("FunctionName")
@JvmSynthetic
public fun CommandArgumentContext(block: CommandArgumentContextBuilder.() -> Unit): CommandArgumentContext {
return SimpleCommandArgumentContext(CommandArgumentContextBuilder().apply(block).distinctByReversed { it.klass })
public fun buildCommandArgumentContext(block: CommandArgumentContextBuilder.() -> Unit): CommandArgumentContext {
return CommandArgumentContextBuilder().apply(block).build()
}
/**
* @see CommandArgumentContext
* 参考 [buildCommandArgumentContext]
*/
public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutableListOf() {
@JvmName("add") // TODO: 2020/8/19 java class support
public inline infix fun <T : Any> KClass<T>.with(parser: CommandArgumentParser<T>): ParserPair<*> =
ParserPair(this, parser).also { add(it) }
/**
* 添加一个指令解析器.
*/
@JvmName("add")
public infix fun <T : Any> Class<T>.with(parser: CommandArgumentParser<T>): CommandArgumentContextBuilder =
this.kotlin with parser
/**
* 添加一个指令解析器
*/
@JvmName("add")
public inline infix fun <T : Any> KClass<T>.with(parser: CommandArgumentParser<T>): CommandArgumentContextBuilder {
add(ParserPair(this, parser))
return this@CommandArgumentContextBuilder
}
/**
* 添加一个指令解析器
@ -179,7 +208,12 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
@LowPriorityInOverloadResolution
public inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgumentParser<T>.(s: String, sender: CommandSender) -> T
): ParserPair<*> = ParserPair(this, CommandArgumentParser(parser)).also { add(it) }
): CommandArgumentContextBuilder {
add(ParserPair(this, object : CommandArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
}))
return this@CommandArgumentContextBuilder
}
/**
* 添加一个指令解析器
@ -187,12 +221,18 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
@JvmSynthetic
public inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgumentParser<T>.(s: String) -> T
): ParserPair<*> =
ParserPair(this, CommandArgumentParser { s: String, _: CommandSender -> parser(s) }).also { add(it) }
): CommandArgumentContextBuilder {
add(ParserPair(this, object : CommandArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = parser(raw)
}))
return this@CommandArgumentContextBuilder
}
@JvmSynthetic
public inline fun <reified T : Any> add(parser: CommandArgumentParser<T>): ParserPair<*> =
ParserPair(T::class, parser).also { add(it) }
public inline fun <reified T : Any> add(parser: CommandArgumentParser<T>): CommandArgumentContextBuilder {
add(ParserPair(T::class, parser))
return this@CommandArgumentContextBuilder
}
/**
* 添加一个指令解析器
@ -201,7 +241,9 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
@JvmSynthetic
public inline infix fun <reified T : Any> add(
crossinline parser: CommandArgumentParser<*>.(s: String) -> T
): ParserPair<*> = T::class with CommandArgumentParser { s: String, _: CommandSender -> parser(s) }
): CommandArgumentContextBuilder = T::class with object : CommandArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = parser(raw)
}
/**
* 添加一个指令解析器
@ -211,7 +253,14 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
@LowPriorityInOverloadResolution
public inline infix fun <reified T : Any> add(
crossinline parser: CommandArgumentParser<*>.(s: String, sender: CommandSender) -> T
): ParserPair<*> = T::class with CommandArgumentParser(parser)
): CommandArgumentContextBuilder = T::class with object : CommandArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
}
/**
* 完成构建, 得到 [CommandArgumentContext]
*/
public fun build(): CommandArgumentContext = SimpleCommandArgumentContext(this.distinctByReversed { it.klass })
}
internal inline fun <T, K> List<T>.distinctByReversed(selector: (T) -> K): List<T> {

View File

@ -11,27 +11,82 @@
package net.mamoe.mirai.console.command.description
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.SimpleCommand
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.content
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* 指令参数解析器.
* 指令参数解析器. 用于解析字符串或 [SingleMessage] 到特定参数类型.
*
* @see CommandArgumentContext
* @see ExistFriendArgumentParser
* ### 参数解析
*
* [SimpleCommand] 中的示例:
* ```
* suspend fun CommandSender.mute(target: Member, duration: Int)
* ```
* [CommandManager] 总是从 [SimpleCommand.context] 搜索一个 [T] [Member] [CommandArgumentParser], 并调用其 [CommandArgumentParser.parse]
*
* ### 内建指令解析器
* - 基础类型: [ByteArgumentParser], [ShortArgumentParser], [IntArgumentParser], [LongArgumentParser]
* [FloatArgumentParser], [DoubleArgumentParser],
* [BooleanArgumentParser], [StringArgumentParser]
*
* - [Bot]: [ExistingBotArgumentParser]
* - [Friend]: [ExistingFriendArgumentParser]
* - [Group]: [ExistingGroupArgumentParser]
* - [Member]: [ExistMemberArgumentParser]
*
*
* @see SimpleCommand 简单指令
* @see CompositeCommand 复合指令
*
* @see buildCommandArgumentContext 指令参数环境, [CommandArgumentParser] 的集合
*/
public interface CommandArgumentParser<out T : Any> {
/**
* 解析一个字符串为 [T] 类型参数
*
* **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException].
* 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户.
*
* @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出.
*
* @see CommandArgumentParserException
*/
@Throws(CommandArgumentParserException::class)
public fun parse(raw: String, sender: CommandSender): T
/**
* 解析一个字符串为 [T] 类型参数
*
* **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException].
* 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户.
*
* @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出.
*
* @see CommandArgumentParserException
*/
@Throws(CommandArgumentParserException::class)
@JvmDefault
public fun parse(raw: SingleMessage, sender: CommandSender): T = parse(raw.content, sender)
}
/**
* 解析
* 解析一个字符串或 [SingleMessage] [T] 类型参数
*
* @throws IllegalArgumentException [raw] 既不是 [SingleMessage], 也不是 [String] 时抛出.
*/
@JvmSynthetic
@Throws(IllegalArgumentException::class)
public fun <T : Any> CommandArgumentParser<T>.parse(raw: Any, sender: CommandSender): T {
contract {
returns() implies (raw is String || raw is SingleMessage)
@ -45,14 +100,23 @@ public fun <T : Any> CommandArgumentParser<T>.parse(raw: Any, sender: CommandSen
}
/**
* 抛出一个 [CommandArgumentParserException]
* 抛出一个 [CommandArgumentParserException] 的捷径
*
* @throws CommandArgumentParserException
*/
@Suppress("unused")
@JvmSynthetic
@Throws(CommandArgumentParserException::class)
public inline fun CommandArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
throw CommandArgumentParserException(message, cause)
}
/**
* 检查参数 [condition]. 当它为 `false` 时调用 [message] 并以其返回值作为消息, 抛出异常 [CommandArgumentParserException]
*
* @throws CommandArgumentParserException
*/
@Throws(CommandArgumentParserException::class)
@JvmSynthetic
public inline fun CommandArgumentParser<*>.checkArgument(
condition: Boolean,
@ -60,10 +124,13 @@ public inline fun CommandArgumentParser<*>.checkArgument(
) {
contract {
returns() implies condition
callsInPlace(message, InvocationKind.AT_MOST_ONCE)
}
if (!condition) illegalArgument(message())
}
/*
/**
* 创建匿名 [CommandArgumentParser]
*/
@ -88,3 +155,4 @@ public inline fun <T : Any> CommandArgumentParser(
override fun parse(raw: SingleMessage, sender: CommandSender): T = messageParser(raw, sender)
}
*/

View File

@ -7,16 +7,14 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command
package net.mamoe.mirai.console.internal.command
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.internal.command.executeCommandInternal
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
import net.mamoe.mirai.console.internal.command.intersectsIgnoringCase
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.message.MessageEvent
@ -88,7 +86,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
///// IMPL
override val CommandOwner.registeredCommands: List<Command> get() = this@CommandManagerImpl.registeredCommands.filter { it.owner == this }
override val CommandOwner.registeredCommands: List<Command> get() = CommandManagerImpl.registeredCommands.filter { it.owner == this }
override val allRegisteredCommands: List<Command> get() = registeredCommands.toList() // copy
override val commandPrefix: String get() = "/"
override fun CommandOwner.unregisterAllCommands() {

View File

@ -9,9 +9,10 @@
@file:Suppress("unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.console.command.description
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.description.CommandArgumentParser
import java.lang.reflect.Parameter
import kotlin.reflect.KClass

View File

@ -14,7 +14,6 @@ package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.description.CommandArgumentContext
import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
import net.mamoe.mirai.console.command.description.CommandParameter
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
import kotlin.reflect.KAnnotatedElement

View File

@ -109,18 +109,14 @@ internal inline fun <reified T> List<T>.dropToTypedArray(n: Int): Array<T> = Arr
@JvmSynthetic
@Throws(CommandExecutionException::class)
internal suspend inline fun CommandSender.executeCommandInternal(
internal suspend fun CommandSender.executeCommandInternal(
command: Command,
args: Array<out Any>,
commandName: String,
checkPermission: Boolean
) {
if (checkPermission && !command.testPermission(this)) {
throw CommandExecutionException(
command,
commandName,
CommandPermissionDeniedException(command)
)
throw CommandExecutionException(this, command, commandName, CommandPermissionDeniedException(this, command))
}
kotlin.runCatching {
@ -128,7 +124,7 @@ internal suspend inline fun CommandSender.executeCommandInternal(
}.onFailure {
catchExecutionException(it)
if (it !is CommandArgumentParserException) {
throw CommandExecutionException(command, commandName, it)
throw CommandExecutionException(this, command, commandName, it)
}
}
}

View File

@ -22,8 +22,8 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands
import net.mamoe.mirai.console.command.description.CommandArgumentContext
import net.mamoe.mirai.console.command.description.CommandArgumentParser
import net.mamoe.mirai.console.command.description.buildCommandArgumentContext
import net.mamoe.mirai.console.initTestEnvironment
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
import net.mamoe.mirai.message.data.Image
@ -187,7 +187,7 @@ internal class TestCommand {
val composite = object : CompositeCommand(
ConsoleCommandOwner,
"test",
overrideContext = CommandArgumentContext {
overrideContext = buildCommandArgumentContext {
add(object : CommandArgumentParser<MyClass> {
override fun parse(raw: String, sender: CommandSender): MyClass {
return MyClass(raw.toInt())