diff --git a/docs/Contacts.md b/docs/Contacts.md index 89748b9df..3731b4652 100644 --- a/docs/Contacts.md +++ b/docs/Contacts.md @@ -6,7 +6,7 @@ 要主动发送一条消息,调用 `Contact.sendMessage()` 即可。 -可通过 `Bot.getFriend` 或 `Bot.getGroup` 获取相关对象。 +可通过 `Bot.getFriend` 或 `Bot.getGroup` 获取相关对象,也可以通过事件获取。 > 下一步,[Events](Events.md) > diff --git a/docs/Messages.md b/docs/Messages.md index a79d6fae6..d1e4ca8a3 100644 --- a/docs/Messages.md +++ b/docs/Messages.md @@ -1,5 +1,19 @@ # Mirai - Messages +## 目录 +- [消息系统](#消息系统) +- [消息类型](#消息类型) +- [消息元素](#消息元素) +- [Mirai 码](#mirai-码) + - [转义规则](#转义规则) + - [消息链的 mirai 码](#消息链的-mirai-码) + - [由 `CodableMessage` 取得 mirai 码字符串](#由-codablemessage-取得-mirai-码字符串) + - [由 mirai 码字符串取得 `MessageChain` 实例](#由-mirai-码字符串取得-messagechain-实例) +- [消息链](#消息链) + - [构造消息链](#构造消息链) + - [元素唯一性](#元素唯一性) + - [获取消息链中的消息元素](#获取消息链中的消息元素) + ## 消息系统 在 Contacts 章节提到,要发送消息,使用 `Contact.sendMessage(Message)`。 @@ -10,6 +24,266 @@ mirai 提供大量消息链的扩展:[MessageChain.kt](../mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt#L59)。 +> 回到 [目录](#目录) + +## 消息类型 + +*单个消息元素(`SingleMessage`)*分为 *消息内容(`MessageContent`)* 和 *消息元数据(`MessageMetadata`)*。 + +实践中,消息内容和消息元数据会混合存在于消息链中。 + +> 回到 [目录](#目录) + ## 消息元素 -### \ No newline at end of file +Mirai 支持富文本消息。各类型消息元素如下文表格所示。 + +消息拥有三种转换到字符串的表示方式。 +- `toMiraiCode()`: 消息的一种序列化方式,格式为 `[mirai:TYPE:PROP]`,其中 `TYPE` 为消息类型, `PROP` 为属性。 +- `contentToSting()`: QQ 对话框中以纯文本方式会显示的消息内容。无法用纯文字表示的消息会丢失信息,如图片总是 `[图片]`。 +- `toString()`: Java 对象的 `toString()`,会尽可能包含多的信息用于调试作用,**行为可能不确定**。 + +[`PlainText`]: ../mirai-core-api/src/commonMain/kotlin/message/data/At.kt +[`At`]: ../mirai-core-api/src/commonMain/kotlin/message/data/At.kt +[`AtAll`]: ../mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt +[`Face`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Face.kt +[`PokeMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt +[`VipFace`]: ../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt +[`Image`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Image.kt +[`FlashImage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt +[`MarketFace`]: ../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt + +[`MessageSource`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt +[`QuoteReply`]: ../mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt +[`LightApp`]: ../mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt +[`SimpleServiceMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt +[`Voice`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt +[`ForwardMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt + +| 消息类型 | 属性 | 解释 | `contentToString()` | `toMiraiCode()` | +|:------------------------:|:--------------------------------------------|:--------------------|:------------------------------------|:-------------------------------------------------| +| [`PlainText`] | `content: String` | 纯文本 | `$content` | `$content` | +| [`Image`] | `imageId: String` | 自定义图片 | `[图片]` | `[mirai:image:$imageId]` | +| [`At`] | `target: Int` | 提及某人 | `@$target` | `[mirai:at:$target]` | +| [`AtAll`] | | 提及全体成员 | `@全体成员` | `[mirai:atall]` | +| [`Face`] | `id: Int` | 原生表情 | `[表情对应的中文名]` | `[mirai:face:id]` | +| [`FlashImage`] | `image: Image` | 闪照 | `[闪照]` | `[mirai:flash:${image.imageId}]` | +| [`PokeMessage`] | `name: String`, `pokeType: Int` , `id: Int` | 戳一戳消息(消息非动作) | `[戳一戳]` | `[mirai:poke:$name,$pokeType,$id]` | +| [`VipFace`] | `kind: VipFace.Kind`, `count: Int` | VIP 表情 | `[${kind.name}]x$count` | `[mirai:vipface:${kind.id},${kind.name},$count]` | +| [`LightApp`] | `content: String` | 小程序 | `$content` | `[mirai:app:$content]` | +| [`Voice`] | `content: String` | 语音 | `$content` | `[mirai:app:$content]` | +| [`MarketFace`] | `id: Int, name: String` | 商城表情 | `[表情对应的中文名]` | *不支持* | +| [`MessageSource`] | ... | 消息来源元数据 | *空字符串* | *不支持* | +| [`QuoteReply`] | `source: MessageSource` | 引用回复 | *空字符串* | *不支持* | +| [`ForwardMessage`] | ... | 合并转发 | *`[mirai:forward:NOT_IMPLEMENTED]`* | *不支持* | +| [`SimpleServiceMessage`] | `serviceId: Int, content: String` | (不稳定)服务消息 | `$content` | *不支持* | + +***注意:内容会首先被转义,详见 [转义规则](#转义规则)*** + +> 回到 [目录](#目录) + +## Mirai 码 + +实现了接口 `CodableMessage` 的消息类型支持 mirai 码表示。 + +### 转义规则 + +mirai 码内的属性字符串会被转义。 + +| 原字符 | 转义结果字符 | +|:----------:|:---------:| +| `[` | `\[` | +| `]` | `\]` | +| `:` | `\:` | +| `,` | `\,` | +| `\` | `\\` | +| *换行符 \n* | `\n` | +| *换行符 \r* | `\r` | + +### 消息链的 mirai 码 + +消息链 [`MessageChain`] 是多个 [`SingleMessage`] 的集合。[`MessageChain`] 也实现 [`CodableMessage`]。在转换为 mirai 码时所有 [`CodableMessage`] 直接相连: +``` +val chain = messageChainOf(PlainText("plain"), At(123), AtAll) + +chain.toMiraiCode() // "plain[mirai:at:123][mirai:atall]" +``` + +### 由 `CodableMessage` 取得 mirai 码字符串 + +通过 `CodableMessage.toMiraiCode()`。 + +``` +val at = At(123)// 是纯文本 + +at.toMiraiCode() // 结果为 `[mirai:at:123]` +``` + +### 由 mirai 码字符串取得 `MessageChain` 实例 + +``` +// Kotlin +val chain = "[mirai:atall]".parseMiraiCode() + +// Java +MessageChain chain = MiraiCode.parseMiraiCode("[mirai:atall]"); +``` + +> 回到 [目录](#目录) + +## 消息链 + +[`MessageChain`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt +[`SingleMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Message.kt +[`CodableMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt + +前文已经介绍消息链,这里介绍消息链的使用。 + +在 [Contacts 章节](Contacts.md) 提到,要发送消息使用 `Contact.sendMessage`。`Contact.sendMessage` 的定义是: +```kotlin +```kotlin + suspend fun sendMessage(message: Message): MessageReceipt +``` + +要发送简单的单元素消息,使用: +```kotlin +contact.sendMessage(PlainText("Hello!")) +``` +```java +contact.sendMessage(new PlainText("Hello!")); +``` + +要发送多元素消息,可将消息使用 `plus` 操作连接: +```kotlin +contact.sendMessage(PlainText("你要的图片是") + Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f")) // 一个纯文本加一个图片 +``` +```java +contact.sendMessage(new PlainText("你要的图片是:").plus(Image.fromId("/f8f1ab55-bf8e-4236-b55e-955848d7069f"))); // 一个纯文本加一个图片 +``` + +### 构造消息链 + +更复杂的消息则需要构造为消息链。 + +#### 在 Kotlin 构造消息链 + +| 定义 | +|:--------------------------------------------------------| +| `fun Iterable.asMessageChain(): MessageChain` | +| `fun Sequence.asMessageChain(): MessageChain` | +| `fun Array.asMessageChain(): MessageChain` | +| `fun Message.asMessageChain(): MessageChain` | +| `fun messageChainOf(vararg Message): MessageChain` | +| `fun Message.plus(tail: Message): MessageChain` | + +可以使用如上表格所示的方法构造,或使用 DSL builder。 + +每个 `Message.unaryPlus` 都会被加入到结果消息链中。 + +```kotlin +val chain = buildMessageChain { + +PlainText("a") + +AtAll + +Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f") +} +``` + +#### 在 Java 构造消息链 + +| 定义 | +|:---------------------------------------------------------------------| +| `public static MessageChain newChain(Iterable iterable)` | +| `public static MessageChain newChain(Message iterable...)` | +| `public static MessageChain newChain(Iterator iterable...)` | + +方法都位于 `net.mamoe.mirai.message.data.MessageUtils`。 + +使用 `MessageChainBuilder`: +```java +MessageChain chain = new MessageChainBuilder() + .append(new PlainText("a")) + .append(AtAll.INSTANCE) + .append(Image.fromId("/f8f1ab55-bf8e-4236-b55e-955848d7069f")) + .build(); +``` + +### 元素唯一性 + +[`MessageKey`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt +[`ConstrainSingle`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Message.kt#L350-L370 +[`HummerMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt + +部分元素只能单一存在于消息链中。这样的元素实现接口 [`ConstrainSingle`]。 + +唯一的元素例如 *消息元数据* [`MessageSource`],在连接时,新的(右侧)元素会替换旧的(左侧)元素。如: +```kotlin +val source1: MessageSource +val source2: MessageSource + +val chain: MessageChain = source1 + source2 +// 结果 chain 只包含一个元素,即右侧的 source2。 +``` + +元素唯一性的识别基于 [`MessageKey`]。[`MessageKey`] 拥有多态机制。元素替换时会替换。如 [`HummerMessage`] 的继承关系 +``` + MessageContent + ↑ + HummerMessage + ↑ + +------------+-------------+------------+ + | | | | + PokeMessage VipFace FlashImage ... +``` + +当连接一个 [`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] +``` + + +### 获取消息链中的消息元素 + +#### A. 筛选 List +[`MessageChain`] 继承接口 `List`。 +```kotlin +val image: Image? = chain.filterIsInstance().firstOrNull() +``` +```java +Image image = (Image) chain.stream().filter(Image.class::isInstance).findFirst().orElse(null); +``` + +在 Kotlin 要获取第一个指定类型实例还可以使用扩展。 +```kotlin +val image: Image? = chain.findIsInstance() +val image: Image = chain.firstIsInstance() // 不存在时 NoSuchElementException +``` + +#### B. 获取唯一消息 +如果要获取 `ConstrainSingle` 的消息元素,可以快速通过键获得。 + +```kotlin +val quote: QuoteReply? = chain[QuoteReply] // 类似 Map.get +val quote: QuoteReply = chain.getOrFail(QuoteReply) // 不存在时 NoSuchElementException +``` +```java +QuoteReply quote = chain.get(QuoteReply.Key); +``` + +> 这是因为 `MessageKey` 一般都以消息元素的 `companion object` 实现 + +#### C. 使用属性委托 + +可在 Kotlin 使用属性委托。这样的方法与上述方法在性能上等价。 + +```kotlin +val image: Image by chain // 不存在时 NoSuchElementException +val image: Image? by chain.orNull() +val image: Image? by chain.orElse { /* 返回一个 Image */ } +``` + + +> 回到 [目录](#目录) +> +> [回到 Mirai 文档索引](README.md) diff --git a/docs/MiraiCodeSepecification.md b/docs/MiraiCodeSepecification.md deleted file mode 100644 index db3e87d09..000000000 --- a/docs/MiraiCodeSepecification.md +++ /dev/null @@ -1,43 +0,0 @@ -# mirai Code Specification - mirai 码规范 - -> 此文档最后更新于 2020/06/12, 基于 mirai `1.1.0` - -## mirai 码 -mirai 的部分 [消息](../mirai-core-api/src/commonMain/kotlin/message/data/Message.kt) 可以表示为形如 `[mirai:atall]` 的字符串. - -## 变更记录 -- `1.1.0`: 引入 mirai 码于 `mirai-serialization` 模块 -- `1.2.0`: mirai 码集成到 mirai-core。不再需要 `mirai-serialization` 模块。 - -## 格式 - -格式分为有参数和无参数两种. - -### 无参数 - -字符串格式: \[mirai:*typename*\] - -```regex -(?:\[mirai:([^:]+)\]) -``` - -| Message Type | mirai Code Typename | Example | -|:-----------------------------------------------------------------------------------|:--------------------|:----------------| -| [AtAll](../mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt) | atall | `[mirai:atall]` | - -### 有参数 -字符串格式: \[mirai:*名称*:*参数列表*\] -多个参数之间使用逗号分隔, 如 `[mirai:at:123456,test]` - -```regex -(?:\[mirai:([^\]]*)?:(.*?)?\]) -``` - -| Message Type | mirai Code Typename | Params | Example | Note | -|:-----------------------------------------------------------------------------------------------------|:--------------------|:----------------------|:------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [At](../mirai-core-api/src/commonMain/kotlin/message/data/At.kt#L29) | at | `target`, `display` | `[mirai:at:123456,test]` | `target` 为 at 对象的 QQ 账号;
`display` 为官方客户端中 at 显示的内容 | -| [Face](../mirai-core-api/src/commonMain/kotlin/message/data/Face.kt#L20) | face | `id` | `[mirai:face:123]` | `id` 见 [Face.IdList](../mirai-core-api/src/commonMain/kotlin/message/data/Face.kt#L36-L237) | -| [PokeMessage](../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt#L40) | poke | `name`, `type` , `id` | `[mirai:poke:戳一戳,1,-1]` | 详见 [PokeMessage.Types](../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt#L55-L138) | -| [VipFace](../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt#L149) | vipface | `id`, `name`, `count` | `[mirai:vipface:9,榴莲,5]` | 详见 [VipFace.Companion](../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt#L174-L225) | -| [Image](../mirai-core-api/src/commonMain/kotlin/message/data/Image.kt#L35) | image | `imageId` | `[mirai:image:/f8f1ab55-bf8e-4236-b55e-955848d7069f]` | `imageId` 见 [Image.imageId](../mirai-core-api/src/commonMain/kotlin/message/data/Image.kt#L82) | -| [FlashImage](../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt#L234) | flash | `imageId` | `[mirai:flash:/f8f1ab55-bf8e-4236-b55e-955848d7069f]` | `imageId` 见 [Image.imageId](../mirai-core-api/src/commonMain/kotlin/message/data/Image.kt#L82) | diff --git a/docs/README.md b/docs/README.md index 5fd0e6774..ee2589edd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -50,6 +50,8 @@ mirai 官方提供 Kotlin/Java 等 JVM 平台语言开发支持。如果你不 | *酷 Q 插件* | [iTXTech/mirai-native] | 支持酷 Q 插件在 mirai 上运行 | | *酷 Q HTTP* | [yyuueexxiinngg/cqhttp-mirai] | 在 mirai-console 开启酷 Q HTTP 服务。 | +> *想在这里添加你的项目?欢迎提交 PR。* + ### 基于 `mirai-core` 的 SDK - `Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) 基于 mirai-core 的 Lua SDK,并提供了 Java 扩展支持,可在 Lua 中调用 Java 代码开发机器人 @@ -73,3 +75,4 @@ mirai 官方提供 Kotlin/Java 等 JVM 平台语言开发支持。如果你不 - [Bots](Bots.md) - [Contacts](Contacts.md) - [Events](Events.md) +- [Messages](Messages.md) diff --git a/mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt index 60e0ab94f..58fe28e1c 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt @@ -16,12 +16,7 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi /** * 可以使用 mirai 码表示的 [Message] 类型. * - * 使用 `mirai-serialization` 中 `String.parseMiraiCode()` 转回 [Message]. - * - * ## 规范 - * 可在 [MiraiCodeSepecification.md](https://github.com/mamoe/mirai/blob/dev/docs/MiraiCodeSepecification.md) 查看 mirai 码规范. - * - * @suppress 警告: 此 API 可能在任何时刻被改变 + * 从字符串解析 mirai 码:[parseMiraiCode] * * @see At * @see AtAll @@ -34,8 +29,6 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi public interface CodableMessage : Message { /** * 转换为 mirai 码. - * - * @suppress 警告: 此 API 可能在任何时刻被改变 */ public fun toMiraiCode(): String = buildString { appendMiraiCode(this) } diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt index da640cc65..031b638b6 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt @@ -49,9 +49,9 @@ public abstract class AbstractMessageKey( * ↑ * HummerMessage * ↑ - * +------------+-------------+ - * | | | - * PokeMessage VipFace FlashImage + * +------------+-------------+------------+ + * | | | | + * PokeMessage VipFace FlashImage ... * * ``` *