diff --git a/docs/Bots.md b/docs/Bots.md index 2b7a1c029..ba2382c2e 100644 --- a/docs/Bots.md +++ b/docs/Bots.md @@ -4,14 +4,17 @@ - [1. 创建和配置 `Bot`](#1-创建和配置-bot) - [配置 Bot](#配置-bot) + - [重要配置](#重要配置) + - [切换心跳策略](#切换心跳策略) + - [切换登录协议](#切换登录协议) + - [覆盖登录解决器](#覆盖登录解决器) - [常用配置](#常用配置) - [修改运行目录](#修改运行目录) - [修改 Bot 缓存目录](#修改-bot-缓存目录) - [设备信息](#设备信息) - - [切换登录协议](#切换登录协议) - [重定向日志](#重定向日志) - - [覆盖登录解决器](#覆盖登录解决器) - [启用列表缓存](#启用列表缓存) + - [更多配置](#更多配置) - [获取当前所有 `Bot` 实例](#获取当前所有-bot-实例) - [2. 登录](#2-登录) - [处理滑动验证码](#处理滑动验证码) @@ -21,8 +24,6 @@ 一个机器人被以 `Bot` 对象描述。mirai 的交互入口点是 `Bot`。`Bot` 只可通过 [`BotFactory`](../mirai-core-api/src/commonMain/kotlin/BotFactory.kt#L22-L87) 内的 `newBot` 方法获得: -> 你现在还不需要知道 `Bot` 可以干什么。 - ```kotlin interface BotFactory { fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot @@ -67,6 +68,52 @@ Bot bot = BotFactory.INSTANCE.newBot(qq, password, new BotConfiguration() {{ > 可在 [BotConfiguration.kt](../mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt#L23) 查看完整配置列表 +### 重要配置 + +#### 切换心跳策略 + +心跳策略默认为最佳的 `STAT_HB`,但不适用于一些账号。 + +如果遇到 Bot **闲置一段时间后**,发消息返回成功但群内收不到的情况,请切换心跳策略,依次尝试 `STAT_HB`、`REGISTER` 和 `NONE`。 + +``` +// Kotlin +heartbeatStrategy = BotConfiguration.HeartbeatStrategy.REGISTER + +// Java +setHeartbeatStrategy(BotConfiguration.HeartbeatStrategy.REGISTER) +``` + +#### 切换登录协议 +Mirai 支持多种登录协议:`ANDROID_PHONE`,`ANDROID_PAD`,`ANDROID_WATCH`,默认使用 `ANDROID_PHONE`。 + +若登录失败,可尝试切换协议。**但注意部分功能在部分协议上不受支持**,详见源码内注释。 + +要切换协议: +``` +// Kotlin +protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD + +// Java +setProtocol(MiraiProtocol.ANDROID_PAD) +``` + +#### 覆盖登录解决器 + +在登录时可能遇到图形验证码或滑动验证码,Mirai 会使用 `LoginSolver` 解决验证码。 + +- 在 JVM, Mirai 会根据环境支持情况选择 Swing/CLI 实现,通常不需要手动提供 +- 在 Android 需要手动提供 `LoginSolver` + +若要覆盖默认的 `LoginSolver` (通常不需要): +``` +// Kotlin +loginSolver = YourLoginSolver + +// Java +setLoginSolver(new YourLoginSolver()) +``` + ### 常用配置 #### 修改运行目录 @@ -126,19 +173,6 @@ setDeviceInfo(bot -> /* create device info */) 在线生成自定义设备信息的 `device.json`: https://ryoii.github.io/mirai-devicejs-generator/ -#### 切换登录协议 -Mirai 支持多种登录协议:`ANDROID_PHONE`,`ANDROID_PAD`,`ANDROID_WATCH`,默认使用 `ANDROID_PHONE`。 - -若登录失败,可尝试切换协议。**但注意部分功能在部分协议上不受支持**,详见源码内注释。 - -要切换协议: -``` -// Kotlin -protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD - -// Java -setProtocol(MiraiProtocol.ANDROID_PAD) -``` #### 重定向日志 Bot 有两个日志类别,`Bot` 或 `Net`。`Bot` 为通常日志,如收到事件。`Net` 为网络日志,包含收到和发出的每一个包和网络层解析时遇到的错误。 @@ -168,21 +202,6 @@ noNetworkLog() noBotLog() ``` -#### 覆盖登录解决器 -Mirai 会使用 `LoginSolver` 解决验证码。 - -- 在 Android 需要手动提供 `LoginSolver` -- 在 JVM, Mirai 会根据环境支持情况选择 Swing/CLI 实现,通常不需要手动提供 - -覆盖默认的 `LoginSolver`: -``` -// Kotlin -loginSolver = YourLoginSolver - -// Java -setLoginSolver(new YourLoginSolver()) -``` - > 要获取更多有关 `LoginSolver` 的信息,查看 [LoginSolver.kt](../mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt#L32) #### 启用列表缓存 @@ -190,6 +209,8 @@ Mirai 在启动时会拉取全部好友列表和群成员列表。当账号拥 Mirai 自动根据事件更新列表,并在每次登录时与服务器校验缓存有效性,**但有时候可能发生意外情况导致列表没有同步。如果出现找不到群员或好友等不同步情况,请关闭缓存并[提交 Bug](https://github.com/mamoe/mirai/issues/new?assignees=&labels=question&template=bug.md)** +建议在测试环境使用缓存,而在正式环境关闭缓存(默认关闭缓存)。 + 要开启列表缓存(自 mirai 2.4.0): ``` // 开启所有列表缓存 @@ -212,6 +233,9 @@ contactListCache.setGroupMemberListCacheEnabled(true) // 开启群成员列表 contactListCache.setSaveIntervalMillis(60000) // 可选设置有更新时的保存时间间隔, 默认 60 秒 ``` +#### 更多配置 + +参阅 `BotConfiguration` 源码内注释。 ### 获取当前所有 `Bot` 实例 @@ -234,12 +258,12 @@ contactListCache.setSaveIntervalMillis(60000) // 可选设置有更新时的保 [#993]: https://github.com/mamoe/mirai/discussions/993 -| 错误信息 | 可能的原因 | 可能的解决方案 | -|:--------------|:---------------|:----------------------| -| 当前版本过低 | 密码错误 | 检查密码或修改密码到 16 位以内 | -| 当前上网环境异常 | 设备锁 | 开启或关闭设备锁 (登录保护) | -| 禁止登录 | 需要处理滑块验证码 | [project-mirai/mirai-login-solver-selenium] | -| 密码错误 | 密码错误或过长 | 手机协议最大支持 16 位密码 ([#993]). 在官方 PC 客户端登录后修改密码 | +| 错误信息 | 可能的原因 | 可能的解决方案 | +|:--------------|:---------------|:-----------------------------------------------------------| +| 当前版本过低 | 密码错误 | 检查密码或修改密码到 16 位以内 | +| 当前上网环境异常 | 设备锁 | 开启或关闭设备锁 (登录保护) | +| 禁止登录 | 需要处理滑块验证码 | [project-mirai/mirai-login-solver-selenium] | +| 密码错误 | 密码错误或过长 | 手机协议最大支持 16 位密码 ([#993]). 在官方 PC 客户端登录后修改密码 | 若以上方案无法解决问题,请尝试 [切换登录协议](#切换登录协议) 和 **[处理滑动验证码](#处理滑动验证码)**。 diff --git a/docs/Contacts.md b/docs/Contacts.md index 287034b33..3efdd87cc 100644 --- a/docs/Contacts.md +++ b/docs/Contacts.md @@ -2,23 +2,76 @@ [![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIEJvdCB7XG4gICAgK2ZyaWVuZHM6IENvbnRhY3RMaXN0XG4gICAgK2dyb3VwczogQ29udGFjdExpc3RcbiAgICArZ2V0RnJpZW5kKExvbmcpIEZyaWVuZD9cbiAgICArZ2V0RnJpZW5kT3JOdWxsKExvbmcpIEZyaWVuZFxuICAgICtnZXRHcm91cChMb25nKSBHcm91cD9cbiAgICArZ2V0R3JvdXBPckZhaWwoTG9uZykgR3JvdXBcbiAgICArbG9naW4oKVxuICAgICtjbG9zZSgpXG59XG5cbmNsYXNzIENvbnRhY3RPckJvdCB7XG4gICAgK2lkOiBJbnRcbiAgICArYXZhdGFyVXJsOiBTdHJpbmdcbn1cblxuY2xhc3MgVXNlck9yQm90IHtcbiAgICArbnVkZ2UoKSBOdWRnZVxufVxuXG5jbGFzcyBDb250YWN0IHtcbiAgICArYm90OiBCb3RcbiAgICArc2VuZE1lc3NhZ2UoTWVzc2FnZSkgTWVzc2FnZVJlY2VpcHRcbiAgICArc2VuZE1lc3NhZ2UoU3RyaW5nKSBNZXNzYWdlUmVjZWlwdFxuICAgICt1cGxvYWRJbWFnZShFeHRlcm5hbEltYWdlKSBJbWFnZVxufVxuXG5jbGFzcyBVc2VyIHtcbiAgICArbmljazogU3RyaW5nXG4gICAgK3JlbWFyazogU3RyaW5nXG4gICAgK3F1ZXJ5UHJvZmlsZSgpIFVzZXJQcm9maWxlXG59XG5cbmNsYXNzIEdyb3VwIHtcbiAgICArbWVtYmVyczogQ29udGFjdExpc3RcbiAgICArbmFtZTogU3RyaW5nXG4gICAgK3NldHRpbmdzOiBHcm91cFNldHRpbmdzXG4gICAgK293bmVyOiBOb3JtYWxNZW1iZXJcbiAgICArYm90TXV0ZVJlbWFpbmluZzogTG9uZ1xuICAgICtib3RQZXJtaXNzaW9uOiBNZW1iZXJQZXJtaXNzaW9uXG4gICAgK3F1aXQoKSBCb29sZWFuXG4gICAgK3VwbG9hZFZvaWNlKCkgVm9pY2Vcbn1cblxuY2xhc3MgTm9ybWFsTWVtYmVyIHtcbiAgICArbXV0ZSgpXG4gICAgK2tpY2soKVxufVxuXG5jbGFzcyBBbm9ueW1vdXNNZW1iZXIge1xuICAgICthbm9ueW1vdXNJZDogU3RyaW5nXG59XG5cbmNsYXNzIE1lbWJlciB7XG4gICAgK2dyb3VwOiBHcm91cFxufVxuXG5jbGFzcyBPdGhlckNsaWVudCB7XG4gICAgK2luZm9cbn1cblxuQ29udGFjdE9yQm90PHwtLUNvbnRhY3RcbkNvbnRhY3RPckJvdDx8LS1Vc2VyT3JCb3RcblxuVXNlck9yQm90PHwtLUJvdFxuVXNlck9yQm90PHwtLVVzZXJcblxuQ29udGFjdDx8LS1Vc2VyXG5Db250YWN0PHwtLUdyb3VwXG5Db250YWN0PHwtLU90aGVyQ2xpZW50XG5cblVzZXI8fC0tTWVtYmVyXG5Vc2VyPHwtLUZyaWVuZFxuXG5NZW1iZXI8fC0tTm9ybWFsTWVtYmVyXG5NZW1iZXI8fC0tQW5vbnltb3VzTWVtYmVyIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIEJvdCB7XG4gICAgK2ZyaWVuZHM6IENvbnRhY3RMaXN0XG4gICAgK2dyb3VwczogQ29udGFjdExpc3RcbiAgICArZ2V0RnJpZW5kKExvbmcpIEZyaWVuZD9cbiAgICArZ2V0RnJpZW5kT3JOdWxsKExvbmcpIEZyaWVuZFxuICAgICtnZXRHcm91cChMb25nKSBHcm91cD9cbiAgICArZ2V0R3JvdXBPckZhaWwoTG9uZykgR3JvdXBcbiAgICArbG9naW4oKVxuICAgICtjbG9zZSgpXG59XG5cbmNsYXNzIENvbnRhY3RPckJvdCB7XG4gICAgK2lkOiBJbnRcbiAgICArYXZhdGFyVXJsOiBTdHJpbmdcbn1cblxuY2xhc3MgVXNlck9yQm90IHtcbiAgICArbnVkZ2UoKSBOdWRnZVxufVxuXG5jbGFzcyBDb250YWN0IHtcbiAgICArYm90OiBCb3RcbiAgICArc2VuZE1lc3NhZ2UoTWVzc2FnZSkgTWVzc2FnZVJlY2VpcHRcbiAgICArc2VuZE1lc3NhZ2UoU3RyaW5nKSBNZXNzYWdlUmVjZWlwdFxuICAgICt1cGxvYWRJbWFnZShFeHRlcm5hbEltYWdlKSBJbWFnZVxufVxuXG5jbGFzcyBVc2VyIHtcbiAgICArbmljazogU3RyaW5nXG4gICAgK3JlbWFyazogU3RyaW5nXG4gICAgK3F1ZXJ5UHJvZmlsZSgpIFVzZXJQcm9maWxlXG59XG5cbmNsYXNzIEdyb3VwIHtcbiAgICArbWVtYmVyczogQ29udGFjdExpc3RcbiAgICArbmFtZTogU3RyaW5nXG4gICAgK3NldHRpbmdzOiBHcm91cFNldHRpbmdzXG4gICAgK293bmVyOiBOb3JtYWxNZW1iZXJcbiAgICArYm90TXV0ZVJlbWFpbmluZzogTG9uZ1xuICAgICtib3RQZXJtaXNzaW9uOiBNZW1iZXJQZXJtaXNzaW9uXG4gICAgK3F1aXQoKSBCb29sZWFuXG4gICAgK3VwbG9hZFZvaWNlKCkgVm9pY2Vcbn1cblxuY2xhc3MgTm9ybWFsTWVtYmVyIHtcbiAgICArbXV0ZSgpXG4gICAgK2tpY2soKVxufVxuXG5jbGFzcyBBbm9ueW1vdXNNZW1iZXIge1xuICAgICthbm9ueW1vdXNJZDogU3RyaW5nXG59XG5cbmNsYXNzIE1lbWJlciB7XG4gICAgK2dyb3VwOiBHcm91cFxufVxuXG5jbGFzcyBPdGhlckNsaWVudCB7XG4gICAgK2luZm9cbn1cblxuQ29udGFjdE9yQm90PHwtLUNvbnRhY3RcbkNvbnRhY3RPckJvdDx8LS1Vc2VyT3JCb3RcblxuVXNlck9yQm90PHwtLUJvdFxuVXNlck9yQm90PHwtLVVzZXJcblxuQ29udGFjdDx8LS1Vc2VyXG5Db250YWN0PHwtLUdyb3VwXG5Db250YWN0PHwtLU90aGVyQ2xpZW50XG5cblVzZXI8fC0tTWVtYmVyXG5Vc2VyPHwtLUZyaWVuZFxuXG5NZW1iZXI8fC0tTm9ybWFsTWVtYmVyXG5NZW1iZXI8fC0tQW5vbnltb3VzTWVtYmVyIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0) -| 类型 | 描述 | 最低支持的版本 | -|:--------------:|:---------------------------------------------------|:-----------:| -| `ContactOrBot` | `Contact` 和 `Bot` 的公共接口 | 2.0 | -| `OtherClient` | Bot 的*其他客户端*, 如 "我的 iPad", "我的电脑" | 2.0 | -| `Bot` | 机器人对象 | 2.0 | -| `Contact` | 联系人对象, 即所有的群, 好友, 陌生人, 群成员等 | 2.0 | -| `Group` | 群对象 | 2.0 | -| `User` | 用户对象, 即 "个人". 包含好友, 陌生人, 群成员, 临时会话用户 | 2.0 | -| `Friend` | 好友对象 | 2.0 | -| `Stranger` | 陌生人对象 | 2.0 | -| `Member` | 群成员对象, 属于一个 `Group`. | 2.0 | +| 类型 | 描述 | 最低支持的版本 | +|:-----------------:|:-------------------------------------------------|:-----------:| +| `ContactOrBot` | `Contact` 和 `Bot` 的公共接口 | 2.0 | +| `OtherClient` | Bot 的*其他客户端*,如 "我的 iPad","我的电脑" | 2.0 | +| `Bot` | 机器人对象 | 2.0 | +| `Contact` | 联系人对象,即所有的群,好友,陌生人,群成员等 | 2.0 | +| `Group` | 群对象 | 2.0 | +| `User` | 用户对象,即 "个人". 包含好友,陌生人,群成员,临时会话用户 | 2.0 | +| `Friend` | 好友对象 | 2.0 | +| `Stranger` | 陌生人对象 | 2.0 | +| `Member` | 群成员对象,属于一个 `Group`. | 2.0 | +| `NormalMember` | 普通群成员对象. | 2.0 | +| `AnonymousMember` | 匿名群成员对象. | 2.0 | + + +我们称 `Contact` 为 _联系人_,它表示一个 `Bot` 可以联系的对象。如上图所示,`Contact` 的子类不仅有好友和群成员,还包括群和其他客户端。因此请不要因“联系人”中的“人”就认为它只代表一个用户。 + +## 获取联系人对象 + +`Bot.getFriends()`,`Bot.getGroups()` 等方法可以获取到对象列表。 + +可通过 `Bot.getFriend`, `Bot.getGroup`,`Bot.getStranger` 以 QQ 号或群号主动获取某个对象。 + +可以通过事件被动获取 *(后文介绍)*。 + +## 联系人对象唯一且属于 Bot + +每个联系人对象都属于一个 `Bot`。可以通过 `Contact.bot` 获取到它们所属的 `Bot`。 + +对于同一个 `Bot`,不会有 `id` 相同的两个 `Group` 对象。通过 `Bot.getGroup` 得到的和在群消息事件得到的相同 `id` 的 `Group` 对象是同一个。 + +若应用同时登录多个 `Bot`,不同 `Bot` 的 `id` 相同的 `Group` 也是互相独立的。 + +## 常用功能 基于面向对象的设计,可直接获取 `Contact` 的属性如 `nick`,`permission`。请在实践时在接口源码内查看更清晰的说明。 -要主动发送一条消息,总是调用 `Contact.sendMessage(message)`*(`message` 在后文介绍)*。 +### 接收消息 -可通过 `Bot.getFriend`, `Bot.getGroup`,`Bot.getStranger` 获取相关对象,也可以通过事件获取 *(事件在后文介绍)*。 +消息通过事件被动接收。事件将在下一章节 [Events](Events.md) 讲解。 + +### 主动发送消息 + +获取到目标对象并调用 `Contact.sendMessage(message)`。如要向某个群发送消息,则需要对应的 `Group` 对象,并调用 `Group.sendMessage(message)`。 + +注意,由于对象代表意义不同,发送的消息类型也可能不同。`NormalMember` 是普通群成员,`NormalMember.sendMessage` 是对群成员发送临时会话消息,而 `Friend.sendMessage` 是发送好友消息。(备注:在实际情况下,如果机器人与群成员有好友关系,`NormalMember.sendMessage` 也会自动转换为发送好友消息,以保证消息送达) + +### 联系其他客户端 + +一个 QQ 账号可以在多个客户端登录,mirai 支持向其他客户端收发消息. 其他客户端被抽象为 `OtherClient`。 + +可以通过 `Bot.getOtherClients()` 获取到所有在线的其他客户端列表,并使用 `OtherClient.sendMessage` 来发送消息,这与操作普通好友类似。 + +### 使用戳一戳 + +戳一戳的被动接收与消息的接收相同,也是以事件的形式(`NudgeEvent`)。 + +要发起戳一戳,使用 `Contact.nudge()` 创建一个戳一戳动作(`Nudge`)然后将其发送到目标用户或群(`Nudge.sendTo`)。 + +### 操作群成员禁言和移除 + +使用 `Member.mute`, `Member.unmute`, `Member.kick`。 + +### 提及群成员 + +提及(`@`)群成员在 mirai 属于 _消息_ 的范畴。将在后面章节介绍。 + +### 其他功能 + +其他功能与上述类似,都作为成员方法位于目标对象中。未在成员方法或类注释中出现的其他功能即为目前还没有支持的功能。 ---- diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/README.md b/docs/EventList.md similarity index 77% rename from mirai-core-api/src/commonMain/kotlin/event/events/README.md rename to docs/EventList.md index 9f85d5a3e..dcaf85192 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/README.md +++ b/docs/EventList.md @@ -6,13 +6,13 @@ - 在 IntelliJ 平台双击 shift 可输入类名进行全局搜索 - 在 IntelliJ 平台, 按 alt + 7 可打开文件的结构, [效果图](/.github/EZSLAB`K@YFFOW47{090W8B.png) -注释: -- 此列表自 mirai `2.0.0` 起开始维护, 故在 `2.0.0` 前的变更将不做记录. -- 支持某事件的 mirai 版本号将会显式标注, 通过数学区间形式表示, 如 `[1.1.0, 1.3.0)`, 但有简便表示方法: - - (`1.1.0+`) 等注释表示在 `1.1.0` 及更新版本才支持, 区间表示为 `[1.1.0, +∞)` - - (`1.1.0-`) 等注释表示在早于 `1.1.0` 的版本才支持, 区间表示为 `[1.0.0, 1.1.0)` + + + + + -### [Bot](bot.kt) +### Bot - Bot 登录完成: BotOnlineEvent - Bot 离线: BotOfflineEvent - 主动: Active @@ -25,35 +25,35 @@ - Bot 被戳: BotNudgedEvent ### 消息 -- 被动收到消息:[MessageEvent](MessageEvent.kt) +- 被动收到消息:MessageEvent - 群消息:GroupMessageEvent - 好友消息:FriendMessageEvent - 群临时会话消息:GroupTempMessageEvent - 陌生人消息:StrangerMessageEvent - 其他客户端消息:OtherClientMessageEvent -- 主动发送消息前: [MessagePreSendEvent](MessagePreSendEvent.kt) +- 主动发送消息前: MessagePreSendEvent - 群消息: GroupMessagePreSendEvent - 好友消息: FriendMessagePreSendEvent - 群临时会话消息: GroupTempMessagePreSendEvent - 陌生人消息:StrangerMessagePreSendEvent - 其他客户端消息:OtherClientMessagePreSendEvent -- 主动发送消息后: [MessagePostSendEvent](MessagePostSendEvent.kt) +- 主动发送消息后: MessagePostSendEvent - 群消息: GroupMessagePostSendEvent - 好友消息: FriendMessagePostSendEvent - 群临时会话消息: GroupTempMessagePostSendEvent - 陌生人消息:StrangerMessagePostSendEvent - 其他客户端消息:OtherClientMessagePostSendEvent -- 消息撤回: [MessageRecallEvent](MessageRecallEvent.kt) +- 消息撤回: MessageRecallEvent - 好友撤回: FriendRecall - 群撤回: GroupRecall - 群临时会话撤回: TempRecall -- 图片上传前: [BeforeImageUploadEvent](ImageUploadEvent.kt) -- 图片上传完成: [ImageUploadEvent](ImageUploadEvent.kt) +- 图片上传前: BeforeImageUploadEvent +- 图片上传完成: ImageUploadEvent - 图片上传成功: Succeed - 图片上传失败: Failed -- 戳一戳: [NudgeEvent](NudgeEvent.kt) +- 戳一戳: NudgeEvent -### [群](group.kt) +### 群 - 机器人被踢出群或在其他客户端主动退出一个群: BotLeaveEvent - 机器人主动退出一个群: Active - 机器人被管理员或群主踢出群: Kick @@ -94,7 +94,7 @@ - 群成员被禁言: MemberMuteEvent - 群成员被取消禁言: MemberUnmuteEvent -### [好友](friend.kt) +### 好友 - 好友昵称改变: FriendRemarkChangeEvent - 成功添加了一个新好友: FriendAddEvent - 好友已被删除: FriendDeleteEvent diff --git a/docs/Events.md b/docs/Events.md index 988363d13..ce135f41e 100644 --- a/docs/Events.md +++ b/docs/Events.md @@ -23,14 +23,14 @@ ## 事件系统 -Mirai 以事件驱动。 +Mirai 许多功能都依赖事件。 [`Event`]: ../mirai-core-api/src/commonMain/kotlin/event/Event.kt#L21-L62 每个事件都实现接口 [`Event`],且继承 `AbstractEvent`。 实现 `CancellableEvent` 的事件可以被取消(`CancellableEvent.cancel`)。 -**[事件列表](../mirai-core-api/src/commonMain/kotlin/event/events/README.md#事件)** +**[事件列表](EventList.md)** > 回到 [目录](#目录) @@ -39,6 +39,10 @@ Mirai 以事件驱动。 如果你了解事件且不希望详细阅读,可以立即仿照下面示例创建事件监听并跳过本章节。 +注意,**`GlobalEventChannel` 会监听到来自所有 `Bot` 的事件,如果只希望监听某一个 `Bot` 的事件,请使用 `bot.eventChannel`。** + +有关消息 `Message`、`MessageChain` 将会在后文 _消息系统_ 章节解释。 + ### Kotlin ```kotlin @@ -46,6 +50,9 @@ Mirai 以事件驱动。 GlobalEventChannel.parentScope(coroutineScope).subscribeAlways { event -> // this: GroupMessageEvent // event: GroupMessageEvent + + // `event.message` 是接收到的消息内容, 可自行处理. 由于 `this` 也是 `GroupMessageEvent`, 可以通过 `message` 直接获取. 详细查阅 `GroupMessageEvent`. + subject.sendMessage("Hello!") } // `GlobalEventChannel.parentScope(coroutineScope)` 也可以替换为使用扩展 `coroutineScope.globalEventChannel()`, 根据个人习惯选择 @@ -58,32 +65,33 @@ val listener: CompletableJob = GlobalEventChannel.subscribeAlways e.printStackTrace() } +``` + ### Java ```java // 创建监听 Listener listener = GlobalEventChannel.INSTANCE.subscribeAlways(GroupMessageEvent.class, event -> { - event.getSubject().sendMessage("Hello!"); + MessageChain chain = event.getMessage(); // 可获取到消息内容等, 详细查阅 `GroupMessageEvent` + + event.getSubject().sendMessage("Hello!"); // 回复消息 }) listener.complete(); // 停止监听 ``` -异常默认会被相关 Bot 日志记录。可以在 `subscribeAlways` 之前添加如下内容来处理异常。 -``` -// Kotlin -.exceptionHandler { e -> e.printStackTrace() } - -// Java +异常默认会被相关 `Bot` 日志记录。可以在 `subscribeAlways` 之前添加如下内容来处理异常。 +```java .exceptionHandler(e -> e.printStackTrace()) ``` -**`GlobalEventChannel` 会监听到来自所有 `Bot` 的事件,如果只希望监听某一个 bot,请使用 `bot.eventChannel`。** - > 你已经了解了基本事件操作。现在你可以继续阅读通道处理和扩展等内容,或: > > - 跳到下一章 [Messages](Messages.md) -> - [查看事件列表](../mirai-core-api/src/commonMain/kotlin/event/events/README.md#事件) +> - [查看事件列表](EventList.md) > - [回到事件文档目录](#目录) > - [回到 Mirai 文档索引](CoreAPI.md) @@ -477,7 +485,7 @@ MyCoroutineScope.subscribeAlways { ``` val image = when (下一条消息) { 包含图片 { 查询图片链接() } - 包含纯文本 { 下载图片() } + 包含纯文本URL { 下载图片() } 其他情况 { 引用回复() } 超时 { 引用回复() } } @@ -499,7 +507,7 @@ whileSelectMessages { subject.sendMessage("已关闭复读") false // 停止循环 } - // 也可以使用 startsWith("") { true } 等 DSL + // 也可以使用 startsWith("") { ... } 等 DSL default { subject.sendMessage(message) true // 继续循环 diff --git a/docs/Messages.md b/docs/Messages.md index d6fbf0a26..b6e732266 100644 --- a/docs/Messages.md +++ b/docs/Messages.md @@ -9,12 +9,14 @@ - [构造消息链](#构造消息链) - [元素唯一性](#元素唯一性) - [获取消息链中的消息元素](#获取消息链中的消息元素) + - [序列化](#序列化) + - [消息的其他常用功能](#消息的其他常用功能) - [Mirai 码](#mirai-码) - [转义规则](#转义规则) - [消息链的 mirai 码](#消息链的-mirai-码) - [由 `CodableMessage` 取得 mirai 码字符串](#由-codablemessage-取得-mirai-码字符串) - [由 mirai 码字符串取得 `MessageChain` 实例](#由-mirai-码字符串取得-messagechain-实例) - - [`serializeToString` 与 `toString` 的区别](#serializeToString-与-toString-的区别) + - [`serializeToMiraiCode` 与 `toString` 的区别](#serializetomiraicode-与-tostring-的区别) ## 消息系统 @@ -38,18 +40,19 @@ Mirai 支持富文本消息。 ### 内容 -*内容(`MessageContent`)* 即为 *纯文本*、*提及某人*、*图片*、*语音* 和 *音乐分享* 等**有内容**的数据,一条消息中必须包含内容才能发送。 +*内容(`MessageContent`)* 即为 *纯文本*、*提及某人*、*图片*、*语音* 和 *音乐分享* 等**有内容**的数据,一条消息中必须包含内容才能发送。 ### 元数据 *元数据(`MessageMetadata`)* 包含 *来源*、*引用回复* 和 *秀图标识* 等。 -- *消息来源*(`MessageSource`)存在于每条消息中,包含唯一识别信息,用于撤回和引用回复的定位。 -- *引用回复*(`QuoteReply`)若存在,则会在客户端中解析为本条消息引用了另一条消息。 +- *消息来源*(`MessageSource`)存在于每条消息中,包含唯一识别信息,用于撤回和引用回复的定位。 +- *引用回复*(`QuoteReply`)若存在,则会在客户端中解析为本条消息引用了另一条消息。 - *秀图标识*(`ShowImageFlag`)若存在,则表明这条消息中的图片是以秀图发送(QQ 的一个功能)。 元数据与内容的区分就在于,一条消息没有元数据也能显示,但一条消息不能没有内容。**元数据是消息的属性**。 +后文会介绍如何获取元数据。 > 回到 [目录](#目录) @@ -110,7 +113,7 @@ Mirai 支持多种消息类型。 | [`ForwardMessage`] | 合并转发 | `[转发消息]` | 2.0 *(1)* | | [`SimpleServiceMessage`] | (不稳定)服务消息 | `$content` | 2.0 | | [`MusicShare`] | 音乐分享 | `[分享]曲名` | 2.1 | -| [`Dice`] | 骰子 | `[骰子:$value]` | 2.5 | +| [`Dice`] | 魔法表情骰子 | `[骰子:$value]` | 2.5 | | [`FileMessage`] | 文件消息 | `[文件]文件名称` | 2.5 | @@ -207,7 +210,7 @@ val chain = buildMessageChain { +PlainText("a") +AtAll +Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f") - add(At(123456)) + add(At(123456)) // `+` 和 `add` 作用相同 } // chain 结果是包含 PlainText, AtAll, Image, At 的 MessageChain @@ -244,7 +247,25 @@ MessageChain chain = new MessageChainBuilder() 通常要把消息作为字符串处理,在 Kotlin 使用 `message.content` 或在 Java 使用 `message.contentToString()`。 -获取到的字符串表示只包含各 [`MessageContent`] 以官方风格显示的消息内容。如 `"你本次测试的成绩是[图片]"`、`[语音]`、`[微笑]` +获取到的字符串表示只包含各 [`MessageContent`] 以官方风格显示的消息内容。如 `"你本次测试的成绩是[图片]"`、`[语音]`、`[微笑]`。 + +### 处理富文本消息 + +Mirai 不内置富文本消息的处理工具类。`MessageChain` 实现接口 `List`,一个思路是遍历 list 并判断类型处理: +```java +for (element : messageChain) { + if (element instanceof Image) { + // 处理一个 Image + } +} +``` +也可以像数组一样按下标随机访问: +```java +SingleMessage element = messageChain.get(0); +if (element instanceof Image) { + // 处理一个 Image +} +``` ### 元素唯一性 @@ -264,7 +285,7 @@ val chain: MessageChain = source1 + source2 // 结果 chain 只包含一个元素,即右侧的 source2。 ``` -元素唯一性的识别基于 [`MessageKey`]。[`MessageKey`] 拥有多态机制。元素替换时会替换。如 [`HummerMessage`] 的继承关系 +元素唯一性的识别基于 [`MessageKey`]。有些 [`MessageKey`] 拥有多态机制。例如 [`HummerMessage`] 的继承关系 ``` MessageContent ↑ @@ -275,20 +296,34 @@ val chain: MessageChain = source1 + source2 PokeMessage VipFace FlashImage ... ``` -当连接一个 [`VipFace`] 到一个 [`MessageChain`] 时,由于 [`VipFace`] 最上层为 `MessageContent`,消息链中第一个 `MessageContent` 会被(保留顺序地)替换为 [`VipFace`],其他所有 `MessageContent` 都会被删除。 +当连接一个 [`VipFace`] 到一个 [`MessageChain`] 时,由于 [`VipFace`] 最远父类为 `MessageContent`,消息链中第一个 `MessageContent` 会被(保留顺序地)替换为 [`VipFace`],其他所有 `MessageContent` 都会被删除。 ```kotlin -val chain = messageChainOf(quoteReply, plainText, at, atAll) // quoteReply 是 MessageMetadata, 其他三个都是 MessageContent -val result = chain + VipFace(VipFace.AiXin, 1) // VipFace 是 ConstrainSingle,最上层键为 MessageContent,因此替换所有的 MessageContent -// 结果为 [quoteReply, VipFace] +// Kotlin + +val face = VipFace(VipFace.AiXin, 1) // VipFace 是 ConstrainSingle +val chain = messageChainOf(plainText, quoteReply, at, atAll) // quoteReply 是 MessageMetadata, 其他三个都是 MessageContent +val result = chain + face // 右侧的 VipFace 替换掉所有的 MessageContent. 它会存在于第一个 MessageContent 位置. +// 结果为 [VipFace, QuoteReply] ``` +```java +// Java + +VipFace face = new VipFace(VipFace.AiXin, 1); // VipFace 是 ConstrainSingle +MessageChain chain = MessageChain.newChain(plainText, quoteReply, at, atAll); // quoteReply 是 MessageMetadata, 其他三个都是 MessageContent +MessageChain result = chain.plus(); // 右侧的 VipFace 替换掉所有的 MessageContent. 它会存在于第一个 MessageContent 位置. +// 结果为 [VipFace, QuoteReply] +``` + +简单来说,这符合现实的逻辑:一条消息如果包含了语音,就不能同时包含群文件、提及全体成员、文字内容等元素。进行 `chain.plus(voice)` 时,如果消息内容冲突则会发生替换,且总是右侧元素替换左侧元素。 +而如果消息可以同时存在,比如一个纯文本和一个或多个图片相连 `plainText.plus(image1).plus(image2)` 时没有冲突,不会发生替换。结果将会是 `[PlainText, Image, Image]` 的 `MessageChain`。 ### 获取消息链中的消息元素 #### A. 筛选 List [`MessageChain`] 继承接口 `List`。 ```kotlin -val image: Image? = chain.filterIsInstance().firstOrNull() +val image: Image? = chain.findIsInstance() ``` ```java Image image = (Image) chain.stream().filter(Image.class::isInstance).findFirst().orElse(null); @@ -300,7 +335,7 @@ val image: Image? = chain.findIsInstance() val image: Image = chain.firstIsInstance() // 不存在时 NoSuchElementException ``` -#### B. 获取唯一消息 +#### B. 获取唯一元素 如果要获取 `ConstrainSingle` 的消息元素,可以快速通过键获得。 ```kotlin @@ -311,6 +346,8 @@ val quote: QuoteReply = chain.getOrFail(QuoteReply) // 不存在时 NoSuchElemen QuoteReply quote = chain.get(QuoteReply.Key); ``` +一些元数据就可以通过这个方法获得。如上述示例就是在获取引用回复(`QuoteReply`)。如果获取不为 `null` 则表明这条消息包含对其他某条消息的引用。 + > 这是因为 `MessageKey` 一般都以消息元素的 `companion object` 实现 #### C. 使用属性委托 @@ -323,13 +360,125 @@ val image: Image? by chain.orNull() val image: Image? by chain.orElse { /* 返回一个 Image */ } ``` +#### D. 遍历 List +```java +for (SingleMessage message : messageChain) { + // ... +} +``` + +也可以使用 `messageChain.iterator()`。 + ### 序列化 +> 简单地讲,序列化指的是将内存中的对象转换为其他易于存储等的表示方式。如对象变 JSON 字符串、对象变 MiraiCode 字符串。 + 消息可以序列化为 JSON 字符串,使用 `MessageChain.serializeToJsonString` 和 `MessageChain.deserializeFromJsonString`。 +```java + +String json = MessageChain.serializeToJsonString(message); + +MessageChain chain = MessageChain.deserializeFromJsonString(message); + +``` + +#### 使用 kotlinx.serialization + +若要将消息类型使用在其他类型中,如 +```kotlin +data class Foo( + val image: Image +) +``` + +则需要在序列化时为 format 添加 `serializersModule`: +```kotlin +val json = Json { + serializersModule = MessageSerializers.serializersModule +} +``` + +如果遇到 [`Encountered unknown key 'type'.`](https://github.com/mamoe/mirai/issues/1273#issuecomment-850997979) 等序列化错误,请添加: +```kotlin +Json { + serializersModule = MessageSerializers.serializersModule + ignoreUnknownKeys = true // 添加这一行 +} +``` + +#### 元素类型不一定被保留 + +如 _消息源 `MessageSource`_ 有 _在线消息源 `OnlineMessageSource`_ 和 _离线消息源 `OfflineMessageSource`_ 之分。`OnlineMessageSource` 在序列化并反序列化后会变为 `OfflineMessageSource`,因为在线消息源只能从消息回执和消息事件中的 `MessageChain` 得到。 + +但在 API 使用上不会有区别(共有属性获取到的值不会变化)。只是可能需要注意 `equals` 等方法的使用。 + +所有 `Message` 反序列化的结果都是 `MessageChain`,即使原消息可能是 `SingleMessage`。如果原消息是 `SingleMessage`,可在反序列化后通过 `chian.get(0)` 下标访问并强转类型(或者其他更好的方法)。 + +#### 序列化稳定性 + +在旧版本产生的 JSON 字符串在绝大多数情况下可以由新版本 Mirai 读取并反序列化成原 `MessageChain`。如果更新时放弃了对某个旧版本消息元素的支持,将会在更新日志中说明。 + +但这个规则不适用于 _实验性特性_(带有 `@MiraiExperimentalApi` 注解)。任何使用了实验性特性的消息元素都随时可变。 + +### 消息的其他常用功能 + +#### 撤回自己或群员的消息 + +撤回的核心操作是 `MessageSource.recall`。 如果操作目标 `MessageSource` 指代的是自己的消息,那么就撤回该消息,操作群员消息同理。 + +来自 `GroupMessageEvent` 等消息事件的属性 `message` 的 `MessageChain` 都包含 `MessageSource` 元素,指代这条消息本身。那么就可以用来撤回这条消息。 + +```kotlin +// Kotlin +messageSource.recall() +messageChain.recall() // 获取其中 MessageSource 元素并操作 recall + +messageSource.recallIn(3000) // 启动协程, 3 秒后撤回消息. 返回的 AsyncRecallResult 可以获取结果 +messageChain.recallIn(3000) +``` + +```java +// Java +MessageSource.recall(messageSource); +MessageSource.recall(messageChain); // 获取其中 MessageSource 元素并操作 recall + +MessageSource.recallIn(messageSource, 3000) // 启动异步任务, 3 秒后撤回消息. 返回的 AsyncRecallResult 可以获取结果 +MessageSource.recallIn(messageChain, 3000) +``` + +#### 发送带有引用回复的消息 + +引用回复 `QuoteReply` 是一个元数据 `MessageMetadata`。将 `QuoteReply` 实例连接到消息链中,发送后的消息就会引用一条其他消息。 + +例如监听事件并回复一条群消息: + +```kotlin +// Kotlin +bot.eventChannel.subscribeAlways { // this: GroupMessageEvent + subject.sendMessage(message.quote() + "Hi!") // 引用收到的消息并回复 "Hi!", 也可以添加图片等更多元素. +} +``` + +```java +// Java +bot.getEventChannel().subscribeAlways(event -> { + MessageChain chain = new MessageChainBuilder() // 引用收到的消息并回复 "Hi!", 也可以添加图片等更多元素. + .append(new QuoteReply(event.getMessage())) + .append("Hi!") + .build(); + event.getSubject().sendMessage(chain); +}); +``` + +#### 更多消息类型 + +在 [消息元素](#消息元素) 表格中找到你需要的消息元素,然后到源码内注释查看相应的用法说明。 + + ## Mirai 码 -实现了接口 `CodableMessage` 的消息类型支持 mirai 码表示。 +实现了接口 `CodableMessage` 的消息类型支持 mirai 码表示。可以在[下文](#由-codablemessage-取得-mirai-码字符串)找到这些消息类型的列表。 ### 转义规则 @@ -369,7 +518,7 @@ mirai 码内的属性字符串会被转义。 如果不进行转义直接进行 mirai 码拼接 (如: `[mirai:msg:{"msg": [1, 2, 3]}]`), 那么 mirai 码会被错误解析 > 解析结果如下: -> +> > - mirai 码 `[mirai:msg:{"msg": [1, 2, 3]` > - 纯文本 `}]` @@ -419,20 +568,22 @@ MessageChain chain = MiraiCode.deserializeFromMiraiCode("[mirai:atall]"); ### 转义字符串 +若要执行转义(一般没有必要手动这么做): + ```kotlin PlainText("[mirai:atall]").serializeToMiraiCode() // \[mirai\:atall\] ``` ```java -new PlainText("[mirai:atall]").serializeToMiraiCode() // \[mirai\:atall\] +MiraiCode.serializeToMiraiCode(new PlainText("[mirai:atall]")) // \[mirai\:atall\] ``` -### `serializeToString` 与 `toString` 的区别 +### `serializeToMiraiCode` 与 `toString` 的区别 - 如 [消息元素](#消息元素) 所示, `toString()` 会尽可能包含多的信息用于调试作用,**行为可能不确定** -- `toString()` 偏人类可读, `serializeToString()` 偏机器可读 -- `toString()` **没有转义**, `serializeToString()` 有正确转义 -- `serializeToString()` 会跳过 `不支持 mirai 码处理` 的元素 +- `toString()` 偏人类可读, `serializeToMiraiCode()` 偏机器可读 +- `toString()` **没有转义**, `serializeToMiraiCode()` 有正确转义 +- `serializeToMiraiCode()` 会跳过 `不支持 mirai 码处理` 的元素 - `toString()` 基本不可用于 `deserializeMiraiCode`/`MiraiCode.deserializeFromMiraiCode` diff --git a/mirai-core-api/README.md b/mirai-core-api/README.md index 7717d2ff5..d8eb1366f 100644 --- a/mirai-core-api/README.md +++ b/mirai-core-api/README.md @@ -64,7 +64,7 @@ mirai 核心 API 模块。本文档帮助读者了解该模块的主要架构。 ## `net.mamoe.mirai.event.events` -事件列表。[README](src/commonMain/kotlin/event/events/README.md#事件) +事件列表。[README](src/commonMain/kotlin/event/events/EventList.md#事件) ## `net.mamoe.mirai.message`