diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt index a9ebce45a..dce0724a9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt @@ -1,3 +1,12 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + @file:Suppress("unused") package net.mamoe.mirai.console.command @@ -78,6 +87,23 @@ sealed class CommandExecuteResult { override val status: CommandExecuteStatus get() = CommandExecuteStatus.COMMAND_NOT_FOUND } + /** 权限不足 */ + class PermissionDenied( + /** 尝试执行的指令 */ + override val command: Command, + /** 尝试执行的指令名 */ + override val commandName: String + ) : CommandExecuteResult() { + /** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ + override val args: Nothing? get() = null + + /** 指令执行时发生的错误, 总是 `null` */ + override val exception: Nothing? get() = null + + /** 指令最终执行状态, 总是 [CommandExecuteStatus.PERMISSION_DENIED] */ + override val status: CommandExecuteStatus get() = CommandExecuteStatus.PERMISSION_DENIED + } + /** * 指令的执行状态 */ @@ -89,7 +115,10 @@ sealed class CommandExecuteResult { EXECUTION_EXCEPTION, /** 没有匹配的指令 */ - COMMAND_NOT_FOUND + COMMAND_NOT_FOUND, + + /** 权限不足 */ + PERMISSION_DENIED } } @@ -121,6 +150,18 @@ fun CommandExecuteResult.isExecutionException(): Boolean { return this is CommandExecuteResult.ExecutionException } +/** + * 当 [this] 为 [CommandExecuteResult.ExecutionException] 时返回 `true` + */ +@JvmSynthetic +fun CommandExecuteResult.isPermissionDenied(): Boolean { + contract { + returns(true) implies (this@isPermissionDenied is CommandExecuteResult.PermissionDenied) + returns(false) implies (this@isPermissionDenied !is CommandExecuteResult.PermissionDenied) + } + return this is CommandExecuteResult.PermissionDenied +} + /** * 当 [this] 为 [CommandExecuteResult.ExecutionException] 时返回 `true` */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecutionException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecutionException.kt new file mode 100644 index 000000000..4faeb35fc --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecutionException.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("MemberVisibilityCanBePrivate") + +package net.mamoe.mirai.console.command + +/** + * 在 [executeCommand] 中, [Command.onCommand] 抛出异常时包装的异常. + */ +class CommandExecutionException( + /** + * 执行过程发生异常的指令 + */ + val command: Command, + /** + * 匹配到的指令名 + */ + val name: String, + cause: Throwable +) : RuntimeException( + "Exception while executing command '${command.primaryName}'", + cause +) { + override fun toString(): String = + "CommandExecutionException(command=$command, name='$name')" +} + diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index bda174d01..ca7069252 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -131,7 +131,7 @@ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock { //// executing without detailed result (faster) /** - * 解析并执行一个指令 + * 解析并执行一个指令. 将会检查指令权限, 在无权限时抛出 * * @param messages 接受 [String] 或 [Message], 其他对象将会被 [Any.toString] * @@ -160,32 +160,6 @@ suspend fun CommandSender.executeCommand(message: MessageChain): Command? { } -/** - * 在 [executeCommand] 中, [Command.onCommand] 抛出异常时包装的异常. - */ -class CommandExecutionException( - /** - * 执行过程发生异常的指令 - */ - val command: Command, - /** - * 匹配到的指令名 - */ - val name: String, - /** - * 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] - */ - val args: Array, - cause: Throwable -) : RuntimeException( - "Exception while executing command '${command.primaryName}' with args ${args.joinToString { "'$it'" }}", - cause -) { - override fun toString(): String = - "CommandExecutionException(command=$command, name='$name', args=${args.contentToString()})" -} - - //// execution with detailed result /** @@ -224,6 +198,10 @@ internal suspend inline fun CommandSender.executeCommandDetailedInternal( val command = InternalCommandManager.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName) val args = messages.flattenCommandComponents().dropToTypedArray(1) + + if (!command.testPermission(this)) { + return CommandExecuteResult.PermissionDenied(command, commandName) + } kotlin.runCatching { command.onCommand(this, args) }.fold( diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt index 2023953fa..8317e3b7b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt @@ -25,6 +25,9 @@ import net.mamoe.mirai.contact.isOwner interface CommandPermission { /** * 判断 [this] 是否拥有这个指令的权限 + * + * @see CommandSender.hasPermission + * @see CommandPermission.testPermission */ fun CommandSender.hasPermission(): Boolean @@ -32,11 +35,13 @@ interface CommandPermission { /** * 满足两个权限其中一个即可使用指令 */ // no extension for Java + @JvmDefault infix fun or(another: CommandPermission): CommandPermission = OrCommandPermission(this, another) /** * 同时拥有两个权限才能使用指令 */ // no extension for Java + @JvmDefault infix fun and(another: CommandPermission): CommandPermission = AndCommandPermission(this, another) @@ -48,7 +53,7 @@ interface CommandPermission { } /** - * 任何人都不能使用这个指令. 指令只能通过代码在 [execute] 使用 + * 任何人都不能使用这个指令. 指令只能通过调用 [Command.onCommand] 执行. */ object None : CommandPermission { override fun CommandSender.hasPermission(): Boolean = false @@ -94,7 +99,7 @@ interface CommandPermission { * 仅控制台能使用和这个指令 */ object Console : CommandPermission { - override fun CommandSender.hasPermission(): Boolean = false + override fun CommandSender.hasPermission(): Boolean = this is ConsoleCommandSender } object Default : CommandPermission by (Manager or Console) @@ -117,6 +122,8 @@ inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean = inline fun CommandPermission.testPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() } +inline fun Command.testPermission(sender: CommandSender): Boolean = sender.hasPermission(this.permission) + internal class OrCommandPermission( private val first: CommandPermission, private val second: CommandPermission diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermissionDeniedException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermissionDeniedException.kt new file mode 100644 index 000000000..c8a65a23a --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermissionDeniedException.kt @@ -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.command + +/** + * 在 [executeCommand] 中, [CommandSender] 未拥有 [Command.permission] 所要求的权限时抛出的异常. + * + * 总是作为 [CommandExecutionException.cause]. + */ +class CommandPermissionDeniedException( + /** + * 执行过程发生异常的指令 + */ + val command: Command +) : RuntimeException("Permission denied while executing command '${command.primaryName}'") { + override fun toString(): String = + "CommandPermissionDeniedException(command=$command)" +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt index 1552786d8..f2b82aceb 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt @@ -171,18 +171,19 @@ internal suspend inline fun CommandSender.executeCommandInternal( commandName: String ): Command? { val command = InternalCommandManager.matchCommand(commandName) ?: return null - val rawInput = messages.flattenCommandComponents() - val loweredArgs = rawInput.dropToTypedArray(1) + if (!command.testPermission(this)) { + throw CommandExecutionException(command, commandName, CommandPermissionDeniedException(command)) + } kotlin.runCatching { - command.onCommand(this, loweredArgs) + command.onCommand(this, messages.flattenCommandComponents().dropToTypedArray(1)) }.fold( onSuccess = { return command }, onFailure = { - throw CommandExecutionException(command, commandName, loweredArgs, it) + throw CommandExecutionException(command, commandName, it) } ) }