commonMain.message.data.MessageChain.kt Maven / Gradle / Ivy
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:JvmMultifileClass
@file:JvmName("MessageUtils")
@file:Suppress("unused", "NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME")
package net.mamoe.mirai.message.data
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.message.code.MiraiCode
import net.mamoe.mirai.message.code.MiraiCode.deserializeMiraiCode
import net.mamoe.mirai.message.data.MessageChain.Companion.deserializeFromMiraiCode
import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString
import net.mamoe.mirai.message.data.MessageSource.Key.quote
import net.mamoe.mirai.message.data.MessageSource.Key.recall
import net.mamoe.mirai.message.data.MessageSource.Key.recallIn
import net.mamoe.mirai.utils.DeprecatedSinceMirai
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.safeCast
import java.util.stream.Stream
import kotlin.reflect.KProperty
import kotlin.streams.asSequence
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_ABSTRACT_MESSAGE_KEYS as RAMK
/**
* 消息链, `List`, 即 [单个消息元素][SingleMessage] 的有序集合.
*
* [MessageChain] 代表一条完整的聊天中的消息, 可包含 [带内容的消息 `MessageContent`][MessageContent]
* 和 [不带内容的元数据 `MessageMetadata`][MessageMetadata].
*
* # 元素类型
*
* [MessageContent] 如 [纯文字][PlainText], [图片][Image], [语音][Voice], 是能被用户看到的内容.
*
*
* [MessageMetadata] 是用来形容这条消息的状态的数据, 因此称为 *元数据 (metadata)*.
* 元数据目前只分为 [消息来源 `MessageSource`][MessageSource] 和 [引用回复 `QuoteReply`][QuoteReply].
*
* [MessageSource] 存储这条消息的发送人, 接收人, 识别 ID (服务器提供), 发送时间等信息.
* **[MessageSource] 是精确的**. 凭 [MessageSource] 就可以在服务器上定位一条消息, 因此可以用来 [撤回消息][MessageSource.recall].
*
* [QuoteReply] 是一个标记, 表示这条消息引用了另一条消息 (在官方客户端中可通过 "回复" 功能发起引用). [QuoteReply.source] 则指代那条被引用的消息.
* 由于 [MessageSource] 是精确的, 如果对 [QuoteReply.source] 使用 [MessageSource.recall], 则可以撤回那条被引用的消息.
*
*
* # 获得消息链
*
* 在消息事件中可以获得消息内容作为 [MessageChain]: [MessageEvent.message]
*
* 在主动发送消息时, 可使用如下方案.
*
* ## 在 Kotlin 构造消息链
* - 获取不包含任何元素的消息链: [EmptyMessageChain]
* - [messageChainOf][messageChainOf]: 类似 [listOf], 将多个 [Message] 构造为 [MessageChain]:
* ```
* val chain = messageChainOf(PlainText("..."), Image("..."), ...)
* ```
* - [buildMessageChain][buildMessageChain]: 使用 DSL 构建器.
* ```
* val chain = buildMessageChain {
* +"你想要的图片是:"
* +Image("...")
* }
* ```
* - [Message.plus][Message.plus]: 将两个消息相连成为一个消息链:
* ```
* val chain = PlainText("Hello ") + PlainText("Mirai!") // chain: MessageChain
* ```
* - [toMessageChain][toMessageChain] 将 [Iterable], [Array], [Sequence], [Iterator], [Flow], [Stream] 转换为 [MessageChain].
* 相关定义为:
* ```
* public fun Sequence.toMessageChain(): MessageChain
* public fun Iterable.toMessageChain(): MessageChain
* public fun Iterator.toMessageChain(): MessageChain
* public fun Stream.toMessageChain(): MessageChain
* public fun Flow.toMessageChain(): MessageChain
* public fun Array.toMessageChain(): MessageChain
* ```
* - [Message.toMessageChain][Message.toMessageChain] 将单个 [Message] 包装成一个单元素的 [MessageChain]
*
* ## 在 Java 构造消息链
* - `MessageUtils.newChain`: 有多个重载, 相关定义如下:
* ```java
* public static MessageChain newChain(Message messages...)
* public static MessageChain newChain(Iterable iterable)
* public static MessageChain newChain(Iterator iterator)
* public static MessageChain newChain(Stream stream)
* public static MessageChain newChain(Message[] array)
* ```
* - [Message.plus][Message.plus]: 将两个消息相连成为一个消息链:
* ```java
* MessageChain chain = new PlainText("Hello ").plus(new PlainText("Mirai!"))
* ```
* - [MessageChainBuilder][MessageChainBuilder]:
* ```java
* MessageChainBuilder builder = MessageChainBuilder.create();
* builder.append(new PlainText("Hello "));
* builder.append(new PlainText(" Mirai!"));
* MessageChain chain = builder.build();
* ```
*
* # 元素唯一性
*
* 部分消息类型如 [语音][Voice], [小程序][LightApp] 在官方客户端限制中只允许单独存在于一条消息. 在创建 [MessageChain] 时这种限制会被体现.
*
* 当添加只允许单独存在的消息元素到一个消息链时, 已有的元素可能会被删除或替换. 详见 [AbstractPolymorphicMessageKey] 和 [ConstrainSingle].
*
* # 操作消息链
*
* [MessageChain] 继承 `List`. 可以以 [List] 的方式处理 [MessageChain].
*
* 额外地, 若要获取一个 [ConstrainSingle] 的元素, 可以通过 [ConstrainSingle.key]:
* ```
* val quote = chain[QuoteReply] // Kotlin
*
* QuoteReply quote = chain.get(QuoteReply.Key) // Java
* ```
*
* 相关地还可以使用 [MessageChain.contains] 和 [MessageChain.getOrFail]
*
* ## 直接索引访问
*
* [MessageChain] 实现接口 [List], 可以通过索引 `get(index)` 来访问. 由于 [MessageChain] 是稳定的, 这种访问操作也是稳定的.
*
* 但在处理来自服务器的 [MessageChain] 时, 请尽量避免这种直接索引访问. 来自服务器的消息的组成有可能会变化, 可能会有新的 [MessageMetadata] 加入.
* 例如用户发送了两条内容相同的消息, 但其中一条带有引用回复而另一条没有, 则两条消息的索引可能有变化 (当然内容顺序不会改变, 只是 [QuoteReply] 的位置有可能会变化).
* 因此在使用直接索引访问时要格外注意兼容性, 故不推荐这种访问方案.
*
* ## 撤回和引用
*
* 要撤回消息, 查看 [MessageSource]
*
* - [MessageSource.quote]
* - [MessageSource.recall]
* - [MessageSource.recallIn]
* - `MessageChain.quote`
* - `MessageChain.recall`
* - `MessageChain.recallIn`
*
* ## Kotlin 扩展
*
* ### 属性委托
* ```
* val at: At? by chain.orNull()
* val at: At by chain.orElse { /* 返回一个 At */ }
* val at: At by chain
* ```
*
* ### 筛选得到 [Sequence] 与 [List]
* - [MessageChain.contentsSequence]
* - [MessageChain.metadataSequence]
* - [MessageChain.contentsList]
* - [MessageChain.metadataList]
*
* # 序列化
*
* ## kotlinx-serialization 序列化
*
* - 使用 [MessageChain.serializeToJsonString] 将 [MessageChain] 序列化为 JSON [String].
* - 使用 [MessageChain.deserializeFromJsonString] 将 JSON [String] 反序列化为 [MessageChain].
*
* ## Mirai Code 序列化
*
* 详见 [MiraiCode]
*
* - 使用 [MessageChain.serializeToMiraiCode] 将 [MessageChain] 序列化为 Mirai Code [String].
* - 使用 [MessageChain.deserializeFromMiraiCode] 将 Mirai Code [String] 反序列化为 [MessageChain].
*
*/
@Serializable(MessageChain.Serializer::class)
@NotStableForInheritance
public sealed interface MessageChain :
Message, List, RandomAccess, CodableMessage {
/**
* 获取第一个类型为 [key] 的 [Message] 实例. 若不存在此实例, 返回 `null`.
*
* 此方法目前仅适用于 [ConstrainSingle] 的消息类型, 如 [MessageSource].
*
* ### Kotlin 使用方法
* ```
* val chain: MessageChain = ...
*
* val source = chain[MessageSource] // MessageSource 为伴生对象
* ```
*
* ### Java 使用方法
* ```java
* MessageChain chain = ...
* chain.get(MessageSource.Key)
* ```
*
* @param key 由各个类型消息的伴生对象持有. 如 [MessageSource.Key]
*
* @see MessageChain.getOrFail 在找不到此类型的元素时抛出 [NoSuchElementException]
*/
public operator fun get(@ResolveContext(RAMK) key: MessageKey): M? {
@Suppress("UNCHECKED_CAST")
return firstOrNull { key.safeCast.invoke(it) != null } as M?
}
/**
* 当存在 [ConstrainSingle.key] 为 [key] 的 [SingleMessage] 实例时返回 `true`.
*
* 此方法目前仅适用于 [ConstrainSingle] 的消息类型, 如 [MessageSource].
*
* ### Kotlin 使用方法
* ```
* val chain: MessageChain = ...
*
* if (chain.contains(QuoteReply)) {
* // 包含引用回复
* }
* ```
*
* ### Java 使用方法
* ```java
* MessageChain chain = ...
* if (chain.contains(QuoteReply.Key)) {
* // 包含引用回复
* }
* ```
*
* @param key 由各个类型消息的伴生对象持有. 如 [MessageSource.Key]
*
* @see MessageChain.getOrFail 在找不到此类型的元素时抛出 [NoSuchElementException]
*/
public operator fun contains(@ResolveContext(RAMK) key: MessageKey): Boolean =
any { key.safeCast.invoke(it) != null }
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
forEach { it.safeCast()?.appendMiraiCodeTo(builder) }
}
/**
* 将 [MessageChain] 作为 `List` 序列化. 使用 [多态序列化][Polymorphic].
*
* 在实践时请提供 [MessageSerializers.serializersModule] 到指定 [SerialFormat].
*
* @see ListSerializer
* @see MessageSerializers
*/
public object Serializer : KSerializer {
@Suppress("DEPRECATION_ERROR")
private val delegate = ListSerializer(PolymorphicSerializer(SingleMessage::class))
override val descriptor: SerialDescriptor = delegate.descriptor
override fun deserialize(decoder: Decoder): MessageChain = delegate.deserialize(decoder).toMessageChain()
override fun serialize(encoder: Encoder, value: MessageChain): Unit = delegate.serialize(encoder, value)
}
public companion object {
private fun getDefaultJson() = Json {
serializersModule =
MessageSerializers.serializersModule // don't convert to property, serializersModule is volatile.
ignoreUnknownKeys = true
}
/**
* 从 JSON 字符串解析 [MessageChain]
* @param json 需要包含 [MessageSerializers.serializersModule]
* @see serializeToJsonString
*/
@JvmOverloads
@JvmStatic
public fun deserializeFromJsonString(
string: String,
json: Json = getDefaultJson()
): MessageChain {
return json.decodeFromString(Serializer, string)
}
/**
* 从 JSON 字符串解析 [MessageChain]
* @param json 需要包含 [MessageSerializers.serializersModule]
* @see deserializeFromJsonString
* @see serializeToJsonString
*/
@JvmSynthetic
@JvmStatic
public inline fun String.deserializeJsonToMessageChain(json: Json): MessageChain =
deserializeFromJsonString(this, json)
/**
* 从 JSON 字符串解析 [MessageChain]
* @see serializeToJsonString
* @see deserializeFromJsonString
*/
@JvmSynthetic
@JvmStatic
public inline fun String.deserializeJsonToMessageChain(): MessageChain = deserializeFromJsonString(this)
/**
* 将 [MessageChain] 序列化为 JSON 字符串.
* @see deserializeFromJsonString
*/
@JvmOverloads
@JvmStatic
public fun MessageChain.serializeToJsonString(
json: Json = getDefaultJson()
): String = json.encodeToString(Serializer, this)
/**
* 将 [MessageChain] 序列化为指定格式的字符串.
*
* @see serializeToJsonString
* @see StringFormat.encodeToString
*/
@ExperimentalSerializationApi
@JvmStatic
public fun MessageChain.serializeToString(format: StringFormat): String =
format.encodeToString(Serializer, this)
/**
* 解析形如 "[mirai:]" 的 mirai 码, 即 [CodableMessage.serializeToMiraiCode] 返回的内容.
* @see MiraiCode.deserializeMiraiCode
*/
@JvmStatic
public fun MessageChain.deserializeFromMiraiCode(miraiCode: String, contact: Contact? = null): MessageChain =
miraiCode.deserializeMiraiCode(contact)
}
}
/**
* 不含任何元素的 [MessageChain].
*/
//@Serializable(MessageChain.Serializer::class)
public object EmptyMessageChain : MessageChain, List by emptyList() {
override val size: Int get() = 0
override fun toString(): String = ""
override fun contentToString(): String = ""
override fun serializeToMiraiCode(): String = ""
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
}
override fun equals(other: Any?): Boolean = other === this
override fun hashCode(): Int = 1
override fun iterator(): Iterator = EmptyMessageChainIterator
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated(
"Serializers for EmptyMessageChain is not provided any more. " +
"Please specify your serial property as MessageChain and use contextual and polymorphic serializers from MessageSerializers.serializersModule.",
level = DeprecationLevel.WARNING
) // deprecated since 2.8-M1
@DeprecatedSinceMirai(warningSince = "2.8")
public fun serializer(): KSerializer = MessageChain.Serializer
private object EmptyMessageChainIterator : Iterator {
override fun hasNext(): Boolean = false
override fun next(): Nothing = throw NoSuchElementException("EmptyMessageChain is empty.")
}
}
// region accessors
/**
* 获取第一个类型为 [key] 的 [Message] 实例, 在找不到此类型的元素时抛出 [NoSuchElementException]
*
* @param key 由各个类型消息的伴生对象持有. 如 [MessageSource.Key]
*/
@JvmSynthetic
public inline fun MessageChain.getOrFail(
@ResolveContext(RAMK) key: MessageKey,
crossinline lazyMessage: (key: MessageKey) -> String = { key.toString() }
): M = get(key) ?: throw NoSuchElementException(lazyMessage(key))
/**
* 获取 `Sequence`
* 相当于 `this.asSequence().filterIsInstance()`
*/
@JvmSynthetic
public fun MessageChain.contentsSequence(): Sequence =
this.asSequence().filterIsInstance()
/**
* 获取 `Sequence`
* 相当于 `this.asSequence().filterIsInstance()`
*/
@JvmSynthetic
public fun MessageChain.metadataSequence(): Sequence =
this.asSequence().filterIsInstance()
/**
* 筛选 [MessageMetadata]
*/
public fun MessageChain.metadataList(): List = this.filterIsInstance()
/**
* 筛选 [MessageContent]
*/
public fun MessageChain.contentsList(): List = this.filterIsInstance()
/**
* 获取第一个 [M] 实例. 在不存在时返回 `null`.
*/
@JvmSynthetic
public inline fun MessageChain.findIsInstance(): M? =
this.find { it is M } as M?
/**
* 获取第一个 [M] 实例. 在不存在时返回 `null`.
* @see findIsInstance
*/
@JvmSynthetic
public inline fun MessageChain.firstIsInstanceOrNull(): M? =
this.find { it is M } as M?
/**
* 获取第一个 [M] 实例. 在不存在时抛出 [NoSuchElementException].
* @see findIsInstance
*/
@JvmSynthetic
public inline fun MessageChain.firstIsInstance(): M = this.first { it is M } as M
/**
* 当 [this] 中存在 [M] 的实例时返回 `true`.
*/
@JvmSynthetic
public inline fun MessageChain.anyIsInstance(): Boolean = this.any { it is M }
// endregion accessors
// region toMessageChain
/**
* 返回一个包含 [messages] 所有元素的消息链, 保留顺序.
*
* ```
* val chain = messageChainOf(messageChainOf(AtAll, new PlainText("")), messageChainOf(Image(""), QuoteReply()))
* ```
* 将会得到 `chain` 为 `[AtAll, PlainText, Image, QuoteReply]`
*
* @see buildMessageChain
*/
@JvmName("newChain")
public inline fun messageChainOf(vararg messages: Message): MessageChain = messages.toMessageChain()
/**
* 扁平化 [this] 并创建一个 [MessageChain].
*/
@JvmName("newChain")
public fun Sequence.toMessageChain(): MessageChain =
createMessageChainImplOptimized(this.constrainSingleMessages())
/**
* 扁平化 [this] 并创建一个 [MessageChain].
*/
@JvmName("newChain")
public fun Stream.toMessageChain(): MessageChain = this.asSequence().toMessageChain()
/**
* 扁平化 [this] 并创建一个 [MessageChain].
*/
@JvmName("newChain")
public suspend fun Flow.toMessageChain(): MessageChain =
buildMessageChain { collect { add(it) } }
/**
* 扁平化 [this] 并创建一个 [MessageChain].
*/
@JvmName("newChain")
public inline fun Iterable.toMessageChain(): MessageChain = this.asSequence().toMessageChain()
/**
* 扁平化 [this] 并创建一个 [MessageChain].
*/
@JvmName("newChain")
public inline fun Iterator.toMessageChain(): MessageChain = this.asSequence().toMessageChain()
/**
* 扁平化 [this] 并创建一个 [MessageChain].
*/
@JvmSynthetic
// no JvmName because 'fun messageChainOf(vararg messages: Message)'
public inline fun Array.toMessageChain(): MessageChain = this.asSequence().toMessageChain()
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNCHECKED_CAST")
@kotlin.internal.LowPriorityInOverloadResolution // prefer Iterable.toMessageChain() for MessageChain
@JvmName("newChain")
public fun Message.toMessageChain(): MessageChain = when (this) {
is MessageChain -> (this as List).toMessageChain()
else -> MessageChainImpl(
listOf(
this as? SingleMessage ?: error("Message is either MessageChain nor SingleMessage: $this")
)
)
}
// region delegate
/**
* 提供一个类型的值的委托. 若不存在则会抛出异常 [NoSuchElementException]
*
* 用法:
* ```
* val message: MessageChain
*
* val at: At by message
* val image: Image by message
* ```
*/
@JvmSynthetic
public inline operator fun MessageChain.getValue(thisRef: Any?, property: KProperty<*>): T =
this.firstIsInstance()
/**
* 可空的委托
* @see orNull
*/
@JvmInline
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
public value class OrNullDelegate @PublishedApi internal constructor(@JvmField @PublishedApi internal val value: Any?) {
@Suppress("UNCHECKED_CAST") // don't inline, IC error
public operator fun getValue(thisRef: Any?, property: KProperty<*>): R = value as R
}
/**
* 提供一个类型的 [Message] 的委托, 若不存在这个类型的 [Message] 则委托会提供 `null`
*
* 用法:
* ```
* val message: MessageChain
*
* val at: At? by message.orNull()
* ```
* @see orNull 提供一个不存在则 null 的委托
* @see orElse 提供一个不存在则使用默认值的委托
*/
@JvmSynthetic
public inline fun MessageChain.orNull(): OrNullDelegate =
OrNullDelegate(this.firstIsInstanceOrNull())
/**
* 提供一个类型的 [Message] 的委托, 若不存在这个类型的 [Message] 则委托会提供 `null`
*
* 用法:
* ```
* val message: MessageChain
*
* val at: At by message.orElse { /* 返回一个 At */ }
* val atNullable: At? by message.orElse { /* 返回一个 At? */ }
* ```
* @see orNull 提供一个不存在则 null 的委托
*/
@Suppress("RemoveExplicitTypeArguments")
@JvmSynthetic
public inline fun MessageChain.orElse(
lazyDefault: () -> R
): OrNullDelegate = OrNullDelegate(this.firstIsInstanceOrNull() ?: lazyDefault())
// endregion delegate
© 2015 - 2025 Weber Informatics LLC | Privacy Policy