From cccdb3cdf21dc6bdd759ea40d8978c3f035dc233 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 3 Apr 2021 22:39:55 +0800 Subject: [PATCH] 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 * Update mirai-core-api/src/commonMain/kotlin/message/data/Message.kt Co-authored-by: Karlatemp * Update docs for messages * Update docs for messages * Revert inappropriate changes * Fix doc Co-authored-by: Karlatemp --- docs/Messages.md | 14 +- .../src/commonMain/kotlin/IMirai.kt | 10 +- .../commonMain/kotlin/contact/NormalMember.kt | 2 +- .../kotlin/event/events/MessageEvent.kt | 32 ++-- .../kotlin/message/code/MiraiCode.kt | 2 + .../src/commonMain/kotlin/message/data/At.kt | 2 + .../commonMain/kotlin/message/data/AtAll.kt | 11 ++ .../kotlin/message/data/ConstrainSingle.kt | 4 +- .../commonMain/kotlin/message/data/Dice.kt | 2 + .../commonMain/kotlin/message/data/Face.kt | 2 + .../kotlin/message/data/FlashImage.kt | 4 + .../kotlin/message/data/ForwardMessage.kt | 30 +++- .../commonMain/kotlin/message/data/Image.kt | 10 +- .../kotlin/message/data/MarketFace.kt | 2 +- .../commonMain/kotlin/message/data/Message.kt | 162 +++++++++++------- .../kotlin/message/data/MessageChain.kt | 17 ++ .../message/data/MessageChainBuilder.kt | 39 ++++- .../kotlin/message/data/MessageKey.kt | 5 +- .../kotlin/message/data/MessageSource.kt | 83 +++++---- .../message/data/MessageSourceBuilder.kt | 28 ++- .../kotlin/message/data/MusicShare.kt | 44 ++++- .../kotlin/message/data/PlainText.kt | 3 + .../kotlin/message/data/PokeMessage.kt | 2 + .../kotlin/message/data/QuoteReply.kt | 6 + .../kotlin/message/data/RichMessage.kt | 9 +- .../kotlin/message/data/ShowImageFlag.kt | 6 +- .../kotlin/message/data/SingleMessage.kt | 2 +- .../commonMain/kotlin/message/data/VipFace.kt | 2 +- .../commonMain/kotlin/message/data/Voice.kt | 13 +- .../kotlin/utils/ExternalResource.kt | 27 ++- .../kotlin/message/LongMessageInternal.kt | 4 + .../kotlin/message/ReceiveMessageHandler.kt | 4 + 32 files changed, 418 insertions(+), 165 deletions(-) diff --git a/docs/Messages.md b/docs/Messages.md index f3626bdd8de..d964dd9ecdb 100644 --- a/docs/Messages.md +++ b/docs/Messages.md @@ -21,7 +21,8 @@ [![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ) -`SingleMessage` 表示单个消息元素,`MessageChain`(消息链) 是 `List`。主动发送的消息和从服务器接收消息都是 `MessageChain`。 +`SingleMessage` 表示单个消息元素。 +`MessageChain`(消息链) 是 `List`。主动发送的消息和从服务器接收消息都是 `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` 是相等的。 ### 作为字符串处理消息 diff --git a/mirai-core-api/src/commonMain/kotlin/IMirai.kt b/mirai-core-api/src/commonMain/kotlin/IMirai.kt index 6ddefce124d..d9518b7d833 100644 --- a/mirai-core-api/src/commonMain/kotlin/IMirai.kt +++ b/mirai-core-api/src/commonMain/kotlin/IMirai.kt @@ -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 diff --git a/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt b/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt index 0ff4d5e905c..06ff0500f59 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt @@ -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 diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/MessageEvent.kt b/mirai-core-api/src/commonMain/kotlin/event/events/MessageEvent.kt index d07c8579db3..4faa9642238 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/MessageEvent.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/MessageEvent.kt @@ -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] 相同. + * + * 如果在 [GroupMessageEvent] 对 [sender] 发送消息, 将会通过私聊发送给群员, 而不会发送在群内. + * 使用 [subject] 作为消息目标则可以确保消息发送到用户所在的场景. * - * 在回复消息时, 可通过 [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() diff --git a/mirai-core-api/src/commonMain/kotlin/message/code/MiraiCode.kt b/mirai-core-api/src/commonMain/kotlin/message/code/MiraiCode.kt index e621aba7acf..64d7535c4e1 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/code/MiraiCode.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/code/MiraiCode.kt @@ -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 { /** diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/At.kt b/mirai-core-api/src/commonMain/kotlin/message/data/At.kt index a657aecff9e..66e5367828c 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/At.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/At.kt @@ -27,6 +27,8 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi /** * At 一个群成员. 只能发送给一个群. * + * 使用时直接构造 At 实例即可. + * * ## mirai 码支持 * 格式: [mirai:at:*[target]*] * diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt b/mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt index 4b9ca69c399..6b0b512670d 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt @@ -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 码支持 * 格式: [mirai:atall] * diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/ConstrainSingle.kt b/mirai-core-api/src/commonMain/kotlin/message/data/ConstrainSingle.kt index ef1bd381daf..8de0e479860 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/ConstrainSingle.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/ConstrainSingle.kt @@ -22,8 +22,8 @@ package net.mamoe.mirai.message.data * * 实现此接口的元素将会在连接时自动处理替换. * - * 要获取有关键的信息, 查看 [MessageKey]. - * 要获取有关约束的处理方式, 查看 [AbstractPolymorphicMessageKey]. + * - 要获取有关键的信息, 查看 [MessageKey]. + * - 要获取有关约束的处理方式, 查看 [AbstractPolymorphicMessageKey]. */ public interface ConstrainSingle : SingleMessage { /** diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt index 8265fa2eb48..3bc2f36c761 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt @@ -25,6 +25,8 @@ import kotlin.random.nextInt /** * 骰子. * + * 构造 [Dice] 实例即可使用. 也可以通过 [Dice.random] 获得一个随机点数的实例. + * * @since 2.5 */ @Serializable diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Face.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Face.kt index 9033d0602d5..29b2d9d5ab8 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Face.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Face.kt @@ -21,6 +21,8 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi /** * QQ 自带表情 * + * 使用时通过 [Face.JING_YA] 等静态字段得到 ID 然后构造 [Face] 实例. + * * ## mirai 码支持 * 格式: [mirai:face:*[id]*] */ diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt index 194fd799414..c379aee05d6 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt @@ -28,6 +28,10 @@ import net.mamoe.mirai.utils.safeCast * - 在 Kotlin 使用类构造器顶层函数 `FlashImage(image)`. * - 在 Kotlin 使用扩展 [Image.flash]. * + * ## 获得闪照代表的原图片 + * + * 访问属性 [FlashImage.image] + * * ## mirai 码支持 * 格式: [mirai:flash:*[Image.imageId]*] * diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt index a4b39abac42..d74bd35f9e2 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt @@ -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 ) { + /** + * 渲染这个 [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( * ### 移动端 * 在移动客户端将会显示为卡片 * - * ``: [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> diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt index 0a0ade1d542..c374a8d1b50 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt @@ -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 获取更多说明 * diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt index 74d59ee34f1..4eb9a3fb40b 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt @@ -15,7 +15,7 @@ import net.mamoe.mirai.utils.safeCast /** * 商城表情 * - * 除 [Dice] 可以发送外, 目前不支持直接发送,可保存接收到的来自官方客户端的商城表情然后转发. + * 除 [Dice] 可以发送外, 目前不支持直接构造和发送,可保存接收到的来自官方客户端的商城表情然后转发. * * @see Dice */ diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt index 8a21b57cd65..739f08c81d2 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt @@ -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] 实例. * - * ## 获得 [Message] + * [MessageChain] 是链表形式链接的多个 [SingleMessage] 实例, 类似 [List]. * - * 请先根据实际需求确定需要的类型. + * ## [Message] 是不可变的 * - * 特别地, 要以字符串方式处理消息, 可使用 [contentToString] 或 [content] 得到内容字符串. + * 所有类型的 [Message] 都是不可变的 (immutable), 它们被构造后其所有属性的值就已经固定了. 因此在多线程环境使用是安全的. + * 因此 [contentToString], [serializeToJsonString], [MiraiCode.serializeToMiraiCode] 的返回都是不变的. * + * [MessageChain] 的 [contentToString] 会被缓存, 只会在第一次调用时计算. + * + * ## 获得 [Message] * - * - [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!" + * ``` + * + * 注: 若需要拼接较多 [Message], 推荐使用 [MessageChainBuilder] 加快拼接效率 + * + * ### 使用 [MessageChainBuilder] 来构建消息 + * + * 查看 [MessageChainBuilder]. + * + * ### 发送消息 + * + * - [Contact.sendMessage] 接收 [Message] 参数 + * - [Message.sendTo] 是发送的扩展 + * + * ### 处理消息 + * + * 除了直接访问 [Message] 子类的对象外, 有时候可能需要将 [Message] 作为字符串处理, + * 通常可以使用 [contentToString] 方法或 [content] 扩展得到与官方客户端显示格式相同的内容字符串. + * + * #### 文字处理示例 * + * 本示例实现处理以 `#` 开头的消息: * - * ### 在 Java 使用 [Message]: + * 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 diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt index 9f9cfa63d7e..05dad41e9ed 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt @@ -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") diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MessageChainBuilder.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MessageChainBuilder.kt index a2d093eeb1a..c5364d20eba 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageChainBuilder.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageChainBuilder.kt @@ -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()) { 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 14d2cc3705c..580f5677028 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt @@ -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) { diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt index 3d55bc28170..276e00b6857 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt @@ -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 : diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSourceBuilder.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSourceBuilder.kt index 4d66d3e079d..c62a72a70ee 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSourceBuilder.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSourceBuilder.kt @@ -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, diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt index e80a125ae43..fc6453b748e 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt @@ -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] 不稳定. 未来可能会有变更. */ diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/PlainText.kt b/mirai-core-api/src/commonMain/kotlin/message/data/PlainText.kt index e99d4fd8b9b..87a10ca8608 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/PlainText.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/PlainText.kt @@ -24,6 +24,9 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi * * 使用时直接构造即可. [Message] 也可以直接与 [String] 相加, 详见 [Message.plus]. * + * ## mirai 码支持 + * 将 [content] 转义. 而没有 `[mirai:`. + * * @see String.toPlainText */ @Serializable diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/PokeMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/PokeMessage.kt index 7b629d067ce..2e3e1315e3c 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/PokeMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/PokeMessage.kt @@ -23,6 +23,8 @@ import net.mamoe.mirai.utils.castOrNull * * 备注: 这是消息对话框中显示的 "一个手指" 的戳一戳. 类似微信拍一拍的是 [Nudge]. * + * 使用 [PokeMessage] 的静态字段, 而不要手动构造 [PokeMessage] 实例. + * * ## mirai 码支持 * 格式: [mirai:poke:*[name]*,*[pokeType]*,*[id]*] * diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt b/mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt index 38fa77f3fea..71b03918b1f 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt @@ -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 diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt index e318ed1fb3b..8496f2d4efc 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt @@ -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 */ diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/ShowImageFlag.kt b/mirai-core-api/src/commonMain/kotlin/message/data/ShowImageFlag.kt index 9dcfaad01cb..88e39e2ee8f 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/ShowImageFlag.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/ShowImageFlag.kt @@ -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) diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/SingleMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/SingleMessage.kt index d8295e71116..4d4b210600e 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/SingleMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/SingleMessage.kt @@ -46,7 +46,7 @@ public interface SingleMessage : Message { // TODO: 2021/1/10 Make sealed interf /** * 消息元数据, 即不含内容的元素. * - * 这种类型的 [Message] 只表示一条消息的属性. 其子类为 [MessageSource], [QuoteReply] 和 [CustomMessageMetadata] + * 这种类型的 [Message] 只表示一条消息的属性. 其子类如 [MessageSource], [QuoteReply] 和 [CustomMessageMetadata] * * 所有子类的 [contentToString] 都应该返回空字符串. * diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/VipFace.kt b/mirai-core-api/src/commonMain/kotlin/message/data/VipFace.kt index b0616b6591e..8ee00b8bcbe 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/VipFace.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/VipFace.kt @@ -22,7 +22,7 @@ import net.mamoe.mirai.utils.safeCast /** * VIP 表情. * - * 不支持发送. + * 不支持发送, 在发送时会变为纯文本. * * ## mirai 码支持 * 格式: [mirai:vipface:*[Kind.id]*,*[Kind.name]*,*[count]*] diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt index 18d055cba99..7bece538737 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt @@ -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) diff --git a/mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt b/mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt index ddc5a9ea8e1..20fb3f2dbd3 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt @@ -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] 实例, 而不会将语音发送到目标群或好友. + * + * **服务器仅支持音频格式 `silk` 或 `amr`**. 需要调用方手动[关闭资源][ExternalResource.close]. * - * - 请手动关闭输入流 - * - 请使用 amr 或 silk 格式 + * 目前仅支持发送给群. * * @throws OverFileSizeMaxException */ diff --git a/mirai-core/src/commonMain/kotlin/message/LongMessageInternal.kt b/mirai-core/src/commonMain/kotlin/message/LongMessageInternal.kt index df6cff0152d..2925f603734 100644 --- a/mirai-core/src/commonMain/kotlin/message/LongMessageInternal.kt +++ b/mirai-core/src/commonMain/kotlin/message/LongMessageInternal.kt @@ -85,6 +85,10 @@ internal data class ForwardMessageInternal(override val content: String, val res } } +/** + * 在接收解析消息后会经过一层转换的消息. + * @see MessageChain.refine + */ internal interface RefinableMessage : SingleMessage { /** diff --git a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt index cb8ef2b530b..826d28054a6 100644 --- a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt +++ b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt @@ -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,