@@ -1,296 +1,586 @@
# Mirai Console Backend - Commands
[`Plugin` ]: ../backend/mirai-console/src/plugin/Plugin.kt
[`PluginDescription` ]: ../backend/mirai-console/src/plugin/description/PluginDescription.kt
[`PluginLoader` ]: ../backend/mirai-console/src/plugin/loader/PluginLoader.kt
[`PluginManager` ]: ../backend/mirai-console/src/plugin/PluginManager.kt
[`JvmPluginLoader` ]: ../backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt
[`JvmPlugin` ]: ../backend/mirai-console/src/plugin/jvm/JvmPlugin.kt
[`JvmPluginDescription` ]: ../backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt
[`AbstractJvmPlugin` ]: ../backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt
[`KotlinPlugin` ]: ../backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt
[`JavaPlugin` ]: ../backend/mirai-console/src/plugin/jvm/JavaPlugin.kt
[`Value` ]: ../backend/mirai-console/src/data/Value.kt
[`PluginData` ]: ../backend/mirai-console/src/data/PluginData.kt
[`AbstractPluginData` ]: ../backend/mirai-console/src/data/AbstractPluginData.kt
[`AutoSavePluginData` ]: ../backend/mirai-console/src/data/AutoSavePluginData.kt
[`AutoSavePluginConfig` ]: ../backend/mirai-console/src/data/AutoSavePluginConfig.kt
[`PluginConfig` ]: ../backend/mirai-console/src/data/PluginConfig.kt
[`PluginDataStorage` ]: ../backend/mirai-console/src/data/PluginDataStorage.kt
[`MultiFilePluginDataStorage` ]: ../backend/mirai-console/src/data/PluginDataStorage.kt#L116
[`MemoryPluginDataStorage` ]: ../backend/mirai-console/src/data/PluginDataStorage.kt#L100
[`AutoSavePluginDataHolder` ]: ../backend/mirai-console/src/data/PluginDataHolder.kt#L45
[`PluginDataHolder` ]: ../backend/mirai-console/src/data/PluginDataHolder.kt
[`PluginDataExtensions` ]: ../backend/mirai-console/src/data/PluginDataExtensions.kt
[`MiraiConsole` ]: ../backend/mirai-console/src/MiraiConsole.kt
[`MiraiConsoleImplementation` ]: ../backend/mirai-console/src/MiraiConsoleImplementation.kt
<!--[MiraiConsoleFrontEnd]: ../backend/mirai-console/src/MiraiConsoleFrontEnd.kt-->
[`Command` ]: ../backend/mirai-console/src/command/Command.kt
[`Register` ]: ../backend/mirai-console/src/command/CommandManager.kt#L77
[`AbstractCommand` ]: ../backend/mirai-console/src/command/Command.kt#L90
[`CompositeCommand` ]: ../backend/mirai-console/src/command/CompositeCommand.kt
[`SimpleCommand` ]: ../backend/mirai-console/src/command/SimpleCommand.kt
[`RawCommand` ]: ../backend/mirai-console/src/command/RawCommand.kt
[`CommandManager` ]: ../backend/mirai-console/src/command/CommandManager.kt
[`CommandSender` ]: ../backend/mirai-console/src/command/CommandSender.kt
[`CommandValueArgumentParser` ]: ../backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt
[`CommandArgumentContext` ]: ../backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt
[`CommandArgumentContext.BuiltIns` ]: ../backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt#L66
[`MessageScope` ]: ../backend/mirai-console/src/util/MessageScope.kt
## [`Command`]
>「指令」: 目前通常是 "/commandName arg1 arg2 arg3" 格式的消息。在将来可能会被扩展
"指令" 目前通常是 "/commandName arg1 arg2 arg3" 格式的消息。在将来可能会被扩展。
指令拥有一个主要名称和任意个次要名称。使用任意名称都可以执行指令。
定义指令
------
每个指令都是一个 `Command` 类型的对象。`Command` 是一个接口,定义了基本的指令的属性:
``` kotlin
interface Command {
val names : Array < out String >
val usage : String
val description : String
val permission : Command Permission
val prefixOptional : Boolean
val owner : CommandOwner
suspend fun CommandSender . onCommand ( args : MessageChain )
val names : Array < out String > // 名称
val usage : String // 用法
val description : String // 描述
val permission : Permission // 权限
val prefixOptional : Boolean // 前缀可选
val owner : CommandOwner // 拥有者
val overloads : List < CommandSignature > // 指令的签名列表
}
```
每一条指令都被抽象成 [`Command` ]。
`AbstractCommand` 提供了对 `Command` 的基础实现。而要在插件定义指令,建议继承如下三种指令实现:
### 执行指令
(注意:所有指令都需要注册到指令管理器才能生效,详见 [注册指令 ](#注册指令 ))
指令既可以在代码执行,也可以在消息环境中执行。
### 原生指令
#### 在 [`CommandManager`] 执行指令
原生指令即 [`RawCommand` ],它直接处理触发指令的原消息链。
通过扩展 :
- `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`
`RawCommand` 提供了两个抽象函数,在指令被执行时将会调用它们:
* Kotlin *
``` kotlin
open override suspend fun CommandContext . onCommand ( args : MessageChain )
open override suspend fun CommandSender . onCommand ( args : MessageChain )
```
* Java *
``` java
public abstract class JRawCommand {
// ...
public void onCommand ( CommandContext context , MessageChain args ) {
}
public void onCommand ( CommandSender sender , MessageChain args ) {
}
}
```
例如在聊天环境通过消息链 `/test 123 [图片]` 触发指令(`[图片]` 表示一个图片),`onCommand` 接收的 `args`
为包含 2
个元素的 `MessageChain` 。第一个元素为 `PlainText("123")` ,第二个元素为 `Image` 。
注意,当 `onCommand(CommandSender, MessageChain)`
和 `onCommand(CommandContext, MessageChain)` 被同时覆盖时,
只有 `onCommand(CommandContext, MessageChain)` 会生效。
`CommandContext` 是当前指令的执行环境,定义如下:
``` kotlin
interface CommandContext {
val sender : CommandSender
val originalMessage : MessageChain
}
```
其中 `sender` 为指令执行者。它可能是控制台(`ConsoleCmomandSender`
),也可能是用户(`UserCommandSender` )等;
`originalMessage` 为触发指令的原消息链,包含元数据,也包含指令名。
若在聊天环境触发指令,`originalMessage` 将会包含 `MessageSource` 。
注意,`MessageSource` 等 `MessageMetadata` 的位置是不确定的。取决于 mirai-core
的版本,它可能会存在于消息链中的任意位置。因此请不要依赖于 `originalMessage` 的元素顺序。
`args` 参数的顺序是稳定的,因为它只包含消息内容(`MessageContent` )。
#### 使用 `RawCommand`
只需要按需继承 `onCommand` 其中一个即可。如果需要使用原消息链,则继承 `CommandContext`
的,否则继承 `CommandSender` 的可以使实现更简单。
通常可以以单例形式实现指令,当然非单例模式也是支持的。
下面分别为在 Kotlin 和 Java 的示例实现:
* Kotlin *
``` kotlin
object MyCommand : RawCommand (
MyPluginMain , " name " , // 使用插件主类对象作为指令拥有者;设置主指令名为 "name"
// 可选:
" name2 " , " name3 " , // 增加两个次要名称
usage = " /name arg1 arg2 " , // 设置用法,将会在 /help 展示
description = " 这是一个测试指令 " , // 设置描述,将会在 /help 展示
prefixOptional = true , // 设置指令前缀是可选的,即使用 `test` 也能执行指令而不需要 `/test`
) {
override suspend fun CommandContext . onCommand ( args : MessageChain ) {
}
}
```
* Java *
``` java
public final class MyCommand extends JRawCommand {
public static final MyCommand INSTANCE = new MyCommand ( ) ;
private MyCommand ( ) {
super ( MyPluginMain . INSTANCE , " test " ) ; // 使用插件主类对象作为指令拥有者;设置主指令名为 "test"
// 可选设置如下属性
setUsage ( " /test " ) ; // 设置用法,这将会在 /help 中展示
setDescription ( " 这是一个测试指令 " ) ; // 设置描述,也会在 /help 中展示
setPrefixOptional ( true ) ; // 设置指令前缀是可选的,即使用 `test` 也能执行指令而不需要 `/test`
}
@Override
public void onCommand ( @NotNull CommandSender sender , @NotNull MessageChain args ) {
// 处理指令
}
}
```
### 参数智能解析
Console
提供参数智能解析功能,可以阅读用户手册的 [指令参数智能解析 ](../../docs/ConsoleTerminal.md#指令参数智能解析 )
了解这一功能。
有两种指令实现支持这个功能,它们分别是简单指令 `SimpleCommand` 和复合指令 `CompositeCommand` 。
### 复合指令
复合指令即 `CompositeCommand` ,支持参数智能解析。
Console 通过反射实现参数类型识别。标注 `@SubCommand` 的函数(方法)都会被看作是子指令。
一个简单的子指令定义如下:
* Kotlin *
``` kotlin
object MyComposite : CompositeCommand ( ) {
// ...
@SubCommand ( " name " )
suspend fun foo ( context : CommandContext , arg : String ) {
println ( arg )
}
}
```
* Java *
``` java
public final class MyComposite extends JCompositeCommand {
// ...
@SubCommand ( " name " )
public void foo ( CommandContext context , String arg ) {
System . out . println ( arg ) ;
}
}
```
Java 使用者请了解 Kotlin 的 `fun foo(context: CommandContext, arg: String)` 相当于
Java 的 `public void foo(CommandContext context, String arg)` 。下面部分简单示例将只用
Kotlin 展示。
#### 子指令
用 `@SubCommand` 标注的函数就是子指令。子指令将隶属于其主指令。注册于主指令 `main` 的名称为 `child`
的指令在执行时需要使用 `/main child` ,其中 `/` 表示指令前缀(如果需要)。`/main child arg1 arg2`
中的 `arg1` 和 `arg2` 则表示传递给子指令的第一个和第二个参数。
子指令可以拥有多个名称,即 `@SubCommand("child1", "child2")` 可以由 `/main child1`
或 `/main child2` 执行。
#### 子指令名称
`@SubCommand` 的参数为子指令的名称,可以有多个名称,即 `@SubCommand("name1", "name2")`
。若子指令名称与函数名称相同,可以省略 `@SubCommand`
的参数。例如 `@SubCommand("foo") suspend fun foo()`
可以简写为 `@SubCommand suspend fun foo()` 。
#### 子指令参数
子指令的第一个参数(在 Kotlin 也可以是接收者(`receiver` ))可以是 `CommandContext`
或 `CommandSender` ,用来获取指令执行环境或发送人。与 `RawCommand`
相同,如果需要使用原消息链,则使用 `CommandContext` ,否则使用 `CommandSender` 的可以让实现更简单。
在这个参数以外的就是是子指令的值参数。
值参数将会对应消息链。例如 `@SubCommand fun foo(context: CommandContext, arg: String)`
将会对应 /comp foo
在 Kotlin, 子指令既可以是 `suspend` 也可以不是。
#### 定义参数
子指令函数(方法)定义的参数将按顺序成为指令的参数。如下示例中 `arg1` 将成为第一个参数,`arg2` 为第二个:
* Kotlin *
``` kotlin
object MyComposite : CompositeCommand ( MyPluginMain , " main " ) {
// ...
@SubCommand ( " name " )
suspend fun foo ( context : CommandContext , arg : String , b : Boolean ) {
println ( arg )
}
}
```
* Java *
``` java
public final class MyComposite extends JCompositeCommand {
public MyComposite ( ) {
super ( MyPluginMain . INSTANCE , " main " ) ;
// ...
}
// ...
@SubCommand ( " name " )
public void foo ( CommandContext context , String arg , boolean b ) {
System . out . println ( arg ) ;
}
}
```
在执行时,`/main name 1 true` 中 `1` 将会被解析为 `String` 类型的参数 `arg` 、`true`
将会被解析为 `boolean` 参数的 `b` 。
#### 内置智能解析
可参考 `CommandValueArgumentParser` , Console 内置支持以下类型的参数:
- `Message`
- `SingleMessage`
- `MessageContent`
- 原生数据类型
- `PlainText`
- `Image`
- `String`
- `Bot`
- `Contact`
- `User`
- `Friend`
- `Member`
- `Group`
- `PermissionId`
- `PermitteeId`
- `Enum`
- `TemporalAccessor`
#### 自定义智能解析
可在 `CmopositeCommand` 继承 `context` 属性增加自定义解析器。下面示例中为 `Boolean`
指定了自定义的解析器,子指令的 `b` 参数将会用此解析器解析。
* Kotlin *
``` kotlin
object CustomBooleanParser : CommandValueArgumentParser < Boolean > {
override fun parse ( raw : String , sender : CommandSender ) : Boolean {
return raw == " TRUE! "
}
override fun parse (
raw : MessageContent ,
sender : CommandSender
) : Boolean {
// 将一个图片认为是 'true'
if ( raw is Image && raw . imageId == " {A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg " ) {
return true
}
return super . parse ( message , sender )
}
}
object MyComposite : CompositeCommand (
MyPluginMain , " main " ,
overrideContext = buildCommandArgumentContext {
Boolean :: class with CustomBooleanParser
}
) {
// ...
@SubCommand ( " name " )
suspend fun foo ( context : CommandContext , arg : String , b : Boolean ) {
println ( b )
}
}
```
* Java *
``` java
// CustomBooleanParser.java
public final class CustomBooleanParser implements CommandValueArgumentParser < Boolean > {
@NotNull
@Override
public Boolean parse ( @NotNull String raw , @NotNull CommandSender sender ) throws CommandArgumentParserException {
return raw . equals ( " TRUE! " ) ;
}
@NotNull
@Override
public Boolean parse ( @NotNull MessageContent raw , @NotNull CommandSender sender ) throws CommandArgumentParserException {
// 将一个图片认为是 'true'
if ( raw instanceof Image & & ( ( Image ) raw ) . getImageId ( ) . equals ( " {A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg " ) ) {
return true ;
}
return CommandValueArgumentParser . super . parse ( raw , sender ) ;
}
}
// MyComposite.java
public final class MyComposite extends JCompositeCommand {
public MyComposite ( ) {
super ( MyPluginMain . INSTANCE , " main " ) ;
// ...
addArgumentContext ( new CommandArgumentContextBuilder ( )
. add ( Boolean . TYPE , new CustomBooleanParser ( ) ) // 注册解析器
. build ( ) ) ;
}
// ...
@SubCommand ( " name " )
public void foo ( CommandContext context , String arg , boolean b ) {
System . out . println ( b ) ;
}
}
```
在 `parse` 时抛出 `CommandArgumentParserException`
会被看作是正常退出,异常的内容会返回给指令调用人。在 `parse` 时抛出其他异常则会认为是插件错误。
### 简单指令
简单指令与复合指令拥有一样的智能参数解析功能。简单指令没有子指令,使用 `@Handler` 标注一个函数可以让它处理指令:
* Kotlin *
``` kotlin
object MySimple : SimpleCommand ( MyPluginMain , " main " ) {
// ...
@Handler
suspend fun foo ( context : CommandContext , arg : String , b : Boolean ) {
println ( b )
}
}
```
* Java *
``` java
// MyComposite.java
public final class MyComposite extends JCompositeCommand {
public MyComposite ( ) {
super ( MyPluginMain . INSTANCE , " main " ) ;
// ...
}
// ...
@Handler
public void foo ( CommandContext context , String arg , boolean b ) {
System . out . println ( b ) ;
}
}
```
在执行时,`/main aaaa false` 将会调用 `foo` 函数(方法)。`aaaa` 匹配 `String` 类型的参数 `arg`
, `false` 匹配 `boolean` 类型的参数 `b` 。
简单指令也可以使用自定义参数解析器,用法与复合指令一样。
* Kotlin *
``` kotlin
object MySimple : SimpleCommand (
MyPluginMain , " main " ,
overrideContext = buildCommandArgumentContext {
Boolean :: class with CustomBooleanParser
}
) {
// ...
@Handler
suspend fun foo ( context : CommandContext , arg : String , b : Boolean ) {
println ( b )
}
}
```
* Java *
``` java
// MyComposite.java
public final class MyComposite extends JCompositeCommand {
public MyComposite ( ) {
super ( MyPluginMain . INSTANCE , " main " ) ;
// ...
addArgumentContext ( new CommandArgumentContextBuilder ( )
. add ( Boolean . TYPE , new CustomBooleanParser ( ) ) // 注册解析器
. build ( ) ) ;
}
// ...
@Handler
public void foo ( CommandContext context , String arg , boolean b ) {
System . out . println ( b ) ;
}
}
```
### 选择 [`RawCommand`], [`SimpleCommand`] 或 [`CompositeCommand`]
若需要不限长度的,自由的参数列表,使用 [`RawCommand` ]。
若需要子指令,使用 [`CompositeCommand` ]。否则使用 [`SimpleCommand` ]。
### 自行实现指令
Console 允许插件自行实现指令(不使用上述 `RawCommand` 、`SimpleCommand`
和 `CompositeCommand` )。但注意,在实现时难免会需要使用到抽象指令描述器(如 `CommandArgument`
),而这些描述器是不稳定的。因此插件自行实现指令可能会导致不兼容未来的 Console 版本。
注册指令
-------
所有指令都需要注册到指令管理器才能生效。要注册指令,在 `onEnable`
使用 `CommandManager.registerCommand(command)` 。
### 查看已注册的所有指令
使用 `PluginManager.INSTANCE.getAllRegisteredCommands()`
。可以获得当前已经注册的所有 `Command` 实例列表。
执行指令
-------
指令既可以由插件执行,也可以在消息环境中由用户执行(需要 [chat-command ](https://github.com/project-mirai/chat-command )
)。
### 在插件执行指令
若要通过字符串解析目标指令并执行,使用 `PluginManager.INSTANCE.executeCommand(CommandSender, Message)`
,其中 `Message` 为包含前缀(如果有必要)、指令名称、以及指令参数列表的完整消息。
若要通过字符串解析目标指令并执行,使用 `PluginManager.INSTANCE.executeCommand(CommandSender, Command, Message)`
,其中 `Message` 传递给指令的参数列表,不包含前缀或指令名称。注意,若要执行复合指令,需要包含子指令名称。
### 指令语法解析
一条消息可以被解析为指令,如果它满足:
`<指令前缀><任一指令名> <指令参数列表>`
一条消息可以被解析为指令,如果它满足:
指令参数由空格分隔。参数类型可能为 `MessageContent` 类型,或 `String` (被包装为 `PlainText` )
指令前缀可能是可选的。可以在配置文件配置。(计划支持中)
### [`RawCommand`]
无参数解析, 接收原生参数的指令。
``` kotlin
abstract override suspend fun CommandSender . onCommand ( args : MessageChain )
``` text
<指令前缀><任一指令名> <指令参数列表>
```
例如 `/test 123 [图片]` ,在处理时 `onCommand` 接收的 `args` 为包含 2 个元素的 `MessageChain` 。第一个元素为 `PlainText("123")` ,第二个元素为 `Image` 类型 。
指令参数列表由空格分隔 。
### [`Register`]
需要把指令注册到 `CommandManager` 以在 Mirai Console 生效
``` kotlin
CommandManager . registerCommand ( command )
```
### 指令解析流程
## 参数智能解析
> 本节可能较难理解。但这不会影响你阅读下面的示例。
> 注意:该流程可能会变化,请不要依赖这个流程。
Mirai Console 为了简化处理指令时的解析过程,设计了参数智能解析。
对于
### [`CommandValueArgumentParser`]
``` kotlin
interface CommandArgumentParser < out T : Any > {
fun parse ( raw : String , sender : CommandSender ) : T
fun parse ( raw : MessageContent , sender : CommandSender ) : T = parse ( raw . content , sender )
}
```
用于解析一个参数到一个数据类型。
### [`CommandArgumentContext`]
是 `Class` 到 [`CommandValueArgumentParser` ] 的映射。作用是为某一个类型分配解析器。
#### [内建 `CommandArgumentContext`][`CommandArgumentContext.BuiltIns`]
支持原生数据类型,`Contact` 及其子类,`Bot` 。
#### 构建 [`CommandArgumentContext`]
查看源码内注释:[CommandArgumentContext.kt: Line 146 ](../backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt#L146-L183 )
### 支持参数解析的 [`Command`] 实现
Mirai Console 内建 [`SimpleCommand` ] 与 [`CompositeCommand` ] 拥有 [`CommandArgumentContext` ],在处理参数时会首先解析参数再传递给插件的实现。
### [`SimpleCommand`]
简单指令。
此时示例一定比理论有意义。
Kotlin 示例:
``` kotlin
object MySimpleCommand : SimpleCommand (
MyPluginMain , " tell " , " 私聊 " ,
description = " Tell somebody privately "
) {
@Handler // 标记这是指令处理器 // 函数名随意
suspend fun CommandSender . handle ( target : User , message : String ) { // 这两个参数会被作为指令参数要求
target . sendMessage ( message )
}
}
```
Java 示例:
``` 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 ) ;
}
}
``` text
@Handler suspend fun handle(context: CommandContext, target: User, message: String)
```
指令 `/tell 123456 Hello` 的解析流程:
1. 被分割为 `/` , `"tell"` , `"123456"` , `"Hello"`
2. `MySimpleCommand` 被匹配到, 根据 `/` 和 `"test"` 。`"123456"` , `"Hello"` 被作为指令的原生参数。
3. 由于 `MySimpleCommand` 定义的 `handle` 需要两个参数, `User` 和 `String` , `"123456"` 需要转换成 `User` , `"Hello"` 需要转换成 `String` 。
4 . Console 在 [内建 `CommandArgumentContext` ][`CommandArgumentContext.BuiltIns` ] 寻找适合于 `User` 的 [`CommandValueArgumentParser ` ]
5. `"123456"` 被传入这个 [`CommandValueArgumentParser` ],得到 `User `
6 . `"Hello"` 也会按照 4~5 的步骤转换为 `String` 类型的参数
7 . 解析完成的参数被传入 `handle`
2. 根据 `/` 和 `"test"` ,确定 `MySimpleCommand` 作为目标指令 。`"123456"` , `"Hello"`
作为指令的原生参数 。
3 . 由于 `MySimpleCommand` 定义的 `handle` 需要两个参数, 即 `User` 和 `String `
, `"123456"` 需要转换成 `User` , `"Hello"` 需要转换成 `String ` 。
4 . 指令寻找合适的解析器(`CommandValueArgumentParser` )
5 . `"123456"` 通过 `ExistingUserValueArgumentParser` 变为 `User` 类型的参数
6. `"Hello"` 通过 `StringValueArgumentParser` 变为 `String` 类型的参数
7. 解析完成的参数传入 `handle`
### [`CompositeCommand`]
[`CompositeCommand` ] 的参数解析与 [`SimpleCommand` ] 一样,只是多了「子指令」概念。
示例:
Kotlin 示例:
``` 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} " )
}
}
```
Java 示例:
``` 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 ( ) ) ;
}
}
```
### 文本参数的转义
文本参数转义
-----
不同的参数默认用空格分隔。有时用户希望在文字参数中包含空格本身,参数解析器可以接受三种表示方法。
以上文中定义的 `MySimpleCommand` 为例:
#### 英文双引号
### 英文双引号
表示将其中内容作为一个参数,可以包括空格。
例如:用户输入 `/tell 123456 "Hello world!"` , `message` 会收到 `Hello world!` 。
注意:双引号仅在参数的首尾部生效。例如,用户输入 `/tell 123456 He"llo world!"` , `message` 只会得到 `He"llo` 。
注意:双引号仅在参数的首尾部生效。例如,用户输入 `/tell 123456 He"llo world!"` , `message`
只会得到 `He"llo` 。
#### 转义符
### 转义符
即英文反斜杠 `\` 。表示忽略之后一个字符的特殊含义,仅看作字符本身。
@@ -299,49 +589,41 @@ public class MyCompositeCommand extends CompositeCommand {
- 用户输入 `/tell 123456 Hello\ world!` , `message` 得到 `Hello world!` ;
- 用户输入 `/tell 123456 \"Hello world!\"` , `message` 得到 `"Hello` 。
#### 暂停解析标志
### 暂停解析标志
即连续两个英文短横线 `--` 。表示从此处开始,到**这段文字内容**结束为止,都作为一个完整参数。
例如:
- 用户输入 `/tell 123456 -- Hello:::test\12""3` , `message` 得到 `Hello:::test\12""3` ( `:` 表示空格);
- 用户输入 `/tell 123456 -- Hello @全体成员 test1 test2` ,那么暂停解析的作用范围到 `@` 为止,之后的 `test1` 和 `test2` 是不同的参数。
- 用户输入 `/tell 123456 \ -- Hello` 或 `/tell 123456 "--" Hello` ,这不是暂停解析标志,`message` 得到 `--` 本身。
- 用户输入 `/tell 123456 -- Hello:::test\12""3` , `message`
得到 `Hello:::test\12""3` ( `:` 表示空格);
- 用户输入 `/tell 123456 -- Hello @全体成员 test1 test2` ,那么暂停解析的作用范围到 `@`
为止,之后的 `test1` 和 `test2` 是不同的参数。
- 用户输入 `/tell 123456 \-- Hello` 或 `/tell 123456 "--" Hello`
,这不是暂停解析标志,`message` 得到 `--` 本身。
注意:
`--` 的前后都应与其他参数有间隔,否则不认为这是暂停解析标志。
例如,用户输入 `/tell 123456--Hello world!` , `123456--Hello` 会被试图转换为 `User` 并出错。即使转换成功,`message` 也只会得到 `world!` 。
例如,用户输入 `/tell 123456--Hello world!` , `123456--Hello` 会被试图转换为 `User`
并出错。即使转换成功,`message` 也只会得到 `world!` 。
### 非文本参数的转义
有时可能需要只用一个参数来接受各种消息内容,例如用户可以在 `/tell 123456` 后接图片、表情等,它们都是 `message` 的一部分。
有时可能需要只用一个参数来接受各种消息内容,例如用户可以在 `/tell 123456` 后接图片、表情等,它们都是 `message`
的一部分。
对于这种定义方式, Mirai Console 的支持尚待实现,目前可以使用 [`RawCommand` ] 替代。
对于这种定义方式, Mirai Console 的支持尚待实现,目前可以使用 [`RawCommand` ] 替代。
### 选择 [`RawCommand`], [`SimpleCommand`] 或 [`CompositeCommand`]
## 指令发送者
若需要不限长度的,自由的参数列表,使用 [`RawCommand` ] 。
若需要子指令,使用 [`CompositeCommand` ]。否则使用 [`SimpleCommand` ]。
## [`CommandManager`]
上面已经提到可以在 [`CommandManager` ] 执行指令。[`CommandManager` ] 持有已经注册的指令列表,源码内有详细注释,此处不过多赘述。
## [`CommandSender`]
指令发送者。
### 必要性
指令可能在聊天环境执行,也可能在控制台执行。因此需要一个通用的接口表示这样的执行者。
指令可能在聊天环境执行,也可能在控制台执行 。
指令发送者即 `CommandSender` ,是执行指令时的必须品之一。
### 类型
``` text
CoroutineScope
↑
|
CommandSender <---------+---------------+-------------------------------+
↑ | | |
| | | |
@@ -375,17 +657,90 @@ ConsoleCommandSender AbstractUserCommandSender |
有关类型的详细信息,请查看 [CommandSender.kt](../backend/mirai-console/src/command/CommandSender.kt#L48-L135)
### 获取
### 获取控制台指令发送者
` Contact.asCommandSender()` 或 ` MessageEvent.toCommandSender()`,或 ` ConsoleCommandSender`
`ConsoleCommandSender` 表示以控制台身份执行指令。它是一个单例对象,在 Kotlin 可以直接通过类型获得类名获得实例,在
Java 可通过 `ConsoleCommandSender.INSTANCE` 获得。
## [` MessageScope`]
# ## 获取其他指令发送者
表示几个消息对象的’域‘,即消息对象的集合。用于最小化将同一条消息发送给多个类型不同的目标的付出。
在 Kotlin 可使用扩展函数:`Contact.asCommandSender()`
或 `MessageEvent.toCommandSender()`
。
参考 [MessageScope ](../backend/mirai-console/src/util/MessageScope.kt#L28-L99 )
在 Java 可使用 `CommandSender.from` 和 `CommandSender.of`。
[`MessageScope`]
-----
表示几个消息对象的'域',即消息对象的集合。用于最小化将同一条消息发送给多个类型不同的目标的付出。示例:
*Kotlin*
```kotlin
// 在一个 CompositeCommand 内
@Handler
suspend fun CommandSender.handle(target: Member) {
val duration = Random.nextInt(1, 15)
target.mute(duration)
// 不使用 MessageScope, 无用的样板代码
val thisGroup = this.getGroupOrNull()
val message = "${this.name} 禁言 ${target.nameCardOrNick} $duration 秒"
if (target.group != thisGroup) {
target.group.sendMessage(message)
}
sendMessage(message)
// 使用 MessageScope, 清晰逻辑
// 表示至少发送给 `this`, 当 `this` 的真实发信对象与 `target.group` 不同时, 还额外发送给 `target.group`
this.scopeWith(target.group) {
sendMessage("${name} 禁言了 ${target.nameCardOrNick} $duration 秒")
}
// 同样地, 可以扩展用法, 同时私聊指令执行者:
// this.scopeWith(
// target,
// target.group
// ) { ... }
}
```
* Java *
``` java
public class MyCommand extends SimpleCommand {
@Handler
public void handle ( sender : CommandSender , target : Member ) {
int duration = Random . nextInt ( 1 , 15 ) ;
target . mute ( duration ) ;
// 不使用 MessageScope
Group thisGroup = CommandSenderKt . getGroupOrNull ( sender ) ;
String message = " ${this.name} 禁言 ${target.nameCardOrNick} $duration 秒 " ;
if ( ! target . group . equals ( thisGroup ) ) {
target . group . sendMessage ( message ) ;
}
sender . sendMessage ( message ) ;
// 使用 MessageScope
// 表示至少发送给 `this`, 当 `this` 的真实发信对象与 `target.group` 不同时, 还额外发送给 `target.group`
MessageScope scope = MessageScopeKt . scopeWith ( sender , target ) ;
scope . sendMessage ( " ${name} 禁言了 ${target.nameCardOrNick} $duration 秒 " ) ;
// 或是只用一行:
MessageScopeKt . scopeWith ( sender , target ) . sendMessage ( " ${name} 禁言了 ${target.nameCardOrNick} $duration 秒 " ) ;
}
}
```
----
> 下一步,[PluginData](PluginData.md#mirai-console-backend---plugindata)