Update docs for messages (#1142)

* Update docs for messages

* Update docs for messages

* Update docs for messages

* Update docs for messages

* Update docs/Messages.md

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

* Update mirai-core-api/src/commonMain/kotlin/message/data/Message.kt

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

* Update docs for messages

* Update docs for messages

* Revert inappropriate changes

* Fix doc

Co-authored-by: Karlatemp <karlatemp@vip.qq.com>
This commit is contained in:
Him188 2021-04-03 22:39:55 +08:00 committed by GitHub
parent ea1f43b9c5
commit cccdb3cdf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 419 additions and 166 deletions

View File

@ -21,7 +21,8 @@
[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)
`SingleMessage` 表示单个消息元素,`MessageChain`(消息链) 是 `List<SingleMessage>`。主动发送的消息和从服务器接收消息都是 `MessageChain`
`SingleMessage` 表示单个消息元素。
`MessageChain`(消息链) 是 `List<SingleMessage>`。主动发送的消息和从服务器接收消息都是 `MessageChain`
> 回到 [目录](#目录)
@ -53,6 +54,8 @@ Mirai 支持富文本消息。
## 消息元素
Mirai 支持多种消息类型。
消息拥有三种转换到字符串的表示方式。
| 方法 | 解释 |
@ -109,6 +112,8 @@ Mirai 支持富文本消息。
| [`FileMessage`] | 文件消息 | `[文件]文件名称` | 2.5 |
> *(1)*: [`ForwardMessage`] 在 2.0 支持发送, 在 2.3 支持接收
| [`MessageMetadata`] 类型 | 解释 | 最低支持的版本 |
@ -119,9 +124,11 @@ Mirai 支持富文本消息。
| [`RichMessageOrigin`] | 富文本消息源 | 2.3 |
**请打开相关消息类型的源码查看用法。**
### 用法
> *(1)*: [`ForwardMessage`] 在 2.0 支持发送, 在 2.3 支持接收
只需要得到各种类型 `Message` 的实例就可以使用,可以直接发送(`Contact.sendMessage`)也可以连接到消息链中(`Message.plus`)。
可在上文表格中找到需要的类型并在源码内文档获取更多实践上的帮助。
> 回到 [目录](#目录)
@ -224,6 +231,7 @@ MessageChain chain = new MessageChainBuilder()
.build();
```
该示例中 `+` 是位于 `MessageChainBuilder``Message.unaryPlus` 扩展。使用 `+` 和使用 `add` 是相等的。
### 作为字符串处理消息

View File

@ -68,7 +68,13 @@ public interface IMirai : LowLevelApiAccessor {
public var Http: HttpClient
/**
* 获取 uin
* 获取 uin.
*
* - 用户的 uin 就是用户的 ID (QQ 号码, [User.id]).
* - 部分旧群的 uin 需要通过算法计算 [calculateGroupUinByGroupCode]. 新群的 uin 与在客户端能看到的群号码 ([Group.id]) 相同.
*
* 除了一些偏底层的 API [MessageSourceBuilder.id] , mirai 的所有其他 API 都使用在客户端能看到的用户 QQ 号码和群号码 ([Contact.id]). 并会在需要的时候进行合适转换.
* 若需要使用 uin, 在特定方法的文档中会标出.
*/
public fun getUin(contactOrBot: ContactOrBot): Long {
return if (contactOrBot is Group)
@ -78,6 +84,7 @@ public interface IMirai : LowLevelApiAccessor {
/**
* 使用 groupCode 计算 groupUin. 这两个值仅在 mirai 内部协议区分, 一般人使用时无需在意.
* @see getUin
*/
public fun calculateGroupUinByGroupCode(groupCode: Long): Long {
var left: Long = groupCode / 1000000L
@ -95,6 +102,7 @@ public interface IMirai : LowLevelApiAccessor {
/**
* 使用 groupUin 计算 groupCode. 这两个值仅在 mirai 内部协议区分, 一般人使用时无需在意.
* @see getUin
*/
public fun calculateGroupCodeByGroupUin(groupUin: Long): Long {
var left: Long = groupUin / 1000000L

View File

@ -142,7 +142,7 @@ public interface NormalMember : Member {
/**
* 获取非空群名片或昵称.
* @return [User] [NormalMember] 时返回 [Member.nameCardOrNick], 否则返回 [Member.nick]
*/
*/ // Java: NormalMemberKt.getNameCardOrNick(user)
public val User.nameCardOrNick: String
get() = when (this) {
is NormalMember -> this.nameCardOrNick

View File

@ -16,8 +16,6 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.EventChannel
import net.mamoe.mirai.event.subscribe
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
@ -28,9 +26,7 @@ import net.mamoe.mirai.utils.MiraiInternalApi
/**
* 一个 (收到的) 消息事件.
*
* 它是一个 [BotEvent], 因此可以被 [监听][EventChannel.subscribe]
* 一个消息事件.
*
* @see isContextIdenticalWith 判断语境相同
*/
@ -43,13 +39,13 @@ public interface MessageEvent : Event, Packet, BotPassiveEvent { // TODO: 2021/1
/**
* 消息事件主体.
*
* - 对于好友消息, 这个属性为 [Friend] 的实例, [sender] 引用相同;
* - 对于临时会话消息, 这个属性为 [Member] 的实例, [sender] 引用相同;
* - 对于陌生人消息, 这个属性为 [Stranger] 的实例, [sender] 引用相同
* - 对于群消息, 这个属性为 [Group] 的实例, [GroupMessageEvent.group] 引用相同
* - 对于其他客户端消息, 这个属性为 [OtherClient] 的实例, [OtherClientMessageEvent.client] 引用相同
* - 对于私聊会话, 这个属性与 [sender] 相同;
* - 对于群消息, 这个属性为 [Group] 的实例, [GroupMessageEvent.group] 相同.
*
* 在回复消息时, 可通过 [subject] 作为回复对象
* 如果在 [GroupMessageEvent] [sender] 发送消息, 将会通过私聊发送给群员, 而不会发送在群内.
* 使用 [subject] 作为消息目标则可以确保消息发送到用户所在的场景.
*
* 在回复消息时, 可通过 [subject] 作为回复对象.
*/
public val subject: Contact
@ -61,23 +57,24 @@ public interface MessageEvent : Event, Packet, BotPassiveEvent { // TODO: 2021/1
public val sender: User
/**
* 发送人名称
* 发送人名称. 由群员发送时为群员名片, 由好友发送时为好友昵称. 使用 [User.nameCardOrNick] 也能得到相同的结果.
*/
public val senderName: String
/**
* 消息内容.
*
* 第一个元素一定为 [MessageSource], 存储此消息的发送人, 发送时间, 收信人, 消息 ids 等数据.
* 随后的元素为拥有顺序的真实消息内容.
* 返回的消息链中一定包含 [MessageSource], 存储此消息的发送人, 发送时间, 收信人, 消息 ids 等数据. 随后的元素为拥有顺序的真实消息内容.
*
* 详细查看 [MessageChain]
*/
public val message: MessageChain
/** 消息发送时间 (由服务器提供, 可能与本地有时差) */
/** 消息发送时间戳, 单位为秒. 由服务器提供, 可能与本地有时差. */
public val time: Int
/**
* 消息源. 来自 [message] 的第一个元素,
* 消息源. 来自 [message]. 相当于对 [message] [MessageSource] 参数调用 [MessageChain.get].
*/
public val source: OnlineMessageSource.Incoming get() = message.source as OnlineMessageSource.Incoming
}
@ -261,5 +258,8 @@ public class StrangerMessageEvent constructor(
public override fun toString(): String = "StrangerMessageEvent(sender=${sender.id}, message=$message)"
}
/**
* 消息事件的公共抽象父类, 保留将来使用. 这是内部 API, 请不要使用.
*/
@MiraiInternalApi
public abstract class AbstractMessageEvent : MessageEvent, AbstractEvent()

View File

@ -20,6 +20,8 @@ import net.mamoe.mirai.utils.safeCast
/**
* Mirai 码相关操作.
*
* 可在 GitHub 查看 [详细文档](https://github.com/mamoe/mirai/blob/dev/docs/Messages.md#mirai-%E7%A0%81).
*/
public object MiraiCode {
/**

View File

@ -27,6 +27,8 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi
/**
* At 一个群成员. 只能发送给一个群.
*
* 使用时直接构造 At 实例即可.
*
* ## mirai 码支持
* 格式: &#91;mirai:at:*[target]*&#93;
*

View File

@ -23,6 +23,17 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi
*
* 非会员每天只能发送 10 [AtAll]. 超出部分会被以普通文字看待.
*
* ## 使用 [AtAll]
*
* [AtAll] 是单例, [AtAll] 实例[添加][Message.plus]到消息链中即可.
* ```
* // Kotlin
* contact.sendMessage(AtAll + "test")
*
* // Java
* contact.sendMessage(MessageUtils.newChain(AtAll.INSTANCE, new PlainText("test")));
* ```
*
* ## mirai 码支持
* 格式: &#91;mirai:atall&#93;
*

View File

@ -22,8 +22,8 @@ package net.mamoe.mirai.message.data
*
* 实现此接口的元素将会在连接时自动处理替换.
*
* 要获取有关键的信息, 查看 [MessageKey].
* 要获取有关约束的处理方式, 查看 [AbstractPolymorphicMessageKey].
* - 要获取有关键的信息, 查看 [MessageKey].
* - 要获取有关约束的处理方式, 查看 [AbstractPolymorphicMessageKey].
*/
public interface ConstrainSingle : SingleMessage {
/**

View File

@ -25,6 +25,8 @@ import kotlin.random.nextInt
/**
* 骰子.
*
* 构造 [Dice] 实例即可使用. 也可以通过 [Dice.random] 获得一个随机点数的实例.
*
* @since 2.5
*/
@Serializable

View File

@ -21,6 +21,8 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi
/**
* QQ 自带表情
*
* 使用时通过 [Face.JING_YA] 等静态字段得到 ID 然后构造 [Face] 实例.
*
* ## mirai 码支持
* 格式: &#91;mirai:face:*[id]*&#93;
*/

View File

@ -28,6 +28,10 @@ import net.mamoe.mirai.utils.safeCast
* - Kotlin 使用类构造器顶层函数 `FlashImage(image)`.
* - Kotlin 使用扩展 [Image.flash].
*
* ## 获得闪照代表的原图片
*
* 访问属性 [FlashImage.image]
*
* ## mirai 码支持
* 格式: &#91;mirai:flash:*[Image.imageId]*&#93;
*

View File

@ -25,11 +25,22 @@ import net.mamoe.mirai.utils.safeCast
import net.mamoe.mirai.utils.toLongUnsigned
@MiraiExperimentalApi
/**
* 未通过 [DisplayStrategy] 渲染的合并转发消息. [RawForwardMessage] 仅作为一个中间件, 用于 [ForwardMessageBuilder].
*
* [RawForwardMessage] 可以序列化保存, 也可以被多次[渲染][RawForwardMessage.render]产生不同格式的 [ForwardMessage].
*/
@Serializable
@MiraiExperimentalApi
public data class RawForwardMessage(
/**
* 消息列表
*/
val nodeList: List<ForwardMessage.Node>
) {
/**
* 渲染这个 [RawForwardMessage] 并产生可以发送的 [ForwardMessage]
*/
public fun render(displayStrategy: DisplayStrategy): ForwardMessage = ForwardMessage(
preview = displayStrategy.generatePreview(this),
title = displayStrategy.generateTitle(this),
@ -50,11 +61,9 @@ public data class RawForwardMessage(
* ### 移动端
* 在移动客户端将会显示为卡片
*
* `<title>`: [DisplayStrategy.generateTitle]
*
* `<preview>`: [DisplayStrategy.generatePreview]
*
* `<summary>`: [DisplayStrategy.generateSummary]
* - `<title>`: [DisplayStrategy.generateTitle]
* - `<preview>`: [DisplayStrategy.generatePreview]
* - `<summary>`: [DisplayStrategy.generateSummary]
*
* ```
* |-------------------------|
@ -90,6 +99,7 @@ public data class RawForwardMessage(
*
*
* ## 构造
* - 使用构建器 [ForwardMessageBuilder]
* - 使用 [DSL][buildForwardMessage]
* - 通过 [MessageEvent] 集合转换: [toForwardMessage]
*
@ -113,6 +123,8 @@ public data class ForwardMessage(
/**
* 合并转发卡片展示策略. 用于 [RawForwardMessage] [渲染][RawForwardMessage.render].
*
* @see ForwardMessage
*/
public interface DisplayStrategy {
@ -133,7 +145,6 @@ public data class ForwardMessage(
/**
* 显示在卡片 body , 只会显示 sequence 前四个元素.
* Java 用户: 使用 [sequenceOf] (`SequenceKt.sequenceOf`) [asSequence] (`SequenceKt.asSequence`)
*/
public fun generatePreview(forward: RawForwardMessage): List<String> =
forward.nodeList.map { it.senderName + ": " + it.messageChain.contentToString() }
@ -143,6 +154,9 @@ public data class ForwardMessage(
*/
public fun generateSummary(forward: RawForwardMessage): String = "查看 ${forward.nodeList.size} 条转发消息"
/**
* 默认的, 与官方客户端相似的展示方案.
*/
public companion object Default : DisplayStrategy
}
@ -381,7 +395,7 @@ public annotation class ForwardMessageDsl
*/
public class ForwardMessageBuilder private constructor(
/**
* 消息语境. 可为 [Group] [User]
* 消息语境. 可为 [Group] [User]. 用来确定某 ID 的用户的昵称.
*/
public val context: Contact,
private val container: MutableList<ForwardMessage.INode>

View File

@ -113,12 +113,9 @@ public interface Image : Message, MessageContent, CodableMessage {
public const val SERIAL_NAME: String = "Image"
/**
* 通过 [Image.imageId] 构造一个 [Image] 以便发送.
* 这个图片必须是服务器已经存在的图片.
* 通过 [Image.imageId] 构造一个 [Image] 以便发送. 这个图片必须是服务器已经存在的图片.
* 图片 id 不一定会长时间保存, 因此不建议使用 id 发送图片.
*
* 请查看 `ExternalImageJvm` 获取更多创建 [Image] 的方法
*
* @see Image 获取更多说明
* @see Image.imageId 获取更多说明
*/
@ -183,12 +180,9 @@ public interface Image : Message, MessageContent, CodableMessage {
}
/**
* 通过 [Image.imageId] 构造一个 [Image] 以便发送.
* 这个图片必须是服务器已经存在的图片.
* 通过 [Image.imageId] 构造一个 [Image] 以便发送. 这个图片必须是服务器已经存在的图片.
* 图片 id 不一定会长时间保存, 因此不建议使用 id 发送图片.
*
* 请查看 `ExternalImageJvm` 获取更多创建 [Image] 的方法
*
* @see Image 获取更多说明
* @see Image.imageId 获取更多说明
*

View File

@ -15,7 +15,7 @@ import net.mamoe.mirai.utils.safeCast
/**
* 商城表情
*
* [Dice] 可以发送外, 目前不支持直接发送可保存接收到的来自官方客户端的商城表情然后转发.
* [Dice] 可以发送外, 目前不支持直接构造和发送可保存接收到的来自官方客户端的商城表情然后转发.
*
* @see Dice
*/

View File

@ -22,103 +22,120 @@ import kotlinx.coroutines.flow.fold
import kotlinx.serialization.*
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.code.MiraiCode
import net.mamoe.mirai.message.code.MiraiCode.serializeToMiraiCode
import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString
import kotlin.internal.LowPriorityInOverloadResolution
/**
* 可发送的或从服务器接收的消息.
*
* [消息][Message] 分为
* - [SingleMessage]:
* [Message] 派生为 [SingleMessage] [MessageChain].
*
* [SingleMessage] 分为:
* - [MessageMetadata] 消息元数据, 即消息的属性. 包括: [消息来源][MessageSource], [引用回复][QuoteReply] .
* - [MessageContent] 含内容的消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] .
* - [MessageChain]: 不可变消息链, 链表形式链接的多个 [SingleMessage] 实例.
*
* [MessageChain] 是链表形式链接的多个 [SingleMessage] 实例, 类似 [List].
*
* ## [Message] 是不可变的
*
* 所有类型的 [Message] 都是不可变的 (immutable), 它们被构造后其所有属性的值就已经固定了. 因此在多线程环境使用是安全的.
* 因此 [contentToString], [serializeToJsonString], [MiraiCode.serializeToMiraiCode] 的返回都是不变的.
*
* [MessageChain] [contentToString] 会被缓存, 只会在第一次调用时计算.
*
* ## 获得 [Message]
*
* 请先根据实际需求确定需要的类型.
*
* 特别地, 要以字符串方式处理消息, 可使用 [contentToString] [content] 得到内容字符串.
*
*
* - [PlainText]: 纯文本
* - [Image]: 图片
* - [Face]: 原生表情
* - [At]: 一个群成员的引用
* - [AtAll]: 全体成员的引用
* - [QuoteReply]: 一条消息的引用
* - [RichMessage]: 富文本消息, [XML JSON][ServiceMessage], [小程序][LightApp]
* - [FlashImage]: 闪照
* - [PokeMessage]: 戳一戳 (消息)
* - [VipFace]: VIP 表情
* - [CustomMessage]: 自定义消息类型
* - ...
* 查看 [Message] 子类. 或在 GitHub 查看 [表格](https://github.com/mamoe/mirai/blob/dev/docs/Messages.md#%E6%B6%88%E6%81%AF%E5%85%83%E7%B4%A0).
*
* ## 使用 [Message]
*
* ### Kotlin 使用 [Message]:
* 与使用 [String] 的使用类似.
* ### 转换为 [MessageChain]
*
* - 比较 [SingleMessage] [String]:
* `if(message.content == "你好") friend.sendMessage(event)`
* [MessageChain] 为多个 [SingleMessage] 的集合. [Message] 可能表示 single 也可能表示 chain. 可以通过 [toMessageChain] [Message] 转换为 [MessageChain] 统一处理.
*
* - 连接 [Message] [Message], [String], (使用操作符 [Message.plus]):
* ### 连接两个或多个 [Message]
*
* Kotlin, 使用操作符 [Message.plus]:
* ```
* val text = PlainText("Hello ") + PlainText("world") + "!"
* friend.sendMessage(text) // "Hello world!"
* ```
* 但注意: 不能 `String + Message`. 只能 `Message + String`
*
* Java, 使用 [plus]:
* ```
* MessageChain text = new PlainText("Hello ")
* .plus(new PlainText("world"))
* .plus("!");
* friend.sendMessage(text); // "Hello world!"
* ```
*
* ### Java 使用 [Message]:
* : 若需要拼接较多 [Message], 推荐使用 [MessageChainBuilder] 加快拼接效率
*
* ### 使用 [MessageChainBuilder] 来构建消息
*
* 查看 [MessageChainBuilder].
*
* ### 发送消息
*
* - [Contact.sendMessage] 接收 [Message] 参数
* - [Message.sendTo] 是发送的扩展
*
* ### 处理消息
*
* 除了直接访问 [Message] 子类的对象外, 有时候可能需要将 [Message] 作为字符串处理,
* 通常可以使用 [contentToString] 方法或 [content] 扩展得到与官方客户端显示格式相同的内容字符串.
*
* #### 文字处理示例
*
* 本示例实现处理以 `#` 开头的消息:
*
* Kotlin:
* ```
* val msg = event.message
* val content = msg.content.trim()
* if (content.startsWith("#")) {
* val name = content.substringAfter("#", "")
* when(name) {
* "mute" -> event.sender.mute(60000) // 发 #mute 就把自己禁言 1 分钟
* }
* }
* ```
*
* Java:
* ```
* MessageChain msg = event.message;
* String content = msg.contentToString();
* if (!content.equals("#") && content.startsWith("#")) {
* String name = content.substring(content.indexOf('#') + 1); // `#` 之后的内容
* switch(name) {
* "mute": event.sender.mute(60000) // 发 #mute 就把自己禁言 1 分钟
* }
* }
* ```
*
* 若使用 Java 对象的 [toString], 会得到包含更多信息. 因此 [toString] 结果可能会随着 mirai 更新变化. [toString] 不适合用来处理消息. 只适合用来调试输出.
*
* [Message] 还提供了 [Mirai ][MiraiCode] [JSON][MessageChain.serializeToJsonString] 序列化方式. 可在 [MessageChain] 文档详细了解它们.
*
* ### 发送消息
* - 通过 [Contact] 中的成员函数: [Contact.sendMessage]
* - 通过 [Message] 的扩展函数: [Message.sendTo]
*
* @see PlainText 纯文本
* @see Image 图片
* @see Face 原生表情
* @see At 一个群成员的引用
* @see AtAll 全体成员的引用
* @see QuoteReply 一条消息的引用
* @see RichMessage 富文本消息, [XML JSON][ServiceMessage], [小程序][LightApp]
* @see HummerMessage 一些特殊的消息, [闪照][FlashImage], [戳一戳][PokeMessage], [VIP表情][VipFace]
* @see CustomMessage 自定义消息类型
*
* @see MessageChain 消息链( `List<Message>`)
* @see buildMessageChain 构造一个 [MessageChain]
*
* @see Contact.sendMessage 发送消息
*
* @suppress **注意:** [Message] 类型大多有隐藏的协议实现, 不能被第三方应用继承,
* @suppress **注意:** [Message] 类型大多有隐藏的协议实现, 不能被第三方应用继承.
*/
public interface Message { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
/**
* `this` [tail] 连接.
* 得到包含 mirai 消息元素代码的, 易读的字符串. `At(member) + "test"` 将转为 `"[mirai:at:qqId]test"`.
*
* 连接后可以保证 [ConstrainSingle] 的元素单独存在.
*
* :
* ```
* val a = PlainText("Hello ")
* val b = PlainText("world!")
* val c: MessageChain = a + b
* println(c) // "Hello world!"
* ```
*
* Java 使用 [plus]
*
* @see plus `+` 操作符重载
*/
@JvmSynthetic // in java they should use `plus` instead
public fun followedBy(tail: Message): MessageChain = followedByImpl(tail)
/**
* 得到包含 mirai 消息元素代码的, 易读的字符串. `At(member) + "test"` 将转为 `"[mirai:at:qqId]test"`
*
* 在使用消息相关 DSL 和扩展时, 一些内容比较的实现均使用的是 [contentToString] 而不是 [toString]
* 在使用消息相关 DSL 和扩展时, 一些内容比较的实现均使用的是 [contentToString] 而不是 [toString].
*
* 各个消息类型的转换示例:
* - [PlainText] : `"Hello"`
@ -135,7 +152,10 @@ public interface Message { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.
/**
* 转为最接近官方格式的字符串. `At(member) + "test"` 将转为 `"@群名片 test"`.
*
* 在使用消息相关 DSL 和扩展时, 一些内容比较的实现均使用 [contentToString] 而不是 [toString]
* 在使用消息相关 DSL 和扩展时, 一些内容比较的实现均使用 [contentToString] 而不是 [toString].
*
* 由于消息元素都是不可变的, [contentToString] 的返回也是不变的.
* [MessageChain] [contentToString] 会被缓存, 只会在第一次调用时计算.
*
* 各个消息类型的转换示例:
* - [PlainText] : `"Hello"`
@ -201,6 +221,26 @@ public interface Message { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.
return this.contentToString().equals(another, ignoreCase = ignoreCase)
}
/**
* `this` [tail] 连接.
*
* 连接后可以保证 [ConstrainSingle] 的元素单独存在.
*
* :
* ```
* val a = PlainText("Hello ")
* val b = PlainText("world!")
* val c: MessageChain = a + b
* println(c) // "Hello world!"
* ```
*
* Java 使用 [plus]
*
* @see plus `+` 操作符重载
*/
@JvmSynthetic // in java they should use `plus` instead
public fun followedBy(tail: Message): MessageChain = followedByImpl(tail)
/** 将 [another] 按顺序连接到这个消息的尾部. */
public operator fun plus(another: MessageChain): MessageChain = this + another as Message

View File

@ -134,7 +134,18 @@ import kotlin.streams.asSequence
*
* 相关地还可以使用 [MessageChain.contains] [MessageChain.getOrFail]
*
* ## 直接索引访问
*
* [MessageChain] 实现接口 [List], 可以通过索引 `get(index)` 来访问. 由于 [MessageChain] 是稳定的, 这种访问操作也是稳定的.
*
* 但在处理来自服务器的 [MessageChain] , 请尽量避免这种直接索引访问. 来自服务器的消息的组成有可能会变化, 可能会有新的 [MessageMetadata] 加入.
* 例如用户发送了两条内容相同的消息, 但其中一条带有引用回复而另一条没有, 则两条消息的索引可能有变化 (当然内容顺序不会改变, 只是 [QuoteReply] 的位置有可能会变化).
* 因此在使用直接索引访问时要格外注意兼容性, 故不推荐这种访问方案.
*
* ## 撤回和引用
*
* 要撤回消息, 查看 [MessageSource]
*
* - [MessageSource.quote]
* - [MessageSource.recall]
* - [MessageSource.recallIn]
@ -424,6 +435,12 @@ public inline fun <reified M : SingleMessage> MessageChain.anyIsInstance(): Bool
/**
* 返回一个包含 [messages] 所有元素的消息链, 保留顺序.
*
* ```
* val chain = messageChainOf(messageChainOf(AtAll, new PlainText("")), messageChainOf(Image(""), QuoteReply()))
* ```
* 将会得到 `chain` `[AtAll, PlainText, Image, QuoteReply]`
*
* @see buildMessageChain
*/
@JvmName("newChain")

View File

@ -17,7 +17,7 @@ import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract
/**
* 构建一个 [MessageChain]
* 构建一个 [MessageChain]. 用法查看 [MessageChainBuilder].
*
* @see MessageChainBuilder
*/
@ -27,7 +27,7 @@ public inline fun buildMessageChain(block: MessageChainBuilder.() -> Unit): Mess
}
/**
* 使用特定的容器大小构建一个 [MessageChain]
* 使用特定的容器大小构建一个 [MessageChain]. 用法查看 [MessageChainBuilder].
*
* @see MessageChainBuilder
*/
@ -38,11 +38,36 @@ public inline fun buildMessageChain(initialSize: Int, block: MessageChainBuilder
/**
* [MessageChain] 构建器.
* 多个连续的 [String] 会被连接为单个 [PlainText] 以优化性能.
*
*
* **注意:** 无并发安全性.
*
* ### 连续 String 优化
*
* 多个连续的 [String] 会被连接为单个 [PlainText] 以优化性能
*
* ## Kotlin 示例
*
* ```
* val chain = buildMessageChain {
* +PlainText("a")
* +AtAll
* +Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f")
* add(At(123456))
* }
* ```
*
* 该示例中 `+` [MessageChainBuilder.unaryPlus]. 使用 `+` 和使用 `add` 是相等的.
*
* ## Java 示例
* ```java
* MessageChain chain = new MessageChainBuilder()
* .append(new PlainText("string"))
* .append("string") // 会被构造成 PlainText 再添加, 相当于上一行
* .append(AtAll.INSTANCE)
* .append(Image.fromId("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png"))
* .build();
* ```
*
* @see buildMessageChain 推荐使用
* @see asMessageChain 完成构建
*/
@ -166,8 +191,10 @@ public class MessageChainBuilder private constructor(
return container.set(index, element)
}
///////
// IMPLEMENTATION
/**
* 缓存通过 `add(String)` 添加的字符串, 将连续的字符串连接为一个 [PlainText]
*/
private val cache: StringBuilder = StringBuilder()
private fun flushCache() {
if (cache.isNotEmpty()) {

View File

@ -85,10 +85,7 @@ public fun MessageKey<*>.isInstance(message: SingleMessage): Boolean = this.safe
/**
* 获取最上层 [MessageKey].
*
* [this][MessageKey] [AbstractPolymorphicMessageKey]
*
* [FlashImage], [MessageKey]
* @see AbstractPolymorphicMessageKey
*/
public val <A : SingleMessage> MessageKey<A>.topmostKey: MessageKey<*>
get() = when (this) {

View File

@ -37,29 +37,40 @@ import net.mamoe.mirai.utils.safeCast
*
*
* ## 组成
* [MessageSource] 定位属性, 发信人和收信人, 内容 组成
* [MessageSource] 由以下属性组成:
* - 三个*定位属性* [ids], [internalId], [time]
* - 发送人 ID [fromId]
* - 收信人 ID [targetId]
* - 原消息内容 [originalMessage]
*
* ### 定位属性
* - [ids] 消息 ids (序列号)
* - [internalIds] 消息内部 ids
* - [time] 时间
* 官方客户端通过这三个*定位属性*来准确定位消息, 撤回和引用回复都是如此 (有这三个属性才可以精确撤回和引用某个消息).
*
* 官方客户端通过这三个属性定位消息, 撤回和引用回复都是如此.
*
* ### 发信人和收信人
* - [fromId] 消息发送人
* - [targetId] 消息发送目标
*
* ### 内容
* - [originalMessage] 消息内容
* 即使三个*定位属性*就可以知道原消息是哪一条, 但服务器和官方客户端都实现为读取 [originalMessage] 的内容.
* 也就是说, 如果[引用][quote]一个 [MessageSource], *定位属性*只会被用来支持跳转到原消息, 引用中显示的被引用消息内容只取决于 [originalMessage].
* 可以通过修改 [originalMessage] 来达到显示的内容与跳转内容不符合的效果. 但一般没有必要这么做.
*
* ## 获取
* - 来自 [MessageEvent.message] [MessageChain] 总是包含 [MessageSource]. 可通过 [MessageChain.get] 获取 [MessageSource]:
* ```
* val source = chain[MessageSource]
* // Kotlin
* val source: MessageSource? = chain[MessageSource]
* val notNull: MessageSource = chain.source // 可能抛出 NoSuchElementException
* ```
* - 构造离线消息源 [IMirai.constructMessageSource]
* ```
* // Java
* MessageSource source = chain.get(MessageSource.Key);
* ```
* - 构造离线消息源: [IMirai.constructMessageSource]
* - 使用构建器构造: [MessageSourceBuilder]
*
* ### "修改" 一个 [MessageSource]
* [MessageSource] 是不可变的. 因此不能修改其中属性, 但可以通过 [MessageSource.copyAmend] 或者 [MessageSourceBuilder.allFrom] 来复制一个.
* ```
* MessageSource newSource = new MessageSourceBuilder()
* .allFrom(source) // 从 source 继承所有数据
* .message(new PlainText("aaa")) // 覆盖消息
* .build();
* ```
*
* ## 使用
*
@ -68,6 +79,24 @@ import net.mamoe.mirai.utils.safeCast
* 对于来自 [MessageEvent.message] [MessageChain], 总是包含 [MessageSource].
* 因此也可以对这样的 [MessageChain] 进行 [引用回复][MessageChain.quote] [撤回][MessageChain.recall].
*
* ### Kotlin 示例
* ```
* val source: MessageSource = ...
* source.recall() // 通过 MessageSource 撤回
*
* val event: MessageEvent = ...
* event.message.recall() // 也可以通过来自服务器的 [MessageChain] 撤回, 因为这些 chain 包含 [MessageSource]
* ```
*
* ### Java 示例
* ```
* val source: MessageSource = ...
* source.recall() // 通过 MessageSource 撤回
*
* val event: MessageEvent = ...
* event.message.recall() // 也可以通过来自服务器的 [MessageChain] 撤回, 因为这些 chain 包含 [MessageSource]
* ```
*
*
* @see IMirai.recallMessage 撤回一条消息
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
@ -90,9 +119,6 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
/**
* 消息 ids (序列号). 在获取失败时 (概率很低) 为空数组.
*
* ### 值域
* 值的范围约为 [UShort] 的范围.
*
* ### 顺序
* 群消息的 id 由服务器维护. 好友消息的 id mirai 维护.
* id 不一定从 0 开始.
@ -128,7 +154,7 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
public abstract val time: Int
/**
* 发送人.
* 发送人用户 ID.
*
* - [OnlineMessageSource.Outgoing] 时为 [机器人][Bot.id]
* - [OnlineMessageSource.Incoming] 时为发信 [来源用户][User.id] [][Group.id]
@ -137,7 +163,7 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
public abstract val fromId: Long
/**
* 消息发送目标.
* 消息发送目标用户或群号码.
*
* - [OnlineMessageSource.Outgoing] 时为发信 [目标用户][User.id] [][Group.id]
* - [OnlineMessageSource.Incoming] 时为 [机器人][Bot.id]
@ -146,9 +172,9 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
public abstract val targetId: Long // groupCode / friendUin / memberUin
/**
* 原消息内容.
* source 指代的原消息内容.
*
* 此属性是 **lazy** : 它只会在第一次调用时初始化, 因为需要反序列化服务器发来的整个包, 相当于接收了一条新消息.
* 此属性是惰性初始化: 它只会在第一次调用时初始化, 因为需要反序列化服务器发来的整个包, 相当于接收了一条新消息.
*/
@LazyProperty
public abstract val originalMessage: MessageChain
@ -233,7 +259,7 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
}
/**
* 引用这条消息
* 引用这条消息.
* @see QuoteReply
*/
@JvmStatic
@ -535,12 +561,11 @@ public sealed class OnlineMessageSource : MessageSource() { // TODO: 2021/1/10 E
}
/**
* 由一条消息中的 [QuoteReply] 得到的 [MessageSource].
* 此消息源可能来自一条与机器人无关的消息. 因此无法提供对象化的 `sender` `target` 获取.
* 由一条消息中的 [QuoteReply] 得到的, 或通过 [MessageSourceBuilder] 手动构建的 [MessageSource].
*
* @see buildMessageSource 构建一个 [OfflineMessageSource]
* @see IMirai.constructMessageSource
* @see OnlineMessageSource.toOffline
* 此消息源可能来自一条与机器人无关的消息, 因此缺少相关发送环境信息, 无法提供 `sender` `target` 对象的获取.
*
* 要获得 [OfflineMessageSource], 使用 [MessageSourceBuilder]. 或通过 [OnlineMessageSource.toOffline] 转换得到 (一般没有必要).
*/
public abstract class OfflineMessageSource : MessageSource() { // TODO: 2021/1/10 Extract to separate file in Kotlin 1.5
public companion object Key :

View File

@ -118,19 +118,26 @@ public inline fun Bot.buildMessageSource(
* target(target)
* metadata(source) // 从另一个消息源复制 ids, internalIds, time
*
* time(System.currentTimeMillis())
* // 也可以不设置 time, 则会使用当前系统时间
*
* messages { // 指定消息内容
* +"hi"
* }
*
* messages(messageChain) // 也可以赋值一个 MessageChain
* }
* ```
*
* Kotlin 也可以使用
*
* Java:
* ```java
* MessageSourceBuilder
* .create()
* new MessageSourceBuilder()
* .from(bot)
* .target(target)
* .metadata(source) // 从另一个消息源复制 ids, internalIds, time
* .time(System.currentTimeMillis()) // 也可以不设置, 则会使用当前系统时间
* .messages(new PlainText("hi"))
* .build(botId, MessageSourceKind.FRIEND);
* ```
@ -194,10 +201,17 @@ public open class MessageSourceBuilder public constructor() {
this.originalMessages.addAll(source.originalMessage)
}
/**
* 添加消息. 不会清空已有消息.
*/
public fun messages(messages: Iterable<Message>): MessageSourceBuilder = apply {
this.originalMessages.addAll(messages)
}
/**
* 添加消息. 不会清空已有消息.
*/
public fun messages(vararg message: Message): MessageSourceBuilder = apply {
for (it in message) {
this.originalMessages.add(it)
@ -212,13 +226,14 @@ public open class MessageSourceBuilder public constructor() {
public fun clearMessages(): MessageSourceBuilder = apply { this.originalMessages.clear() }
/**
* 设置发信人
* 设置发信人.
*/
public fun sender(sender: ContactOrBot): MessageSourceBuilder = apply {
this.fromId = sender.id
}
/**
* 设置发信人. 需使用 uin.
* @see IMirai.getUin
*/
public fun sender(uin: Long): MessageSourceBuilder = apply {
@ -233,15 +248,22 @@ public open class MessageSourceBuilder public constructor() {
}
/**
* 设置发信目标. 需使用 uin.
* @see IMirai.getUin
*/
public fun target(uin: Long): MessageSourceBuilder = apply {
this.targetId = uin
}
/**
* 同时设置 [sender] [target]
*/
public fun setSenderAndTarget(sender: ContactOrBot, target: ContactOrBot): MessageSourceBuilder =
sender(sender).target(target)
/**
* 构建生成 [OfflineMessageSource]
*/
public fun build(botId: Long, kind: MessageSourceKind): OfflineMessageSource {
return Mirai.constructMessageSource(
botId,

View File

@ -20,7 +20,9 @@ import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.safeCast
/**
* QQ 互联通道音乐分享
* QQ 互联通道音乐分享.
*
* 构造实例即可使用.
*
* @since 2.1
*/
@ -32,31 +34,57 @@ public data class MusicShare(
*/
public val kind: MusicKind, // 'type' is reserved by serialization
/**
* 消息卡片标题
* 消息卡片标题. 例如 `"ファッション"`
*/
public val title: String,
/**
* 消息卡片内容
* 消息卡片内容. 例如 `"rinahamu/Yunomi"`
*/
public val summary: String,
/**
* 点击卡片跳转网页 URL
* 点击卡片跳转网页 URL. 例如 `"http://music.163.com/song/1338728297/?userid=324076307"`
*/
public val jumpUrl: String,
/**
* 消息卡片图片 URL
* 消息卡片图片 URL. 例如 `"http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg"`
*/
public val pictureUrl: String,
/**
* 音乐文件 URL
* 音乐文件 URL. 例如 `"http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307"`
*/
public val musicUrl: String,
/**
* 在消息列表显示
* 在消息列表显示. 例如 `"[分享]ファッション"`
*/
public val brief: String,
) : MessageContent, ConstrainSingle, CodableMessage {
/*
* 想试试? 可以构造:
// Kotlin
MusicShare(
kind = MusicKind.NeteaseCloudMusic,
title = "ファッション",
summary = "rinahamu/Yunomi",
brief = "",
jumpUrl = "http://music.163.com/song/1338728297/?userid=324076307",
pictureUrl = "http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg",
musicUrl = "http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307"
)
// Java
new MusicShare(
MusicKind.NeteaseCloudMusic,
"ファッション",
"rinahamu/Yunomi",
"http://music.163.com/song/1338728297/?userid=324076307",
"http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg",
"http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307"
);
*/
public constructor(
/**
* 音乐应用类型
@ -103,8 +131,6 @@ public data class MusicShare(
}
// MusicShare(type=NeteaseCloudMusic, title='ファッション', summary='rinahamu/Yunomi', brief='', url='http://music.163.com/song/1338728297/?userid=324076307', pictureUrl='http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg', musicUrl='http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307')
/**
* 注意, baseKey [MessageContent] 不稳定. 未来可能会有变更.
*/

View File

@ -24,6 +24,9 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi
*
* 使用时直接构造即可. [Message] 也可以直接与 [String] 相加, 详见 [Message.plus].
*
* ## mirai 码支持
* [content] 转义. 而没有 `[mirai:`.
*
* @see String.toPlainText
*/
@Serializable

View File

@ -23,6 +23,8 @@ import net.mamoe.mirai.utils.castOrNull
*
* 备注: 这是消息对话框中显示的 "一个手指" 的戳一戳. 类似微信拍一拍的是 [Nudge].
*
* 使用 [PokeMessage] 的静态字段, 而不要手动构造 [PokeMessage] 实例.
*
* ## mirai 码支持
* 格式: &#91;mirai:poke:*[name]*,*[pokeType]*,*[id]*&#93;
*

View File

@ -39,8 +39,14 @@ import net.mamoe.mirai.utils.safeCast
@Serializable
@SerialName(QuoteReply.SERIAL_NAME)
public data class QuoteReply(
/**
* 指代被引用的消息. 其中 [MessageSource.originalMessage] 可以控制客户端显示的消息内容.
*/
public val source: MessageSource
) : Message, MessageMetadata, ConstrainSingle {
/**
* 从消息链中获取 [MessageSource] 并构造.
*/
public constructor(sourceMessage: MessageChain) : this(sourceMessage.getOrFail(MessageSource))
public override val key: MessageKey<QuoteReply> get() = Key

View File

@ -22,7 +22,9 @@ import net.mamoe.mirai.utils.safeCast
import kotlin.annotation.AnnotationTarget.*
/**
* XML, JSON 消息等富文本消息
* XML, JSON 消息等富文本消息.
*
* 通常构造 [LightApp] [ServiceMessage]
*
* **注意**: 富文本消息的 [RichMessage.contentEquals] [RichMessage.toString] 都不稳定. 将来可能在没有任何警告的情况下改变格式.
*
@ -87,7 +89,7 @@ public interface RichMessage : MessageContent, ConstrainSingle {
}
/**
* 小程序, 如音乐分享.
* 小程序.
*
* 大部分 JSON 消息为此类型, 另外一部分为 [ServiceMessage]
*
@ -154,8 +156,11 @@ public class SimpleServiceMessage(
/**
* 服务消息, 可以是 JSON 消息或 XML 消息.
*
* XML 消息有时候是 [SimpleServiceMessage], 有时候是 [LightApp].
* JSON 消息更多情况下通过 [LightApp] 发送.
*
* 建议使用官方客户端发送来确定具体是哪种类型.
*
* @see LightApp 小程序类型消息
* @see SimpleServiceMessage
*/

View File

@ -27,13 +27,15 @@ import net.mamoe.mirai.utils.safeCast
*
*
* ```
* MessageEvent event
* MessageEvent event;
*
* if (event.message.contains(ShowImageFlag)) {
* if (event.message.contains(ShowImageFlag.INSTANCE)) {
* // event.message 包含的图片是作为 '秀图' 发送
* }
* ```
*
* 发送 [ShowImageFlag] 不会有任何效果.
*
* @since 2.2
*/
@SerialName(ShowImageFlag.SERIAL_NAME)

View File

@ -46,7 +46,7 @@ public interface SingleMessage : Message { // TODO: 2021/1/10 Make sealed interf
/**
* 消息元数据, 即不含内容的元素.
*
* 这种类型的 [Message] 只表示一条消息的属性. 其子类 [MessageSource], [QuoteReply] [CustomMessageMetadata]
* 这种类型的 [Message] 只表示一条消息的属性. 其子类 [MessageSource], [QuoteReply] [CustomMessageMetadata]
*
* 所有子类的 [contentToString] 都应该返回空字符串.
*

View File

@ -22,7 +22,7 @@ import net.mamoe.mirai.utils.safeCast
/**
* VIP 表情.
*
* 不支持发送.
* 不支持发送, 在发送时会变为纯文本.
*
* ## mirai 码支持
* 格式: &#91;mirai:vipface:*[Kind.id]*,*[Kind.name]*,*[count]*&#93;

View File

@ -11,6 +11,7 @@ package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsVoice
import net.mamoe.mirai.utils.MiraiExperimentalApi
@ -19,7 +20,9 @@ import net.mamoe.mirai.utils.safeCast
/**
* 需要通过上传到服务器的消息如语音文件
* 需要通过上传到服务器的消息如语音文件.
*
* @suppress 不要使用这个接口. 目前只应该使用 [Voice].
*/
@Serializable
@MiraiExperimentalApi
@ -42,7 +45,13 @@ public abstract class PttMessage : MessageContent {
* 语音消息, 目前只支持接收和转发
*
* 目前, 使用 [Voice] 类型是稳定的, 但调用 [Voice] 中的属性 [fileName], [md5], [fileSize] 是不稳定的. 语音的序列化也可能会在未来有变动.
* 可安全地通过 [ExternalResource.uploadAsVoice] 上传语音并使用.
*
* ## 使用语音
*
* 可以通过 [ExternalResource.uploadAsVoice] 或者 [Group.uploadVoice] 上传语音文件到服务器, 得到 [Voice] 实例. 但这不会发送给目标群.
* 上传后需要通过 [Group.sendMessage] 发送 [Voice] 实例.
*
* [Voice] 实例可以通过序列化方式保存. 下次可以用它发送因而不需要上传. 但可能由于未来服务器更新, 这项功能就不稳定. 因此建议总是上传音频文件而不要保存 [Voice].
*/
@Serializable // experimental
@SerialName(Voice.SERIAL_NAME)

View File

@ -266,6 +266,11 @@ public interface ExternalResource : Closeable {
/**
* 上传文件并获取文件消息.
*
* 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型.
*
* 需要调用方手动[关闭资源][ExternalResource.close].
*
* @param path 远程路径. 起始字符为 '/'. '/foo/bar.txt'
* @since 2.5
* @see RemoteFile.path
@ -281,7 +286,12 @@ public interface ExternalResource : Closeable {
): FileMessage = toExternalResource().use { contact.uploadFile(path, it, callback) }
/**
* 上传文件并获取文件消息. 无论上传是否成功, 本函数都不会关闭资源.
* 上传文件并获取文件消息.
*
* 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型.
*
* 需要调用方手动[关闭资源][ExternalResource.close].
*
* @param path 远程路径. 起始字符为 '/'. '/foo/bar.txt'
* @since 2.5
* @see RemoteFile.path
@ -299,6 +309,9 @@ public interface ExternalResource : Closeable {
/**
* 上传文件并发送文件消息.
*
* 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型.
*
* @param path 远程路径. 起始字符为 '/'. '/foo/bar.txt'
* @since 2.5
* @see RemoteFile.path
@ -314,7 +327,10 @@ public interface ExternalResource : Closeable {
): MessageReceipt<C> = toExternalResource().use { contact.sendFile(path, it, callback) }
/**
* 上传文件并发送件消息. 无论上传是否成功, 本函数都不会关闭资源.
* 上传文件并发送件消息. 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型.
*
* 需要调用方手动[关闭资源][ExternalResource.close].
*
* @param path 远程路径. 起始字符为 '/'. '/foo/bar.txt'
* @since 2.5
* @see RemoteFile.path
@ -331,10 +347,11 @@ public interface ExternalResource : Closeable {
): MessageReceipt<C> = contact.sendFile(path, this, callback)
/**
* 将文件作为语音上传后构造 [Voice].
* 将文件作为语音上传后构造 [Voice]. 上传后只会得到 [Voice] 实例, 而不会将语音发送到目标群或好友.
*
* - 请手动关闭输入流
* - 请使用 amr silk 格式
* **服务器仅支持音频格式 `silk` `amr`**. 需要调用方手动[关闭资源][ExternalResource.close].
*
* 目前仅支持发送给群.
*
* @throws OverFileSizeMaxException
*/

View File

@ -85,6 +85,10 @@ internal data class ForwardMessageInternal(override val content: String, val res
}
}
/**
* 在接收解析消息后会经过一层转换的消息.
* @see MessageChain.refine
*/
internal interface RefinableMessage : SingleMessage {
/**

View File

@ -90,6 +90,10 @@ private fun List<MsgComm.Msg>.toMessageChain(
return builder.build().cleanupRubbishMessageElements()
}
/**
* 接收消息的解析器. [MsgComm.Msg] 转换为对应的 [SingleMessage]
* @see joinToMessageChain
*/
private object ReceiveMessageTransformer {
fun createMessageSource(
bot: Bot,