Forráskód Böngészése

Review docs and improve readability (#1291)

* Review docs and improve readability

* Update docs/Bots.md

Co-authored-by: Karlatemp <karlatemp@vip.qq.com>

* Update docs/Bots.md

Co-authored-by: Karlatemp <karlatemp@vip.qq.com>

* Update docs/Bots.md

Co-authored-by: Karlatemp <karlatemp@vip.qq.com>

* Fix doc

* Fix doc

* Clarify AnonymousMember and NormalMember

* Fix typo

* Add language type

Co-authored-by: Karlatemp <karlatemp@vip.qq.com>
Him188 4 éve
szülő
commit
c03155af6d
6 módosított fájl, 272 hozzáadás és 89 törlés
  1. 62 38
      docs/Bots.md
  2. 0 0
      docs/Contacts.md
  3. 15 15
      docs/EventList.md
  4. 22 14
      docs/Events.md
  5. 172 21
      docs/Messages.md
  6. 1 1
      mirai-core-api/README.md

+ 62 - 38
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 客户端登录后修改密码 |
 
 若以上方案无法解决问题,请尝试 [切换登录协议](#切换登录协议) 和 **[处理滑动验证码](#处理滑动验证码)**。
 

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
docs/Contacts.md


+ 15 - 15
mirai-core-api/src/commonMain/kotlin/event/events/README.md → 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)`
+<!--注释:--> <!--目前还没有这些修改, 因此不展示注释-->
+<!--- 此列表自 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

+ 22 - 14
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<GroupMessageEvent> { 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<GroupMessageEv
 listener.complete() // 停止监听
 ```
 
+异常默认会被相关 `Bot` 日志记录。可以在 `subscribeAlways` 之前添加如下内容来处理异常。
+```
+.exceptionHandler { e -> 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<GroupMessageEvent> {
 ```
 val image = when (下一条消息) {
    包含图片 { 查询图片链接() } 
-   包含纯文本 { 下载图片() }
+   包含纯文本URL { 下载图片() }
    其他情况 { 引用回复() }
    超时 { 引用回复() }
 }
@@ -499,7 +507,7 @@ whileSelectMessages {
         subject.sendMessage("已关闭复读")
         false // 停止循环
     }
-    // 也可以使用 startsWith("") { true } 等 DSL
+    // 也可以使用 startsWith("") { ... } 等 DSL
     default {
         subject.sendMessage(message)
         true // 继续循环

+ 172 - 21
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  *<sup>(1)</sup>* |
 | [`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<SingleMessage>`,一个思路是遍历 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<SingleMessage>`。
 ```kotlin
-val image: Image? = chain.filterIsInstance<Image>().firstOrNull()
+val image: Image? = chain.findIsInstance<Image>()
 ```
 ```java
 Image image = (Image) chain.stream().filter(Image.class::isInstance).findFirst().orElse(null);
@@ -300,7 +335,7 @@ val image: Image? = chain.findIsInstance<Image>()
 val image: Image = chain.firstIsInstance<Image>() // 不存在时 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<GroupMessageEvent> { // this: GroupMessageEvent
+    subject.sendMessage(message.quote() + "Hi!") // 引用收到的消息并回复 "Hi!", 也可以添加图片等更多元素.
+}
+```
+
+```java
+// Java
+bot.getEventChannel().subscribeAlways<GroupMessageEvent>(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`
 
 

+ 1 - 1
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`
 

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott