# Mirai Console Backend - Commands

# Command (opens new window)

「指令」:目前通常是 "/commandName arg1 arg2 arg3" 格式的消息。在将来可能会被扩展

interface Command {
    val names: Array<out String>
    val usage: String
    val description: String
    val permission: CommandPermission
    val prefixOptional: Boolean
    val owner: CommandOwner
    suspend fun CommandSender.onCommand(args: MessageChain)
}
1
2
3
4
5
6
7
8
9

每一条指令都被抽象成 Command (opens new window)

# 执行指令

指令既可以在代码执行,也可以在消息环境中执行。

#CommandManager (opens new window) 执行指令

通过扩展:

  • suspend fun Command.execute(CommandSender, args: Message, checkPermission: Boolean=true): CommandExecutionResult
  • suspend fun Command.execute(CommandSender, args: String, checkPermission: Boolean=true): CommandExecutionResult
  • suspend fun CommandSender.executeCommand(message: Message, checkPermission: Boolean=true): CommandExecutionResult
  • suspend fun CommandSender.executeCommand(message: String, checkPermission: Boolean=true): CommandExecutionResult

# 指令语法解析

一条消息可以被解析为指令,如果它满足:

<指令前缀><任一指令名> <指令参数列表>

指令参数由空格分隔。参数类型可能为 MessageContent 类型,或 String(被包装为 PlainText)

指令前缀可能是可选的。可以在配置文件配置。(计划支持中)

# RawCommand (opens new window)

无参数解析, 接收原生参数的指令。

abstract override suspend fun CommandSender.onCommand(args: MessageChain)
1

例如 /test 123 [图片],在处理时 onCommand 接收的 args 为包含 2 个元素的 MessageChain。第一个元素为 PlainText("123"),第二个元素为 Image 类型。

# Register (opens new window)

需要把指令注册到 CommandManager 以在 Mirai Console 生效

CommandManager.registerCommand(command)
1

# 参数智能解析

本节可能较难理解。但这不会影响你阅读下面的示例。

Mirai Console 为了简化处理指令时的解析过程,设计了参数智能解析。

# CommandValueArgumentParser (opens new window)

interface CommandArgumentParser<out T : Any> {
    fun parse(raw: String, sender: CommandSender): T
    fun parse(raw: MessageContent, sender: CommandSender): T = parse(raw.content, sender)
}
1
2
3
4

用于解析一个参数到一个数据类型。

# CommandArgumentContext (opens new window)

ClassCommandValueArgumentParser (opens new window) 的映射。作用是为某一个类型分配解析器。

# 内建 CommandArgumentContext (opens new window)

支持原生数据类型,Contact 及其子类,Bot

# 构建 CommandArgumentContext (opens new window)

查看源码内注释:CommandArgumentContext.kt: Line 146 (opens new window)

# 支持参数解析的 Command (opens new window) 实现

Mirai Console 内建 SimpleCommand (opens new window)CompositeCommand (opens new window) 拥有 CommandArgumentContext (opens new window),在处理参数时会首先解析参数再传递给插件的实现。

# SimpleCommand (opens new window)

简单指令。

此时示例一定比理论有意义。

Kotlin 示例:

object MySimpleCommand : SimpleCommand(
    MyPluginMain, "tell", "私聊",
    description = "Tell somebody privately"
) {
    @Handler // 标记这是指令处理器  // 函数名随意 
    suspend fun CommandSender.handle(target: User, message: String) { // 这两个参数会被作为指令参数要求
        target.sendMessage(message)
    }
}
1
2
3
4
5
6
7
8
9

Java 示例:

public class MySimpleCommand extends SimpleCommand {

    public MySimpleCommand(JvmPlugin plugin) {
        super(plugin, "tell", new String[]{"私聊"}, "Tell somebody privately", plugin.getParentPermission(), CommandArgumentContext.EMPTY);
    }

    @Handler // 标记这是指令处理器,方法名随意
    public void handle(CommandSender sender, User target, String message) { // 后两个参数会被作为指令参数要求
        target.sendMessage(message);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12

指令 /tell 123456 Hello 的解析流程:

  1. 被分割为 /, "tell", "123456", "Hello"
  2. MySimpleCommand 被匹配到,根据 /"test""123456", "Hello" 被作为指令的原生参数。
  3. 由于 MySimpleCommand 定义的 handle 需要两个参数, UserString"123456" 需要转换成 User"Hello" 需要转换成 String
  4. Console 在 内建 CommandArgumentContext (opens new window) 寻找适合于 UserCommandValueArgumentParser (opens new window)
  5. "123456" 被传入这个 CommandValueArgumentParser (opens new window),得到 User
  6. "Hello" 也会按照 4~5 的步骤转换为 String 类型的参数
  7. 解析完成的参数被传入 handle

# CompositeCommand (opens new window)

CompositeCommand (opens new window) 的参数解析与 SimpleCommand (opens new window) 一样,只是多了「子指令」概念。

示例:

Kotlin 示例:

@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 ConsoleCommandSender.foo() {
        // 使用 ConsoleCommandSender 作为接收者,表示指令只能由控制台执行。
        // 当用户尝试在聊天环境执行时将会收到错误提示。
    }

    @SubCommand("list", "查看列表") // 可以设置多个子指令名。此时函数名会被忽略。
    suspend fun CommandSender.ignoredFunctionName() { // 执行 "/manage list" 时调用这个函数
        sendMessage("/manage list 被调用了")
    }

    // 支持 Image 类型, 需在聊天中执行此指令.
    @SubCommand
    suspend fun UserCommandSender.test(image: Image) { // 执行 "/manage test <一张图片>" 时调用这个函数
        // 由于 Image 类型消息只可能在聊天环境,可以直接使用 UserCommandSender。
        
        sendMessage("/manage image 被调用了, 图片是 ${image.imageId}")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

Java 示例:

public class MyCompositeCommand extends CompositeCommand {

    public MyCompositeCommand(JvmPlugin plugin) {
        // "manage" 是主指令名, 还有更多参数可填, 此处忽略
        super(plugin, "manage", new String[]{}, "示例指令", plugin.getParentPermission(), CommandArgumentContext.EMPTY);
    }

    // [参数智能解析]
    //
    // 在控制台执行 "/manage <群号>.<群员> <持续时间>",
    // 或在聊天群内发送 "/manage <@一个群员> <持续时间>",
    // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>",
    // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>"
    // 时调用这个函数
    @SubCommand // 表示这是一个子指令,使用函数名作为子指令名称
    public void mute(CommandSender sender, Member target, int duration) { // 通过 /manage mute <target> <duration> 调用
        sender.sendMessage("/manage mute 被调用了, 参数为: " + target.toString() + ", " + duration);

        String result;
        try {
            target.mute(duration);
            result = "成功";
        } catch (Exception e) {
            result = "失败," + e.getMessage();
        }

        sender.sendMessage("结果: " + result);
    }

    @SubCommand
    public void foo(ConsoleCommandSender sender) {
        // 使用 ConsoleCommandSender 作为接收者,表示指令只能由控制台执行。
        // 当用户尝试在聊天环境执行时将会收到错误提示。
    }

    @SubCommand(value = {"list", "查看列表"}) // 可以设置多个子指令名。此时函数名会被忽略。
    public void ignoredFunctionName(CommandSender sender) { // 执行 "/manage list" 时调用这个函数
        sender.sendMessage("/manage list 被调用了");
    }

    // 支持 Image 类型, 需在聊天中执行此指令.
    @SubCommand
    public void test(CommandSender sender, Image image) { // 执行 "/manage test <一张图片>" 时调用这个函数
        // 由于 Image 类型消息只可能在聊天环境,可以直接使用 UserCommandSender。

        sender.sendMessage("/manage image 被调用了, 图片是 " + image.getImageId());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 文本参数的转义

不同的参数默认用空格分隔。有时用户希望在文字参数中包含空格本身,参数解析器可以接受三种表示方法。

以上文中定义的 MySimpleCommand 为例:

# 英文双引号

表示将其中内容作为一个参数,可以包括空格。

例如:用户输入 /tell 123456 "Hello world!"message 会收到 Hello world!

注意:双引号仅在参数的首尾部生效。例如,用户输入 /tell 123456 He"llo world!"message 只会得到 He"llo

# 转义符

即英文反斜杠 \。表示忽略之后一个字符的特殊含义,仅看作字符本身。

例如:

  • 用户输入 /tell 123456 Hello\ world!message 得到 Hello world!
  • 用户输入 /tell 123456 \"Hello world!\"message 得到 "Hello

# 暂停解析标志

即连续两个英文短横线 --。表示从此处开始,到这段文字内容结束为止,都作为一个完整参数。

例如:

  • 用户输入 /tell 123456 -- Hello:::test\12""3message 得到 Hello:::test\12""3: 表示空格);
  • 用户输入 /tell 123456 -- Hello @全体成员 test1 test2,那么暂停解析的作用范围到 @ 为止,之后的 test1test2 是不同的参数。
  • 用户输入 /tell 123456 \-- Hello/tell 123456 "--" Hello,这不是暂停解析标志,message 得到 -- 本身。

注意:

-- 的前后都应与其他参数有间隔,否则不认为这是暂停解析标志。

例如,用户输入 /tell 123456--Hello world!123456--Hello 会被试图转换为 User 并出错。即使转换成功,message 也只会得到 world!

# 非文本参数的转义

有时可能需要只用一个参数来接受各种消息内容,例如用户可以在 /tell 123456 后接图片、表情等,它们都是 message 的一部分。

对于这种定义方式,Mirai Console 的支持尚待实现,目前可以使用 RawCommand (opens new window) 替代。

# 选择 RawCommand (opens new window), SimpleCommand (opens new window)CompositeCommand (opens new window)

若需要不限长度的,自由的参数列表,使用 RawCommand (opens new window)

若需要子指令,使用 CompositeCommand (opens new window)。否则使用 SimpleCommand (opens new window)

# CommandManager (opens new window)

上面已经提到可以在 CommandManager (opens new window) 执行指令。CommandManager (opens new window) 持有已经注册的指令列表,源码内有详细注释,此处不过多赘述。

# CommandSender (opens new window)

指令发送者。

# 必要性

指令可能在聊天环境执行,也可能在控制台执行。因此需要一个通用的接口表示这样的执行者。

# 类型

                CoroutineScope
                       ↑
                       |
                 CommandSender &lt;---------+---------------+-------------------------------+
                       ↑                 |               |                               |
                       |                 |               |                               |
                       |     UserCommandSender   GroupAwareCommandSender     CommandSenderOnMessage
                       |                 ↑               ↑                               ↑
                       |                 |               |                               |
              AbstractCommandSender      |               |                               |
                       ↑                 |               |                               |
                       | sealed          |               |                               |
         +-------------+-------------+   |               |                               |
         |                           |   |               |                               |
         |                           |   |               |                               |      }
ConsoleCommandSender    AbstractUserCommandSender        |                               |      } 一级子类
                                     ↑                   |                               |      }
                                     | sealed            |                               |
                                     |                   |                               |
              +----------------------+                   |                               |
              |                      |                   |                               |
              |                      +------+------------+---------------+               |
              |                             |                            |               |
              |                             |                            |               |      }
      FriendCommandSender          MemberCommandSender           TempCommandSender       |      } 二级子类
              ↑                             ↑                            ↑               |      }
              |                             |                            |               |
              |                             |                            |               |      }
 FriendCommandSenderOnMessage  MemberCommandSenderOnMessage  TempCommandSenderOnMessage  |      } 三级子类
              |                             |                            |               |      }
              |                             |                            |               |
              +-----------------------------+----------------------------+---------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

有关类型的详细信息,请查看 CommandSender.kt (opens new window)

# 获取

Contact.asCommandSender()MessageEvent.toCommandSender(),或 ConsoleCommandSender

# MessageScope (opens new window)

表示几个消息对象的’域‘,即消息对象的集合。用于最小化将同一条消息发送给多个类型不同的目标的付出。

参考 MessageScope (opens new window)


下一步,PluginData

返回 开发文档索引