[Review] Messages:

- Optimize empty message chain on builders
- Remove `QuoteReply.bot`
- Code style
- Docs
This commit is contained in:
Him188 2021-01-10 17:58:07 +08:00
parent b4b70f028b
commit a2d9dbfcc4
18 changed files with 998 additions and 671 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
* 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.
@ -21,7 +21,7 @@ import net.mamoe.mirai.Bot
* @see Contact
* @see Bot
*/
public interface ContactOrBot : CoroutineScope {
public interface ContactOrBot : CoroutineScope { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
/**
* QQ 号或群号.
*/

View File

@ -11,10 +11,12 @@ package net.mamoe.mirai.message
import kotlinx.serialization.ContextualSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.*
import net.mamoe.mirai.internal.message.MessageSerializersImpl
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.utils.MiraiExperimentalApi
import kotlin.reflect.KClass
@ -22,6 +24,13 @@ import kotlin.reflect.KClass
/**
* 消息序列化器.
*
* [MessageSerializers] 存放 [SerializersModule], 用于协助 [SingleMessage.Serializer] 的多态序列化.
*
* 要序列化一个 [MessageChain], 请使用内建的 [MessageChain.serializeToJsonString]
*
* @see serializersModule
*
*
* @see SingleMessage.Serializer
* @see MessageChain.Serializer
*
@ -31,12 +40,17 @@ public interface MessageSerializers {
/**
* 包含 [SingleMessage] 多态序列化和 [Message] [ContextualSerializer] 信息的 [SerializersModule].
*
* 在序列化消息时都需要
* 在序列化消息时都需要提供给相关 [Json] 配置的 [Json.serializersModule]. 如通过:
* ```
* val json = Json {
* serializesModule = MessageSerializers.serializersModule
* }
* ```
*/
public val serializersModule: SerializersModule
/**
* 注册一个 [SerializersModuleBuilder.contextual] [SingleMessage] 多态域的 [PolymorphicModuleBuilder.subclass]
* 注册一个 [SerializersModuleBuilder.contextual] [SingleMessage] 多态域的 [PolymorphicModuleBuilder.subclass].
*
* 相当于
* ```
@ -45,6 +59,9 @@ public interface MessageSerializers {
* subclass(baseClass, serializer)
* }
* ```
*
*
* 若要自己实现消息类型, 务必在这里注册对应序列化器, 否则在 [MessageChain.serializeToJsonString] 时将会出错.
*/
@MiraiExperimentalApi
public fun <M : SingleMessage> registerSerializer(baseClass: KClass<M>, serializer: KSerializer<M>)

View File

@ -18,8 +18,6 @@ import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.utils.MiraiExperimentalApi
private const val displayA = "@全体成员"
/**
* "@全体成员".
*
@ -34,30 +32,25 @@ private const val displayA = "@全体成员"
@Serializable
public object AtAll :
MessageContent, CodableMessage {
public const val display: String = displayA
public const val display: String = "@全体成员"
public const val SERIAL_NAME: String = "AtAll"
@Suppress("SpellCheckingInspection")
public override fun toString(): String = "[mirai:atall]"
public override fun contentToString(): String = display
public override fun equals(other: Any?): Boolean {
return other === this
}
override fun contentToString(): String = display
override fun toString(): String = "[mirai:atall]"
override fun toMiraiCode(): String = toString()
override fun toMiraiCode(): String {
return toString()
}
override fun hashCode(): Int = display.hashCode()
override fun equals(other: Any?): Boolean = other === this
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append(toString())
}
public override fun hashCode(): Int {
return display.hashCode()
}
// 自动为消息补充 " "
@JvmSynthetic
public override fun followedBy(tail: Message): MessageChain {
if (tail is PlainText && tail.content.startsWith(' ')) {
return super<MessageContent>.followedBy(tail)

View File

@ -192,6 +192,8 @@ public fun <T : CustomMessage> T.toByteArray(): ByteArray {
* 2. 添加伴生对象, 继承 [CustomMessage.ProtoBufSerializerFactory] [CustomMessage.JsonSerializerFactory], [CustomMessage.Factory]
* 3. 在需要解析消息前调用一次伴生对象以注册
*
* 注意: 这是实验性 API. 可能会在未来发生变动.
*
* @see CustomMessage 查看更多信息
* @see ConstrainSingle 可实现此接口以保证消息链中只存在一个元素
*/

View File

@ -9,6 +9,7 @@
@file:JvmMultifileClass
@file:JvmName("MessageUtils")
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.message.data
@ -28,20 +29,21 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi
public data class Face(public val id: Int) : // used in delegation
MessageContent, CodableMessage {
public override fun toString(): String = "[mirai:face:$id]"
public val name: String get() = contentToString().let { it.substring(1, it.length - 1) }
public override fun contentToString(): String = names.getOrElse(id) { "[表情]" }
override fun toString(): String = toMiraiCode()
override fun contentToString(): String = names.getOrElse(id) { "[表情]" }
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append("[mirai:face:").append(id).append(']')
}
public override fun equals(other: Any?): Boolean = other is Face && other.id == this.id
public override fun hashCode(): Int = id
override fun equals(other: Any?): Boolean = other is Face && other.id == this.id
override fun hashCode(): Int = id
//Auto generated
@Suppress("NonAsciiCharacters", "unused", "SpellCheckingInspection", "all")
@Suppress("NonAsciiCharacters", "unused", "SpellCheckingInspection", "all", "ObjectPropertyName")
public companion object {
public const val SERIAL_NAME: String = "Face"
@ -480,7 +482,10 @@ public data class Face(public val id: Int) : // used in delegation
public const val : Int = QING
public const val ZHENG_YAN: Int = 289
public const val 睁眼: Int = ZHENG_YAN
internal val names: Array<String> = Array(290) { "[表情]" }
@JvmField
public val names: Array<String> = Array(290) { "[表情]" }
init {
names[JING_YA] = "[惊讶]"

View File

@ -0,0 +1,91 @@
/*
* 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/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.safeCast
/**
* 闪照. 闪照的内容取决于 [image] 代表的图片.
*
* ## 构造闪照
*
* 需要首先获得普通图片才能构造闪照. 详见 [Image].
*
* - 使用 [FlashImage.from] 将普通图片转换为闪照.
* - Kotlin 使用类构造器顶层函数 `FlashImage(image)`.
* - Kotlin 使用扩展 [Image.flash].
*
* ## mirai 码支持
* 格式: &#91;mirai:flash:*[Image.imageId]*&#93;
*
* @see Image 查看图片相关信息
*/
@Serializable
@SerialName(FlashImage.SERIAL_NAME)
public data class FlashImage(
/**
* 闪照的内容图片, 即一个普通图片.
*/
@SerialName("imageId")
@Serializable(Image.AsStringSerializer::class)
public val image: Image
) : MessageContent, HummerMessage, CodableMessage, ConstrainSingle {
override val key: MessageKey<FlashImage> get() = Key
private val stringValue: String by lazy(LazyThreadSafetyMode.NONE) { "[mirai:flash:${image.imageId}]" }
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append(stringValue)
}
override fun toMiraiCode(): String = stringValue
override fun toString(): String = stringValue
override fun contentToString(): String = "[闪照]"
public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, FlashImage>(HummerMessage, { it.safeCast() }) {
public const val SERIAL_NAME: String = "FlashImage"
/**
* 将普通图片转换为闪照.
*
* @param imageId 图片 id, 详见 [Image.imageId]
*/
@JvmStatic
public fun from(imageId: String): FlashImage = FlashImage(Image(imageId))
/**
* 将普通图片转换为闪照.
*
* @see Image.flash
*/
@JvmStatic
public inline fun from(image: Image): FlashImage = FlashImage(image)
}
}
/**
* 将普通图片转换为闪照.
*/
@JvmSynthetic
public inline fun FlashImage(imageId: String): FlashImage = FlashImage.from(imageId)
/**
* 将普通图片转换为闪照.
*/
@JvmSynthetic
public inline fun Image.flash(): FlashImage = FlashImage(this)

View File

@ -352,7 +352,7 @@ public class ForwardMessageBuilder private constructor(
*/
public var currentTime: Int = currentTimeSeconds().toInt()
public inner class BuilderNode : ForwardMessage.INode {
public inner class BuilderNode internal constructor() : ForwardMessage.INode {
/**
* 发送人 [User.id]

View File

@ -13,368 +13,22 @@
package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.message.code.internal.appendStringAsMiraiCode
import net.mamoe.mirai.message.data.VipFace.Kind
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.castOrNull
import net.mamoe.mirai.utils.safeCast
/**
* 一些特殊的消息
*
* 注意, [HummerMessage] 类型不稳定, 但它的子类如 [PokeMessage] 是稳定的.
*
* @see PokeMessage 戳一戳
* @see FlashImage 闪照
* @see MarketFace 商城表情
* @see VipFace VIP 表情
*/
@MiraiExperimentalApi
public interface HummerMessage : MessageContent, ConstrainSingle {
public companion object Key :
AbstractPolymorphicMessageKey<MessageContent, HummerMessage>(MessageContent, { it.castOrNull() })
// has service type etc.
}
////////////////////////////////////////
///////////// POKE MESSAGE /////////////
////////////////////////////////////////
/**
* 戳一戳. 可以发送给好友或群.
*
* ## mirai 码支持
* 格式: &#91;mirai:poke:*[name]*,*[pokeType]*,*[id]*&#93;
*
* @see PokeMessage.Companion 使用伴生对象中的常量
*/
@SerialName(PokeMessage.SERIAL_NAME)
@Serializable
public data class PokeMessage @MiraiInternalApi constructor(
/**
* mirai, 显示的名称
*/
public val name: String,
public val pokeType: Int, // 'type' is used by serialization
public val id: Int
) : HummerMessage, CodableMessage {
override val key: MessageKey<HummerMessage>
get() = Key
public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, PokeMessage>(HummerMessage, { it.castOrNull() }) {
public const val SERIAL_NAME: String = "PokeMessage"
/** 戳一戳 */
@JvmField
public val ChuoYiChuo: PokeMessage = PokeMessage("戳一戳", 1, -1)
/** 戳一戳 */
@JvmField
@Deprecated("Use ChuoYiChuo", replaceWith = ReplaceWith("ChuoYiChuo"))
public val Poke: PokeMessage = ChuoYiChuo
/** 比心 */
@JvmField
public val BiXin: PokeMessage = PokeMessage("比心", 2, -1)
/** 比心 */
@JvmField
@Deprecated("Use BiXin", replaceWith = ReplaceWith("BiXin"))
public val ShowLove: PokeMessage = BiXin
/** 点赞 */
@JvmField
public val DianZan: PokeMessage = PokeMessage("点赞", 3, -1)
/** 点赞 */
@JvmField
@Deprecated("Use DianZan", replaceWith = ReplaceWith("DianZan"))
public val Like: PokeMessage = DianZan
/** 心碎 */
@JvmField
public val XinSui: PokeMessage = PokeMessage("心碎", 4, -1)
/** 心碎 */
@JvmField
@Deprecated("Use XinSui", replaceWith = ReplaceWith("XinSui"))
public val Heartbroken: PokeMessage = XinSui
/** 666 */
@JvmField
public val LiuLiuLiu: PokeMessage = PokeMessage("666", 5, -1)
/** 666 */
@JvmField
@Deprecated("Use LiuLiuLiu", replaceWith = ReplaceWith("LiuLiuLiu"))
public val SixSixSix: PokeMessage = LiuLiuLiu
/** 放大招 */
@JvmField
public val FangDaZhao: PokeMessage = PokeMessage("放大招", 6, -1)
/** 宝贝球 (SVIP) */
@JvmField
public val BaoBeiQiu: PokeMessage = PokeMessage("宝贝球", 126, 2011)
/** 玫瑰花 (SVIP) */
@JvmField
public val Rose: PokeMessage = PokeMessage("玫瑰花", 126, 2007)
/** 召唤术 (SVIP) */
@JvmField
public val ZhaoHuanShu: PokeMessage = PokeMessage("召唤术", 126, 2006)
/** 让你皮 (SVIP) */
@JvmField
public val RangNiPi: PokeMessage = PokeMessage("让你皮", 126, 2009)
/** 结印 (SVIP) */
@JvmField
public val JieYin: PokeMessage = PokeMessage("结印", 126, 2005)
/** 手雷 (SVIP) */
@JvmField
public val ShouLei: PokeMessage = PokeMessage("手雷", 126, 2004)
/** 勾引 */
@JvmField
public val GouYin: PokeMessage = PokeMessage("勾引", 126, 2003)
/** 抓一下 (SVIP) */
@JvmField
public val ZhuaYiXia: PokeMessage = PokeMessage("抓一下", 126, 2001)
/** 碎屏 (SVIP) */
@JvmField
public val SuiPing: PokeMessage = PokeMessage("碎屏", 126, 2002)
/** 敲门 (SVIP) */
@JvmField
public val QiaoMen: PokeMessage = PokeMessage("敲门", 126, 2002)
/**
* 所有类型数组
*/
@JvmField
public val values: Array<PokeMessage> = arrayOf(
ChuoYiChuo, BiXin, DianZan, XinSui, LiuLiuLiu,
FangDaZhao, BaoBeiQiu, Rose, ZhaoHuanShu, RangNiPi,
JieYin, ShouLei, GouYin, ZhuaYiXia, SuiPing
)
}
private val stringValue = "[mirai:poke:$name,$pokeType,$id]"
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append("[mirai:poke:").appendStringAsMiraiCode(name)
.append(',').append(pokeType).append(',').append(id)
.append(']')
}
override fun toString(): String = stringValue
override fun contentToString(): String = "[戳一戳]"
//businessType=0x00000001(1)
//pbElem=08 01 18 00 20 FF FF FF FF 0F 2A 00 32 00 38 00 50 00
//serviceType=0x00000002(2)
}
////////////////////////////////////
////////// MARKET FACE /////////////
////////////////////////////////////
/**
* 商城表情
*
* 目前不支持直接发送可支持转发但其取决于表情是否可使用.
*/
public interface MarketFace : HummerMessage {
public val name: String
public val id: Int
override val key: MessageKey<MarketFace>
get() = Key
public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, MarketFace>(HummerMessage, { it.safeCast() }) {
public const val SERIAL_NAME: String = "MarketFace"
}
override fun contentToString(): String = name
}
////////////////////////////////////
///////////// VIP FACE /////////////
////////////////////////////////////
/**
* VIP 表情.
*
* 不支持发送.
*
* ## mirai 码支持
* 格式: &#91;mirai:vipface:*[Kind.id]*,*[Kind.name]*,*[count]*&#93;
*
* @see VipFace.Key 使用伴生对象中的常量
*/
@SerialName(VipFace.SERIAL_NAME)
@Serializable
public data class VipFace @MiraiInternalApi constructor(
/**
* 使用 [Companion] 中常量.
*/
public val kind: Kind,
public val count: Int
) : HummerMessage, CodableMessage {
@Serializable
public data class Kind(
val id: Int,
val name: String
) {
public override fun toString(): String {
return "$id,$name"
}
}
override val key: MessageKey<VipFace>
get() = Key
public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, VipFace>(HummerMessage, { it.safeCast() }) {
public const val SERIAL_NAME: String = "VipFace"
@JvmField
public val LiuLian: Kind = 9 to "榴莲"
@JvmField
public val PingDiGuo: Kind = 1 to "平底锅"
@JvmField
public val ChaoPiao: Kind = 12 to "钞票"
@JvmField
public val LueLueLue: Kind = 10 to "略略略"
@JvmField
public val ZhuTou: Kind = 4 to "猪头"
@JvmField
public val BianBian: Kind = 6 to "便便"
@JvmField
public val ZhaDan: Kind = 5 to "炸弹"
@JvmField
public val AiXin: Kind = 2 to "爱心"
@JvmField
public val HaHa: Kind = 3 to "哈哈"
@JvmField
public val DianZan: Kind = 1 to "点赞"
@JvmField
public val QinQin: Kind = 7 to "亲亲"
@JvmField
public val YaoWan: Kind = 8 to "药丸"
@JvmField
public val values: Array<Kind> = arrayOf(
LiuLian, PingDiGuo, ChaoPiao, LueLueLue, ZhuTou,
BianBian, ZhaDan, AiXin, HaHa, DianZan, QinQin, YaoWan
)
private infix fun Int.to(name: String): Kind = Kind(this, name)
}
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append(stringValue)
}
private val stringValue = "[mirai:vipface:$kind,$count]"
override fun toString(): String = stringValue
override fun contentToString(): String = "[${kind.name}]x$count"
}
///////////////////////////////////////
///////////// FLASH IMAGE /////////////
///////////////////////////////////////
/**
* 闪照
*
* ## mirai 码支持
* 格式: &#91;mirai:flash:*[Image.imageId]*&#93;
*
* @see Image.flash 转换普通图片为闪照
*
* @see Image 查看图片相关信息
*/
@SerialName(FlashImage.SERIAL_NAME)
@Serializable
public data class FlashImage(
/**
* 闪照的内容图片, 即一个普通图片.
*/
@SerialName("imageId")
@Serializable(Image.AsStringSerializer::class)
public val image: Image
) : MessageContent, HummerMessage, CodableMessage, ConstrainSingle {
override val key: MessageKey<FlashImage>
get() = Key
public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, FlashImage>(HummerMessage, { it.safeCast() }) {
public const val SERIAL_NAME: String = "FlashImage"
/**
* 将普通图片转换为闪照.
*
* @param imageId 图片 id, 详见 [Image.imageId]
*/
@JvmStatic
public fun from(imageId: String): FlashImage = FlashImage(Image(imageId))
/**
* 将普通图片转换为闪照.
*
* @see Image.flash
*/
@JvmStatic
public inline fun from(image: Image): FlashImage = FlashImage(image)
}
private val stringValue: String by lazy(LazyThreadSafetyMode.NONE) { "[mirai:flash:${image.imageId}]" }
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append(stringValue)
}
override fun toMiraiCode(): String = stringValue
public override fun toString(): String = stringValue
public override fun contentToString(): String = "[闪照]"
}
/**
* 将普通图片转换为闪照.
*/
@JvmSynthetic
public inline fun FlashImage(imageId: String): FlashImage = FlashImage.from(imageId)
/**
* 将普通图片转换为闪照.
*/
@JvmSynthetic
public inline fun Image.flash(): FlashImage = FlashImage(this)
}

View File

@ -88,14 +88,12 @@ public interface Image : Message, MessageContent, CodableMessage {
*/
public val imageId: String
@kotlinx.serialization.Serializer(forClass = Image::class)
public object AsStringSerializer : KSerializer<Image> by String.serializer().mapPrimitive(
SERIAL_NAME,
serialize = { imageId },
deserialize = { Image(it) },
)
@kotlinx.serialization.Serializer(forClass = Image::class)
public object Serializer : KSerializer<Image> by FallbackSerializer("Image")
@MiraiInternalApi
@ -133,7 +131,7 @@ public interface Image : Message, MessageContent, CodableMessage {
* - 当图片为从服务器接收的消息中的图片时, 可以直接获取下载链接, 本函数不会挂起协程.
* - 其他情况下协程可能会挂起并向服务器查询下载链接, 或不挂起并拼接一个链接.
*
* @return 原图 HTTP 下载链接 ( HTTPS)
* @return 原图 HTTP 下载链接
* @throws IllegalStateException 当无任何 [Bot] 在线时抛出 (因为无法获取相关协议)
*/
@JvmStatic
@ -199,6 +197,11 @@ public interface Image : Message, MessageContent, CodableMessage {
@JvmSynthetic
public inline fun Image(imageId: String): Image = Image.fromId(imageId)
///////////////////////////////////////////////////////////////////////////
// Internals
///////////////////////////////////////////////////////////////////////////
/**
* 计算图片的 md5 校验值.
*

View File

@ -0,0 +1,40 @@
/*
* 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/master/LICENSE
*/
package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.safeCast
/**
* 商城表情
*
* 目前不支持直接发送可保存接收到的来自官方客户端的商城表情然后转发.
*/
public interface MarketFace : HummerMessage {
/**
* `[开心]`
*/
public val name: String
/**
* 内部 id.
*/
@MiraiExperimentalApi
public val id: Int
override val key: MessageKey<MarketFace> get() = Key
override fun contentToString(): String = name
public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, MarketFace>(HummerMessage, { it.safeCast() }) {
public const val SERIAL_NAME: String = "MarketFace"
}
}

View File

@ -35,7 +35,27 @@ import kotlin.internal.LowPriorityInOverloadResolution
* - [MessageContent] 含内容的消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] .
* - [MessageChain]: 不可变消息链, 链表形式链接的多个 [SingleMessage] 实例.
*
* #### Kotlin 使用 [Message]:
* ## 获得 [Message]
*
* 请先根据实际需求确定需要的类型.
*
*
* - [PlainText]: 纯文本
* - [Image]: 图片
* - [Face]: 原生表情
* - [At]: 一个群成员的引用
* - [AtAll]: 全体成员的引用
* - [QuoteReply]: 一条消息的引用
* - [RichMessage]: 富文本消息, [XML JSON][ServiceMessage], [小程序][LightApp]
* - [FlashImage]: 闪照
* - [PokeMessage]: 戳一戳 (消息)
* - [VipFace]: VIP 表情
* - [CustomMessage]: 自定义消息类型
* - ...
*
* ## 使用 [Message]
*
* ### Kotlin 使用 [Message]:
* 这与使用 [String] 的使用非常类似.
*
* - 比较 [SingleMessage] [String]:
@ -48,7 +68,9 @@ import kotlin.internal.LowPriorityInOverloadResolution
* ```
* 但注意: 不能 `String + Message`. 只能 `Message + String`
*
* #### 发送消息
*
*
* ### 发送消息
* - 通过 [Contact] 中的成员函数: [Contact.sendMessage]
* - 通过 [Message] 的扩展函数: [Message.sendTo]
* - [MessageEvent] 中使用 [MessageEvent.reply] 等捷径
@ -68,7 +90,7 @@ import kotlin.internal.LowPriorityInOverloadResolution
*
* @see Contact.sendMessage 发送消息
*/
public interface Message {
public interface Message { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
/**
* `this` [tail] 连接.
@ -222,26 +244,26 @@ public suspend inline operator fun Message.plus(another: Flow<Message>): Message
* 单个消息元素. 与之相对的是 [MessageChain], 是多个 [SingleMessage] 的集合.
*/
@Serializable(SingleMessage.Serializer::class)
public interface SingleMessage : Message {
@kotlinx.serialization.Serializer(forClass = SingleMessage::class)
public object Serializer :
KSerializer<SingleMessage> by PolymorphicSerializer(SingleMessage::class)
public interface SingleMessage : Message { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
public object Serializer : KSerializer<SingleMessage> by PolymorphicSerializer(SingleMessage::class)
}
/**
* 消息元数据, 即不含内容的元素.
*
* 这种类型的 [Message] 只表示一条消息的属性. 其子类为 [MessageSource], [QuoteReply]
* 这种类型的 [Message] 只表示一条消息的属性. 其子类为 [MessageSource], [QuoteReply] [CustomMessageMetadata]
*
* 所有子类的 [contentToString] 都应该返回空字符串.
*
* 要获取详细信息, 查看 [MessageChain].
*
* @see MessageSource 消息源
* @see QuoteReply 引用回复
* @see CustomMessageMetadata 自定义元数据
*
* @see ConstrainSingle 约束一个 [MessageChain] 中只存在这一种类型的元素
*/
public interface MessageMetadata : SingleMessage {
public interface MessageMetadata : SingleMessage { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
/**
* 返回空字符串
*/
@ -253,10 +275,8 @@ public interface MessageMetadata : SingleMessage {
*
* 实现此接口的元素将会在连接时自动处理替换.
*
* @see AbstractMessageKey
* @see AbstractPolymorphicMessageKey
*
* @see MessageSource
* 要获取有关键的信息, 查看 [MessageKey].
* 要获取有关约束的处理方式, 查看 [AbstractPolymorphicMessageKey].
*/
public interface ConstrainSingle : SingleMessage {
/**
@ -280,7 +300,7 @@ public interface ConstrainSingle : SingleMessage {
* @see ForwardMessage 合并转发
* @see Voice 语音
*/
public interface MessageContent : SingleMessage {
public interface MessageContent : SingleMessage { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
public companion object Key : AbstractMessageKey<MessageContent>({ it.safeCast() })
}

View File

@ -27,56 +27,160 @@ 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.parseMiraiCode
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.MiraiExperimentalApi
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.safeCast
import java.util.stream.Stream
import kotlin.reflect.KProperty
import kotlin.streams.asSequence
/**
* 消息链. 空的实现为 [EmptyMessageChain]
* 消息链, `List<SingleMessage>`, [单个消息元素][SingleMessage] 的有序集合.
*
* 要获取更多消息相关的信息, 查看 [Message]
* [MessageChain] 代表一条完整的聊天中的消息, 可包含 [带内容的消息 `MessageContent`][MessageContent]
* [不带内容的元数据 `MessageMetadata`][MessageMetadata].
*
* ### 构造消息链
* - [buildMessageChain][buildMessageChain]: 使用构建器
* - [Message.plus][Message.plus]: 将两个消息相连成为一个消息链
* - [toMessageChain][toMessageChain] [Iterable], [Array] 等类型转换为 [MessageChain]
* - [messageChainOf][messageChainOf] 类似 [listOf], 将多个 [Message] 构造为 [MessageChain]
* # 元素类型
*
* @see get 获取消息链中一个类型的元素, 不存在时返回 `null`
* @see getOrFail 获取消息链中一个类型的元素, 不存在时抛出异常 [NoSuchElementException]
* @see MessageSource.quote 引用这条消息
* @see MessageSource.recall 撤回这条消息 (仅限来自 [MessageEvent] 的消息)
* [MessageContent] [纯文字][PlainText], [图片][Image], [语音][Voice], 是能被用户看到的内容.
*
* @see buildMessageChain 构造一个 [MessageChain]
* @see Message.toMessageChain 将单个 [Message] 转换为 [MessageChain]
* @see toMessageChain [Iterable] [Sequence] 委托为 [MessageChain]
*
* @see forEachContent 遍历内容
* @see allContent 判断是否每一个 [MessageContent] 都满足条件
* @see noneContent 判断是否每一个 [MessageContent] 都不满足条件
* [MessageMetadata] 是用来形容这条消息的状态的数据, 因此称为 *元数据 (metadata)*.
* 元数据目前只分为 [消息来源 `MessageSource`][MessageSource] [引用回复 `QuoteReply`][QuoteReply].
*
* @see orNull 属性委托扩展
* @see orElse 属性委托扩展
* @see getValue 属性委托扩展
* @see flatten 扁平化
* [MessageSource] 存储这条消息的发送人, 接收人, 识别 ID (服务器提供), 发送时间等信息.
* **[MessageSource] 是精确的**. [MessageSource] 就可以在服务器上定位一条消息, 因此可以用来 [撤回消息][MessageSource.recall].
*
* @see MiraiCode mirai
* [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<Message>.toMessageChain(): MessageChain
* public fun Iterable<Message>.toMessageChain(): MessageChain
* public fun Iterator<Message>.toMessageChain(): MessageChain
* public fun Stream<Message>.toMessageChain(): MessageChain
* public fun Flow<Message>.toMessageChain(): MessageChain
* public fun Array<Message>.toMessageChain(): MessageChain
* ```
* - [Message.toMessageChain][Message.toMessageChain] 将单个 [Message] 包装成一个单元素的 [MessageChain]
*
* ## Java 构造消息链
* - `MessageUtils.newChain`: 有多个重载, 相关定义如下:
* ```java
* public static MessageChain newChain(Message messages...)
* public static MessageChain newChain(Iterable<Message> iterable)
* public static MessageChain newChain(Iterator<Message> iterator)
* public static MessageChain newChain(Stream<Message> stream)
* public static MessageChain newChain(Message[] array)
* ```
* - [Message.plus][Message.plus]: 将两个消息相连成为一个消息链:
* ```java
* MessageChain chain = new PlainText("Hello ").plus(new PlainText("Mirai!"))
* ```
* - [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]
*
* [MessageChain] 继承 `List<SingleMessage>`. 可以以 [List] 的方式处理 [MessageChain].
*
* 额外地, 若要获取一个 [ConstrainSingle] 的元素, 可以通过 [ConstrainSingle.key]:
* ```
* val quote = chain[QuoteReply] // Kotlin
*
* QuoteReply quote = chain.get(QuoteReply.Key) // Java
* ```
*
* 相关地还可以使用 [MessageChain.contains] [MessageChain.getOrFail]
*
* ## 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.toMiraiCode] [MessageChain] 序列化为 Mirai Code [String].
* - 使用 [MessageChain.toMiraiCode] Mirai Code [String] 反序列化为 [MessageChain].
*
*
* ## 撤回和引用
* - [MessageSource.quote]
* - [MessageSource.recall]
* - [MessageSource.recallIn]
* - `MessageChain.quote`
* - `MessageChain.recall`
* - `MessageChain.recallIn`
*/
@Serializable(MessageChain.Serializer::class)
public interface MessageChain : Message, List<SingleMessage>, RandomAccess, CodableMessage {
/**
* 元素数量. [EmptyMessageChain] 不参加计数.
*/
public override val size: Int
public interface MessageChain :
Message, List<SingleMessage>, RandomAccess, CodableMessage { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
/**
* 获取第一个类型为 [key] [Message] 实例. 若不存在此实例, 返回 `null`.
*
* 此方法仅适用于 [ConstrainSingle] 的消息类型, [MessageSource]
* 此方法目前仅适用于 [ConstrainSingle] 的消息类型, [MessageSource].
*
* ### Kotlin 使用方法
* ```
@ -98,6 +202,32 @@ public interface MessageChain : Message, List<SingleMessage>, RandomAccess, Coda
public operator fun <M : SingleMessage> get(key: MessageKey<M>): M? =
asSequence().mapNotNull { key.safeCast.invoke(it) }.firstOrNull()
/**
* 当存在 [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 <M : SingleMessage> contains(key: MessageKey<M>): Boolean =
asSequence().any { key.safeCast.invoke(it) != null }
@ -106,7 +236,14 @@ public interface MessageChain : Message, List<SingleMessage>, RandomAccess, Coda
forEach { it.safeCast<CodableMessage>()?.appendMiraiCodeTo(builder) }
}
@kotlinx.serialization.Serializer(MessageChain::class)
/**
* [MessageChain] 作为 `List<SingleMessage>` 序列化. 使用 [多态序列化][Polymorphic].
*
* 在实践时请提供 [MessageSerializers.serializersModule] 到指定 [SerialFormat].
*
* @see ListSerializer
* @see MessageSerializers
*/
public object Serializer : KSerializer<MessageChain> {
@Suppress("DEPRECATION_ERROR")
private val delegate = ListSerializer(PolymorphicSerializer(SingleMessage::class))
@ -115,7 +252,6 @@ public interface MessageChain : Message, List<SingleMessage>, RandomAccess, Coda
override fun serialize(encoder: Encoder, value: MessageChain): Unit = delegate.serialize(encoder, value)
}
@Suppress("DEPRECATION_ERROR")
public companion object {
private fun getDefaultJson() = Json {
serializersModule =
@ -191,20 +327,27 @@ public interface MessageChain : Message, List<SingleMessage>, RandomAccess, Coda
/**
* 不含任何元素的 [MessageChain].
*/
public object EmptyMessageChain : MessageChain, Iterator<SingleMessage>, List<SingleMessage> by emptyList() {
public override val size: Int get() = 0
public override fun toString(): String = ""
public override fun contentToString(): String = ""
public override fun equals(other: Any?): Boolean = other === this
@Serializable(MessageChain.Serializer::class)
public object EmptyMessageChain : MessageChain, List<SingleMessage> by emptyList() {
override val size: Int get() = 0
public override fun iterator(): Iterator<SingleMessage> = this
public override fun hasNext(): Boolean = false
public override fun next(): SingleMessage = throw NoSuchElementException("EmptyMessageChain is empty.")
override fun toString(): String = ""
override fun contentToString(): String = ""
override fun toMiraiCode(): String = ""
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
}
override fun equals(other: Any?): Boolean = other === this
override fun hashCode(): Int = 1
override fun iterator(): Iterator<SingleMessage> = EmptyMessageChainIterator
private object EmptyMessageChainIterator : Iterator<SingleMessage> {
override fun hasNext(): Boolean = false
override fun next(): Nothing = throw NoSuchElementException("EmptyMessageChain is empty.")
}
}
// region accessors
@ -214,53 +357,37 @@ public object EmptyMessageChain : MessageChain, Iterator<SingleMessage>, List<Si
*
* @param key 由各个类型消息的伴生对象持有. [MessageSource.Key]
*/
@JvmOverloads
@JvmSynthetic
public inline fun <M : SingleMessage> MessageChain.getOrFail(
key: MessageKey<M>,
crossinline lazyMessage: (key: MessageKey<M>) -> String = { key.toString() }
): M = get(key) ?: throw NoSuchElementException(lazyMessage(key))
/**
* 遍历每一个 [消息内容][MessageContent]
* 获取 `Sequence<MessageContent>`
* 相当于 `this.asSequence().filterIsInstance<MessageContent>()`
*/
@JvmSynthetic
public inline fun MessageChain.forEachContent(block: (MessageContent) -> Unit) {
for (element in this) {
if (element !is MessageMetadata) {
check(element is MessageContent) { "internal error: Message must be either MessageMetadata or MessageContent" }
block(element)
}
}
}
public fun MessageChain.contentsSequence(): Sequence<MessageContent> =
this.asSequence().filterIsInstance<MessageContent>()
/**
* 如果每一个 [消息内容][MessageContent] 都满足 [block], 返回 `true`
* 获取 `Sequence<MessageMetadata>`
* 相当于 `this.asSequence().filterIsInstance<MessageMetadata>()`
*/
@JvmSynthetic
public inline fun MessageChain.allContent(block: (MessageContent) -> Boolean): Boolean {
this.forEach {
if (it !is MessageMetadata) {
check(it is MessageContent) { "internal error: Message must be either MessageMetadata or MessageContent" }
if (!block(it)) return false
}
}
return true
}
public fun MessageChain.metadataSequence(): Sequence<MessageMetadata> =
this.asSequence().filterIsInstance<MessageMetadata>()
/**
* 如果每一个 [消息内容][MessageContent] 都不满足 [block], 返回 `true`
* 筛选 [MessageMetadata]
*/
@JvmSynthetic
public inline fun MessageChain.noneContent(block: (MessageContent) -> Boolean): Boolean {
this.forEach {
if (it !is MessageMetadata) {
check(it is MessageContent) { "internal error: Message must be either MessageMetadata or MessageContent" }
if (block(it)) return false
}
}
return true
}
public fun MessageChain.metadataList(): List<MessageMetadata> = this.filterIsInstance<MessageMetadata>()
/**
* 筛选 [MessageContent]
*/
public fun MessageChain.contentsList(): List<MessageContent> = this.filterIsInstance<MessageContent>()
/**
@ -307,7 +434,8 @@ public inline fun messageChainOf(vararg messages: Message): MessageChain = messa
* 扁平化 [this] 并创建一个 [MessageChain].
*/
@JvmName("newChain")
public fun Sequence<Message>.toMessageChain(): MessageChain = MessageChainImpl(this.constrainSingleMessages())
public fun Sequence<Message>.toMessageChain(): MessageChain =
createMessageChainImplOptimized(this.constrainSingleMessages())
/**
* 扁平化 [this] 并创建一个 [MessageChain].
@ -415,3 +543,77 @@ public inline fun <reified T : R, R : SingleMessage?> MessageChain.orElse(
): OrNullDelegate<R> = OrNullDelegate<R>(this.firstIsInstanceOrNull<T>() ?: lazyDefault())
// endregion delegate
///////////////////////////////////////////////////////////////////////////
// Deprecated
///////////////////////////////////////////////////////////////////////////
/**
* 遍历每一个 [消息内容][MessageContent]
*/
@JvmSynthetic
@Deprecated(
"Use operations on contentsSequence instead.",
ReplaceWith(
"this.contentsSequence().forEach(block)",
"net.mamoe.mirai.message.data.contentsSequence"
),
DeprecationLevel.ERROR,
)
@PlannedRemoval("2.0.0")
public inline fun MessageChain.forEachContent(block: (MessageContent) -> Unit) {
for (element in this) {
if (element !is MessageMetadata) {
check(element is MessageContent) { "internal error: Message must be either MessageMetadata or MessageContent" }
block(element)
}
}
}
/**
* 如果每一个 [消息内容][MessageContent] 都满足 [block], 返回 `true`
*/
@JvmSynthetic
@Deprecated(
"Use operations on contentsSequence instead.",
ReplaceWith(
"this.contentsSequence().all(block)",
"net.mamoe.mirai.message.data.contentsSequence"
),
DeprecationLevel.ERROR,
)
@PlannedRemoval("2.0.0")
public inline fun MessageChain.allContent(block: (MessageContent) -> Boolean): Boolean {
this.forEach {
if (it !is MessageMetadata) {
check(it is MessageContent) { "internal error: Message must be either MessageMetadata or MessageContent" }
if (!block(it)) return false
}
}
return true
}
/**
* 如果每一个 [消息内容][MessageContent] 都不满足 [block], 返回 `true`
*/
@JvmSynthetic
@Deprecated(
"Use operations on contentsSequence instead.",
ReplaceWith(
"this.contentsSequence().none(block)",
"net.mamoe.mirai.message.data.contentsSequence"
),
DeprecationLevel.ERROR,
)
@PlannedRemoval("2.0.0")
public inline fun MessageChain.noneContent(block: (MessageContent) -> Boolean): Boolean {
this.forEach {
if (it !is MessageMetadata) {
check(it is MessageContent) { "internal error: Message must be either MessageMetadata or MessageContent" }
if (block(it)) return false
}
}
return true
}

View File

@ -135,7 +135,7 @@ public class MessageChainBuilder private constructor(
// avoid resolution to extensions
public fun asMessageChain(): MessageChain {
this.flushCache()
return MessageChainImpl(this.constrainSingleMessages())
return createMessageChainImplOptimized(this.constrainSingleMessages())
}
/** 同 [asMessageChain] */

View File

@ -25,12 +25,10 @@ import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.MessageSource.Key.isAboutFriend
import net.mamoe.mirai.message.data.MessageSource.Key.isAboutGroup
import net.mamoe.mirai.message.data.MessageSource.Key.isAboutStranger
import net.mamoe.mirai.message.data.MessageSource.Key.isAboutTemp
import net.mamoe.mirai.message.data.MessageSource.Key.quote
import net.mamoe.mirai.message.data.MessageSource.Key.recall
import net.mamoe.mirai.utils.LazyProperty
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.safeCast
/**
@ -58,7 +56,7 @@ import net.mamoe.mirai.utils.safeCast
*
* ## 使用
*
* 消息源可用于 [引用回复][QuoteReply] [撤回][IMirai.recallMessage].
* 消息源可用于 [引用回复][MessageSource.quote] [撤回][MessageSource.recall].
*
* @see IMirai.recallMessage 撤回一条消息
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
@ -256,7 +254,7 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
@JvmStatic
public inline fun MessageSource.isAboutFriend(): Boolean {
return when (this) {
is OnlineMessageSource -> subject !is Group && subject !is Member
is OnlineMessageSource -> subject is Friend
is OfflineMessageSource -> kind == MessageSourceKind.FRIEND
}
}
@ -277,208 +275,52 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
}
}
public inline val MessageSource.bot: Bot
get() = when (this) {
is OnlineMessageSource -> bot
is OfflineMessageSource -> Bot.getInstance(botId)
}
public inline val MessageSource.botOrNull: Bot?
get() = when (this) {
is OnlineMessageSource -> bot
is OfflineMessageSource -> Bot.getInstanceOrNull(botId)
}
/**
* 在线消息的 [MessageSource].
* 拥有对象化的 [sender], [target], 也可以直接 [recallMessage] [quote]
*
* ### 来源
* - bot 主动发送消息时, 产生 (由协议模块主动构造) [OnlineMessageSource.Outgoing]
* - bot 接收消息时, 产生 (由协议模块根据服务器的提供的信息构造) [OnlineMessageSource.Incoming]
*
* #### 机器人主动发送消息
* 当机器人 [主动发出消息][Member.sendMessage], 将会得到一个 [消息回执][MessageReceipt].
* 此回执的 [消息源][MessageReceipt.source] 即为一个 [外向消息源][OnlineMessageSource.Outgoing], 代表着刚刚发出的那条消息的来源.
*
* #### 机器人接受消息
* 当机器人接收一条消息 [MessageEvent], 这条消息包含一个 [内向消息源][OnlineMessageSource.Incoming], 代表着接收到的这条消息的来源.
*
*
* ### 实现
* 此类的所有子类都有协议模块实现. 不要自行实现它们, 否则将无法发送
*
* @see OnlineMessageSource.toOffline 转为 [OfflineMessageSource]
* 消息来源类型
*/
public sealed class OnlineMessageSource : MessageSource() {
public companion object Key : AbstractMessageKey<OnlineMessageSource>({ it.safeCast() })
/**
* @see botId
*/
public abstract val bot: Bot
final override val botId: Long get() = bot.id
/**
* 消息发送人. 可能为 [机器人][Bot] [好友][Friend] [群员][Member].
* 即类型必定为 [Bot], [Friend] [Member]
*/
public abstract val sender: ContactOrBot
/**
* 消息发送目标. 可能为 [机器人][Bot] [好友][Friend] [][Group].
* 即类型必定为 [Bot], [Friend] [Group]
*/
public abstract val target: ContactOrBot
/**
* 消息主体. 群消息时为 [Group]. 好友消息时为 [Friend], 临时消息为 [Member]
* 不论是机器人接收的消息还是发送的消息, 此属性都指向机器人能进行回复的目标.
*/
public abstract val subject: Contact
/*
* 以下子类型仅是覆盖了 [target], [subject], [sender] 等的类型
*/
/**
* [机器人主动发送消息][Contact.sendMessage] 产生的 [MessageSource], 可通过 [MessageReceipt] 获得.
*/
public sealed class Outgoing : OnlineMessageSource() {
public companion object Key :
AbstractPolymorphicMessageKey<OnlineMessageSource, Outgoing>(OnlineMessageSource, { it.safeCast() })
public abstract override val sender: Bot
public abstract override val target: Contact
public final override val fromId: Long get() = sender.id
public final override val targetId: Long get() = target.id
public abstract class ToFriend : Outgoing() {
public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToFriend>(Outgoing, { it.safeCast() })
public abstract override val target: Friend
public final override val subject: Friend get() = target
// final override fun toString(): String = "OnlineMessageSource.ToFriend(target=${target.ids})"
}
public abstract class ToStranger : Outgoing() {
public companion object Key :
AbstractPolymorphicMessageKey<Outgoing, ToStranger>(Outgoing, { it.safeCast() })
public abstract override val target: Stranger
public final override val subject: Stranger get() = target
// final override fun toString(): String = "OnlineMessageSource.ToFriend(target=${target.ids})"
}
public abstract class ToTemp : Outgoing() {
public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToTemp>(Outgoing, { it.safeCast() })
public abstract override val target: Member
public val group: Group get() = target.group
public final override val subject: Member get() = target
}
public abstract class ToGroup : Outgoing() {
public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToGroup>(Outgoing, { it.safeCast() })
public abstract override val target: Group
public final override val subject: Group get() = target
}
}
/**
* 接收到的一条消息的 [MessageSource]
*/
public sealed class Incoming : OnlineMessageSource() {
public abstract override val sender: User
public final override val fromId: Long get() = sender.id
public final override val targetId: Long get() = target.id
public abstract class FromFriend : Incoming() {
public companion object Key :
AbstractPolymorphicMessageKey<Incoming, FromFriend>(Incoming, { it.safeCast() })
public abstract override val sender: Friend
public final override val subject: Friend get() = sender
public final override val target: Bot get() = sender.bot
// final override fun toString(): String = "OnlineMessageSource.FromFriend(from=${sender.ids})"
}
public abstract class FromTemp : Incoming() {
public companion object Key :
AbstractPolymorphicMessageKey<Incoming, FromTemp>(Incoming, { it.safeCast() })
public abstract override val sender: Member
public inline val group: Group get() = sender.group
public final override val subject: Member get() = sender
public final override val target: Bot get() = sender.bot
}
public abstract class FromStranger : Incoming() {
public companion object Key :
AbstractPolymorphicMessageKey<Incoming, FromStranger>(Incoming, { it.safeCast() })
public abstract override val sender: Stranger
public final override val subject: Stranger get() = sender
public final override val target: Bot get() = sender.bot
}
public abstract class FromGroup : Incoming() {
public companion object Key :
AbstractPolymorphicMessageKey<Incoming, FromGroup>(Incoming, { it.safeCast() })
public abstract override val sender: Member
public final override val subject: Group get() = sender.group
public final override val target: Group get() = group
public inline val group: Group get() = sender.group
}
public companion object Key :
AbstractPolymorphicMessageKey<OnlineMessageSource, FromTemp>(OnlineMessageSource, { it.safeCast() })
}
}
/**
* 由一条消息中的 [QuoteReply] 得到的 [MessageSource].
* 此消息源可能来自一条与机器人无关的消息. 因此无法提供对象化的 `sender` `target` 获取.
*
* @see buildMessageSource 构建一个 [OfflineMessageSource]
* @see IMirai.constructMessageSource
* @see OnlineMessageSource.toOffline
*/
public abstract class OfflineMessageSource : MessageSource() {
public companion object Key :
AbstractPolymorphicMessageKey<MessageSource, OfflineMessageSource>(MessageSource, { it.safeCast() })
/**
* 消息种类
*/
public abstract val kind: MessageSourceKind
}
@Serializable
public enum class MessageSourceKind {
/**
* 群消息
*/
GROUP,
/**
* 好友消息
*/
FRIEND,
/**
* 来自群成员的临时会话消息
*/
TEMP,
/**
* 来自陌生人的消息
*/
STRANGER
}
/**
* 获取 [MessageSourceKind]
*/
public val MessageSource.kind: MessageSourceKind
get() = when (this) {
is OnlineMessageSource -> kind
is OfflineMessageSource -> kind
}
/**
* 获取 [MessageSourceKind]
*/
public val OnlineMessageSource.kind: MessageSourceKind
get() = when {
isAboutGroup() -> MessageSourceKind.GROUP
isAboutFriend() -> MessageSourceKind.FRIEND
isAboutTemp() -> MessageSourceKind.TEMP
isAboutStranger() -> MessageSourceKind.STRANGER
else -> error("Internal error: OnlineMessageSource.kind reached an unexpected clause")
get() = when (subject) {
is Group -> MessageSourceKind.GROUP
is Friend -> MessageSourceKind.FRIEND
is Member -> MessageSourceKind.TEMP
is Stranger -> MessageSourceKind.STRANGER
else -> error("Internal error: OnlineMessageSource.kind reached an unexpected clause, subject=$subject")
}
// For MessageChain, no need to expose to Java.
@ -548,3 +390,191 @@ public inline val MessageChain.source: MessageSource
@get:JvmSynthetic
public inline val MessageChain.sourceOrNull: MessageSource?
get() = this[MessageSource]
/**
* 根据 [MessageSource.botId] [Bot.getInstance] 获取 [Bot]
*/
public inline val MessageSource.bot: Bot
get() = when (this) {
is OnlineMessageSource -> bot
is OfflineMessageSource -> Bot.getInstance(botId)
}
/**
* 根据 [MessageSource.botId] [Bot.getInstanceOrNull] 获取 [Bot]
*/
public inline val MessageSource.botOrNull: Bot?
get() = when (this) {
is OnlineMessageSource -> bot
is OfflineMessageSource -> Bot.getInstanceOrNull(botId)
}
/**
* 在线消息的 [MessageSource].
* 拥有对象化的 [sender], [target], 也可以直接 [recallMessage] [quote]
*
* ### 来源
* - bot 主动发送消息时, 产生 (由协议模块主动构造) [OnlineMessageSource.Outgoing]
* - bot 接收消息时, 产生 (由协议模块根据服务器的提供的信息构造) [OnlineMessageSource.Incoming]
*
* #### 机器人主动发送消息
* 当机器人 [主动发出消息][Member.sendMessage], 将会得到一个 [消息回执][MessageReceipt].
* 此回执的 [消息源][MessageReceipt.source] 即为一个 [外向消息源][OnlineMessageSource.Outgoing], 代表着刚刚发出的那条消息的来源.
*
* #### 机器人接受消息
* 当机器人接收一条消息 [MessageEvent], 这条消息包含一个 [内向消息源][OnlineMessageSource.Incoming], 代表着接收到的这条消息的来源.
*
*
* ### 实现
* 此类的所有子类都有协议模块实现. 不要自行实现它们, 否则将无法发送
*
* @see OnlineMessageSource.toOffline 转为 [OfflineMessageSource]
*/
public sealed class OnlineMessageSource : MessageSource() { // TODO: 2021/1/10 Extract to separate file in Kotlin 1.5
public companion object Key : AbstractMessageKey<OnlineMessageSource>({ it.safeCast() })
/**
* @see botId
*/
public abstract val bot: Bot
final override val botId: Long get() = bot.id
/**
* 消息发送人. 可能为 [机器人][Bot] [好友][Friend] [群员][Member].
* 即类型必定为 [Bot], [Friend] [Member]
*/
public abstract val sender: ContactOrBot
/**
* 消息发送目标. 可能为 [机器人][Bot] [好友][Friend] [][Group].
* 即类型必定为 [Bot], [Friend] [Group]
*/
public abstract val target: ContactOrBot
/**
* 消息主体. 群消息时为 [Group]. 好友消息时为 [Friend], 临时消息为 [Member]
* 不论是机器人接收的消息还是发送的消息, 此属性都指向机器人能进行回复的目标.
*/
public abstract val subject: Contact
/*
* 以下子类型仅是覆盖了 [target], [subject], [sender] 等的类型
*/
/**
* [机器人主动发送消息][Contact.sendMessage] 产生的 [MessageSource], 可通过 [MessageReceipt] 获得.
*/
public sealed class Outgoing : OnlineMessageSource() {
public companion object Key :
AbstractPolymorphicMessageKey<OnlineMessageSource, Outgoing>(OnlineMessageSource, { it.safeCast() })
public abstract override val sender: Bot
public abstract override val target: Contact
public final override val fromId: Long get() = sender.id
public final override val targetId: Long get() = target.id
public abstract class ToFriend @MiraiInternalApi constructor() : Outgoing() {
public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToFriend>(Outgoing, { it.safeCast() })
public abstract override val target: Friend
public final override val subject: Friend get() = target
// final override fun toString(): String = "OnlineMessageSource.ToFriend(target=${target.ids})"
}
public abstract class ToStranger @MiraiInternalApi constructor() : Outgoing() {
public companion object Key :
AbstractPolymorphicMessageKey<Outgoing, ToStranger>(Outgoing, { it.safeCast() })
public abstract override val target: Stranger
public final override val subject: Stranger get() = target
// final override fun toString(): String = "OnlineMessageSource.ToFriend(target=${target.ids})"
}
public abstract class ToTemp @MiraiInternalApi constructor() : Outgoing() {
public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToTemp>(Outgoing, { it.safeCast() })
public abstract override val target: Member
public val group: Group get() = target.group
public final override val subject: Member get() = target
}
public abstract class ToGroup @MiraiInternalApi constructor() : Outgoing() {
public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToGroup>(Outgoing, { it.safeCast() })
public abstract override val target: Group
public final override val subject: Group get() = target
}
}
/**
* 接收到的一条消息的 [MessageSource]
*/
public sealed class Incoming : OnlineMessageSource() {
public abstract override val sender: User
public final override val fromId: Long get() = sender.id
public final override val targetId: Long get() = target.id
public abstract class FromFriend @MiraiInternalApi constructor() : Incoming() {
public companion object Key :
AbstractPolymorphicMessageKey<Incoming, FromFriend>(Incoming, { it.safeCast() })
public abstract override val sender: Friend
public final override val subject: Friend get() = sender
public final override val target: Bot get() = sender.bot
// final override fun toString(): String = "OnlineMessageSource.FromFriend(from=${sender.ids})"
}
public abstract class FromTemp @MiraiInternalApi constructor() : Incoming() {
public companion object Key :
AbstractPolymorphicMessageKey<Incoming, FromTemp>(Incoming, { it.safeCast() })
public abstract override val sender: Member
public inline val group: Group get() = sender.group
public final override val subject: Member get() = sender
public final override val target: Bot get() = sender.bot
}
public abstract class FromStranger @MiraiInternalApi constructor() : Incoming() {
public companion object Key :
AbstractPolymorphicMessageKey<Incoming, FromStranger>(Incoming, { it.safeCast() })
public abstract override val sender: Stranger
public final override val subject: Stranger get() = sender
public final override val target: Bot get() = sender.bot
}
public abstract class FromGroup @MiraiInternalApi constructor() : Incoming() {
public companion object Key :
AbstractPolymorphicMessageKey<Incoming, FromGroup>(Incoming, { it.safeCast() })
public abstract override val sender: Member
public final override val subject: Group get() = sender.group
public final override val target: Group get() = group
public inline val group: Group get() = sender.group
}
public companion object Key :
AbstractPolymorphicMessageKey<OnlineMessageSource, FromTemp>(OnlineMessageSource, { it.safeCast() })
}
}
/**
* 由一条消息中的 [QuoteReply] 得到的 [MessageSource].
* 此消息源可能来自一条与机器人无关的消息. 因此无法提供对象化的 `sender` `target` 获取.
*
* @see buildMessageSource 构建一个 [OfflineMessageSource]
* @see IMirai.constructMessageSource
* @see OnlineMessageSource.toOffline
*/
public abstract class OfflineMessageSource : MessageSource() { // TODO: 2021/1/10 Extract to separate file in Kotlin 1.5
public companion object Key :
AbstractPolymorphicMessageKey<MessageSource, OfflineMessageSource>(MessageSource, { it.safeCast() })
/**
* 消息种类
*/
public abstract val kind: MessageSourceKind
}

View File

@ -0,0 +1,160 @@
/*
* 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/master/LICENSE
*/
package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.message.code.internal.appendStringAsMiraiCode
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.castOrNull
/**
* 戳一戳. 可以发送给好友或群.
*
* ## mirai 码支持
* 格式: &#91;mirai:poke:*[name]*,*[pokeType]*,*[id]*&#93;
*
* @see PokeMessage.Companion 使用伴生对象中的常量
*/
@SerialName(PokeMessage.SERIAL_NAME)
@Serializable
public data class PokeMessage @MiraiInternalApi constructor(
/**
* mirai, 显示的名称
*/
public val name: String,
public val pokeType: Int, // 'type' is used by serialization
public val id: Int
) : HummerMessage, CodableMessage {
override val key: MessageKey<HummerMessage> get() = Key
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append("[mirai:poke:").appendStringAsMiraiCode(name)
.append(',').append(pokeType).append(',').append(id)
.append(']')
}
override fun toString(): String = "[mirai:poke:$name,$pokeType,$id]"
override fun contentToString(): String = "[戳一戳]"
//businessType=0x00000001(1)
//pbElem=08 01 18 00 20 FF FF FF FF 0F 2A 00 32 00 38 00 50 00
//serviceType=0x00000002(2)
public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, PokeMessage>(HummerMessage, { it.castOrNull() }) {
public const val SERIAL_NAME: String = "PokeMessage"
/** 戳一戳 */
@JvmField
public val ChuoYiChuo: PokeMessage = PokeMessage("戳一戳", 1, -1)
/** 戳一戳 */
@JvmField
@Deprecated("Use ChuoYiChuo", replaceWith = ReplaceWith("ChuoYiChuo"))
public val Poke: PokeMessage = ChuoYiChuo
/** 比心 */
@JvmField
public val BiXin: PokeMessage = PokeMessage("比心", 2, -1)
/** 比心 */
@JvmField
@Deprecated("Use BiXin", replaceWith = ReplaceWith("BiXin"))
public val ShowLove: PokeMessage = BiXin
/** 点赞 */
@JvmField
public val DianZan: PokeMessage = PokeMessage("点赞", 3, -1)
/** 点赞 */
@JvmField
@Deprecated("Use DianZan", replaceWith = ReplaceWith("DianZan"))
public val Like: PokeMessage = DianZan
/** 心碎 */
@JvmField
public val XinSui: PokeMessage = PokeMessage("心碎", 4, -1)
/** 心碎 */
@JvmField
@Deprecated("Use XinSui", replaceWith = ReplaceWith("XinSui"))
public val Heartbroken: PokeMessage = XinSui
/** 666 */
@JvmField
public val LiuLiuLiu: PokeMessage = PokeMessage("666", 5, -1)
/** 666 */
@JvmField
@Deprecated("Use LiuLiuLiu", replaceWith = ReplaceWith("LiuLiuLiu"))
public val SixSixSix: PokeMessage = LiuLiuLiu
/** 放大招 */
@JvmField
public val FangDaZhao: PokeMessage = PokeMessage("放大招", 6, -1)
/** 宝贝球 (SVIP) */
@JvmField
public val BaoBeiQiu: PokeMessage = PokeMessage("宝贝球", 126, 2011)
/** 玫瑰花 (SVIP) */
@JvmField
public val Rose: PokeMessage = PokeMessage("玫瑰花", 126, 2007)
/** 召唤术 (SVIP) */
@JvmField
public val ZhaoHuanShu: PokeMessage = PokeMessage("召唤术", 126, 2006)
/** 让你皮 (SVIP) */
@JvmField
public val RangNiPi: PokeMessage = PokeMessage("让你皮", 126, 2009)
/** 结印 (SVIP) */
@JvmField
public val JieYin: PokeMessage = PokeMessage("结印", 126, 2005)
/** 手雷 (SVIP) */
@JvmField
public val ShouLei: PokeMessage = PokeMessage("手雷", 126, 2004)
/** 勾引 */
@JvmField
public val GouYin: PokeMessage = PokeMessage("勾引", 126, 2003)
/** 抓一下 (SVIP) */
@JvmField
public val ZhuaYiXia: PokeMessage = PokeMessage("抓一下", 126, 2001)
/** 碎屏 (SVIP) */
@JvmField
public val SuiPing: PokeMessage = PokeMessage("碎屏", 126, 2002)
/** 敲门 (SVIP) */
@JvmField
public val QiaoMen: PokeMessage = PokeMessage("敲门", 126, 2002)
/**
* 所有类型数组
*/
@JvmField
public val values: Array<PokeMessage> = arrayOf(
ChuoYiChuo, BiXin, DianZan, XinSui, LiuLiuLiu,
FangDaZhao, BaoBeiQiu, Rose, ZhaoHuanShu, RangNiPi,
JieYin, ShouLei, GouYin, ZhuaYiXia, SuiPing
)
}
}

View File

@ -15,7 +15,6 @@ package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.data.MessageSource.Key.recall
import net.mamoe.mirai.utils.safeCast
@ -25,17 +24,17 @@ import net.mamoe.mirai.utils.safeCast
*
* 支持引用任何一条消息发送给任何人.
*
* #### 元数据
* ### 元数据
* [QuoteReply] 被作为 [MessageMetadata], 因为它不包含实际的消息内容, 且只能在消息中单独存在.
*
* #### [source] 的类型:
* ### [source] 的类型:
* - 在发送引用回复时, [source] 类型为 [OnlineMessageSource] [OfflineMessageSource]
* - 在接收引用回复时, [source] 类型一定为 [OfflineMessageSource]
*
* #### 原消息内容
* ### 原消息内容
* 引用回复的原消息内容完全由 [source] [MessageSource.originalMessage] 控制, 客户端不会自行寻找原消息.
*
* #### 客户端内跳转
* ### 客户端内跳转
* 客户端在跳转原消息时, 会通过 [MessageSource.ids] metadata
*
* @see MessageSource 获取有关消息源的更多信息
@ -58,13 +57,6 @@ public data class QuoteReply(
public override fun hashCode(): Int = source.hashCode()
}
/**
* @see MessageSource.bot
*/
@get:JvmSynthetic
public inline val QuoteReply.bot: Bot
get() = source.bot
/**
* 撤回引用的源消息
*/

View File

@ -0,0 +1,110 @@
/*
* 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/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.message.data.VipFace.Kind
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.safeCast
/**
* VIP 表情.
*
* 不支持发送.
*
* ## mirai 码支持
* 格式: &#91;mirai:vipface:*[Kind.id]*,*[Kind.name]*,*[count]*&#93;
*
* @see VipFace.Key 使用伴生对象中的常量
*/
@Serializable
@SerialName(VipFace.SERIAL_NAME)
public data class VipFace @MiraiInternalApi constructor(
/**
* 使用 [Companion] 中常量.
*/
public val kind: Kind,
public val count: Int
) : HummerMessage, CodableMessage {
override val key: MessageKey<VipFace> get() = Key
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append("[mirai:vipface:").append(kind).append(',').append(count).append(']')
}
override fun toString(): String = "[mirai:vipface:$kind,$count]"
override fun contentToString(): String = "[${kind.name}]x$count"
@Serializable
public data class Kind(
val id: Int,
val name: String
) {
public override fun toString(): String {
return "$id,$name"
}
}
public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, VipFace>(HummerMessage, { it.safeCast() }) {
public const val SERIAL_NAME: String = "VipFace"
@JvmField
public val LiuLian: Kind = 9 to "榴莲"
@JvmField
public val PingDiGuo: Kind = 1 to "平底锅"
@JvmField
public val ChaoPiao: Kind = 12 to "钞票"
@JvmField
public val LueLueLue: Kind = 10 to "略略略"
@JvmField
public val ZhuTou: Kind = 4 to "猪头"
@JvmField
public val BianBian: Kind = 6 to "便便"
@JvmField
public val ZhaDan: Kind = 5 to "炸弹"
@JvmField
public val AiXin: Kind = 2 to "爱心"
@JvmField
public val HaHa: Kind = 3 to "哈哈"
@JvmField
public val DianZan: Kind = 1 to "点赞"
@JvmField
public val QinQin: Kind = 7 to "亲亲"
@JvmField
public val YaoWan: Kind = 8 to "药丸"
@JvmField
public val values: Array<Kind> = arrayOf(
LiuLian, PingDiGuo, ChaoPiao, LueLueLue, ZhuTou,
BianBian, ZhaDan, AiXin, HaHa, DianZan, QinQin, YaoWan
)
private inline infix fun Int.to(name: String): Kind = Kind(this, name)
}
}

View File

@ -51,8 +51,8 @@ internal fun Message.contentEqualsStrictImpl(another: Message, ignoreCase: Boole
/**
* 逐个判断非 [PlainText] [Message] 是否 [equals]
*/
this.forEachContent { thisElement ->
if (thisElement is PlainText) return@forEachContent
this.contentsSequence().forEach { thisElement ->
if (thisElement is PlainText) return@forEach
for (it in anotherIterator) {
if (it is PlainText || it !is MessageContent) continue
if (thisElement != it) return false
@ -158,6 +158,14 @@ internal fun <M : SingleMessage> MessageChain.getImpl(key: MessageKey<M>): M? {
return this.asSequence().mapNotNull { key.safeCast.invoke(it) }.firstOrNull()
}
/**
* @return [EmptyMessageChain] if [delegate] is empty, otherwise [MessageChainImpl]
*/
internal fun createMessageChainImplOptimized(delegate: List<SingleMessage>): MessageChain {
return if (delegate.isEmpty()) EmptyMessageChain
else MessageChainImpl(delegate)
}
/**
* 使用 [Collection] 作为委托的 [MessageChain]
*/
@ -182,7 +190,7 @@ internal data class MessageChainImpl constructor(
@Suppress("FunctionName") // source compatibility with 1.x
internal fun MessageChainImplBySequence(
delegate: Sequence<SingleMessage> // 可以有重复 ConstrainSingle
): MessageChain = MessageChainImpl(delegate.constrainSingleMessages())
): MessageChain = createMessageChainImplOptimized(delegate.constrainSingleMessages())
//////////////////////