diff --git a/mirai-core-api/src/commonMain/kotlin/contact/ContactOrBot.kt b/mirai-core-api/src/commonMain/kotlin/contact/ContactOrBot.kt index 2fc10aa7a..3bf5a2b1a 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/ContactOrBot.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/ContactOrBot.kt @@ -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 号或群号. */ diff --git a/mirai-core-api/src/commonMain/kotlin/message/MessageSerializers.kt b/mirai-core-api/src/commonMain/kotlin/message/MessageSerializers.kt index 43c7f8fd9..2e2b45520 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/MessageSerializers.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/MessageSerializers.kt @@ -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 registerSerializer(baseClass: KClass, serializer: KSerializer) 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 77b9fc9ad..2f111c0bf 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt @@ -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.followedBy(tail) diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/CustomMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/CustomMessage.kt index 60003e83b..d29089c53 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/CustomMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/CustomMessage.kt @@ -192,6 +192,8 @@ public fun T.toByteArray(): ByteArray { * 2. 添加伴生对象, 继承 [CustomMessage.ProtoBufSerializerFactory] 或 [CustomMessage.JsonSerializerFactory], 或 [CustomMessage.Factory] * 3. 在需要解析消息前调用一次伴生对象以注册 * + * 注意: 这是实验性 API. 可能会在未来发生变动. + * * @see CustomMessage 查看更多信息 * @see ConstrainSingle 可实现此接口以保证消息链中只存在一个元素 */ 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 a82466927..6c474d391 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Face.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Face.kt @@ -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 = Array(290) { "[表情]" } + + + @JvmField + public val names: Array = Array(290) { "[表情]" } init { names[JING_YA] = "[惊讶]" diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt new file mode 100644 index 000000000..83893766f --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt @@ -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 码支持 + * 格式: [mirai:flash:*[Image.imageId]*] + * + * @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 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, { 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) \ No newline at end of file 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 212c4bf99..dcbf65ab0 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt @@ -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] diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt index 3c30310f4..3ec5f5b9f 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt @@ -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, { it.castOrNull() }) // has service type etc. -} - -//////////////////////////////////////// -///////////// POKE MESSAGE ///////////// -//////////////////////////////////////// - -/** - * 戳一戳. 可以发送给好友或群. - * - * ## mirai 码支持 - * 格式: [mirai:poke:*[name]*,*[pokeType]*,*[id]*] - * - * @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 - get() = Key - - - public companion object Key : - AbstractPolymorphicMessageKey(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 = 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 - get() = Key - - public companion object Key : - AbstractPolymorphicMessageKey(HummerMessage, { it.safeCast() }) { - public const val SERIAL_NAME: String = "MarketFace" - } - - override fun contentToString(): String = name -} - - -//////////////////////////////////// -///////////// VIP FACE ///////////// -//////////////////////////////////// - -/** - * VIP 表情. - * - * 不支持发送. - * - * ## mirai 码支持 - * 格式: [mirai:vipface:*[Kind.id]*,*[Kind.name]*,*[count]*] - * - * @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 - get() = Key - - public companion object Key : - AbstractPolymorphicMessageKey(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 = 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 码支持 - * 格式: [mirai:flash:*[Image.imageId]*] - * - * @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 - get() = Key - - public companion object Key : - AbstractPolymorphicMessageKey(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) \ No newline at end of file +} \ No newline at end of file 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 2dbc15532..0a0ade1d5 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt @@ -88,14 +88,12 @@ public interface Image : Message, MessageContent, CodableMessage { */ public val imageId: String - @kotlinx.serialization.Serializer(forClass = Image::class) public object AsStringSerializer : KSerializer by String.serializer().mapPrimitive( SERIAL_NAME, serialize = { imageId }, deserialize = { Image(it) }, ) - @kotlinx.serialization.Serializer(forClass = Image::class) public object Serializer : KSerializer 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 校验值. * diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt new file mode 100644 index 000000000..4a4151743 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt @@ -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 get() = Key + + override fun contentToString(): String = name + + public companion object Key : + AbstractPolymorphicMessageKey(HummerMessage, { it.safeCast() }) { + public const val SERIAL_NAME: String = "MarketFace" + } +} \ No newline at end of file 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 45d361bf1..5f9d4901d 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt @@ -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 * 单个消息元素. 与之相对的是 [MessageChain], 是多个 [SingleMessage] 的集合. */ @Serializable(SingleMessage.Serializer::class) -public interface SingleMessage : Message { - @kotlinx.serialization.Serializer(forClass = SingleMessage::class) - public object Serializer : - KSerializer by PolymorphicSerializer(SingleMessage::class) +public interface SingleMessage : Message { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5 + public object Serializer : KSerializer 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({ it.safeCast() }) } 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 29c2fcbac..9ac0f9e31 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt @@ -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] 的有序集合. * - * 要获取更多消息相关的信息, 查看 [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.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]: + * ```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`. 可以以 [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, RandomAccess, CodableMessage { - /** - * 元素数量. [EmptyMessageChain] 不参加计数. - */ - public override val size: Int +public interface MessageChain : + Message, List, 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, RandomAccess, Coda public operator fun get(key: MessageKey): 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 contains(key: MessageKey): Boolean = asSequence().any { key.safeCast.invoke(it) != null } @@ -106,7 +236,14 @@ public interface MessageChain : Message, List, RandomAccess, Coda forEach { it.safeCast()?.appendMiraiCodeTo(builder) } } - @kotlinx.serialization.Serializer(MessageChain::class) + /** + * 将 [MessageChain] 作为 `List` 序列化. 使用 [多态序列化][Polymorphic]. + * + * 在实践时请提供 [MessageSerializers.serializersModule] 到指定 [SerialFormat]. + * + * @see ListSerializer + * @see MessageSerializers + */ public object Serializer : KSerializer { @Suppress("DEPRECATION_ERROR") private val delegate = ListSerializer(PolymorphicSerializer(SingleMessage::class)) @@ -115,7 +252,6 @@ public interface MessageChain : Message, List, 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, RandomAccess, Coda /** * 不含任何元素的 [MessageChain]. */ -public object EmptyMessageChain : MessageChain, Iterator, List 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 by emptyList() { + override val size: Int get() = 0 - public override fun iterator(): Iterator = 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 = EmptyMessageChainIterator + + private object EmptyMessageChainIterator : Iterator { + 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, List MessageChain.getOrFail( key: MessageKey, crossinline lazyMessage: (key: MessageKey) -> String = { key.toString() } ): M = get(key) ?: throw NoSuchElementException(lazyMessage(key)) - /** - * 遍历每一个 [消息内容][MessageContent] + * 获取 `Sequence` + * 相当于 `this.asSequence().filterIsInstance()` */ @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 = + this.asSequence().filterIsInstance() /** - * 如果每一个 [消息内容][MessageContent] 都满足 [block], 返回 `true` + * 获取 `Sequence` + * 相当于 `this.asSequence().filterIsInstance()` */ @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 = + this.asSequence().filterIsInstance() /** - * 如果每一个 [消息内容][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 = this.filterIsInstance() + +/** + * 筛选 [MessageContent] + */ +public fun MessageChain.contentsList(): List = this.filterIsInstance() /** @@ -307,7 +434,8 @@ public inline fun messageChainOf(vararg messages: Message): MessageChain = messa * 扁平化 [this] 并创建一个 [MessageChain]. */ @JvmName("newChain") -public fun Sequence.toMessageChain(): MessageChain = MessageChainImpl(this.constrainSingleMessages()) +public fun Sequence.toMessageChain(): MessageChain = + createMessageChainImplOptimized(this.constrainSingleMessages()) /** * 扁平化 [this] 并创建一个 [MessageChain]. @@ -415,3 +543,77 @@ public inline fun MessageChain.orElse( ): OrNullDelegate = OrNullDelegate(this.firstIsInstanceOrNull() ?: 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 +} 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 09419caf3..45b9b2c08 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageChainBuilder.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageChainBuilder.kt @@ -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] */ 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 76986de20..657c198bb 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt @@ -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({ 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, { 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, { 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, { 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, { 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, { 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, { 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, { 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, { 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, { 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, { 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, { 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({ 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, { 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, { 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, { 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, { 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, { 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, { 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, { 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, { 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, { 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, { 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, { it.safeCast() }) + + /** + * 消息种类 + */ + public abstract val kind: MessageSourceKind +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/PokeMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/PokeMessage.kt new file mode 100644 index 000000000..1503dddc1 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/message/data/PokeMessage.kt @@ -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 码支持 + * 格式: [mirai:poke:*[name]*,*[pokeType]*,*[id]*] + * + * @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 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, { 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 = arrayOf( + ChuoYiChuo, BiXin, DianZan, XinSui, LiuLiuLiu, + FangDaZhao, BaoBeiQiu, Rose, ZhaoHuanShu, RangNiPi, + JieYin, ShouLei, GouYin, ZhuaYiXia, SuiPing + ) + } +} \ No newline at end of file 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 4c4cde87a..cfbc5abdf 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt @@ -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 - /** * 撤回引用的源消息 */ diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/VipFace.kt b/mirai-core-api/src/commonMain/kotlin/message/data/VipFace.kt new file mode 100644 index 000000000..b0616b659 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/message/data/VipFace.kt @@ -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 码支持 + * 格式: [mirai:vipface:*[Kind.id]*,*[Kind.name]*,*[count]*] + * + * @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 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, { 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 = 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) + } +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/impl.kt b/mirai-core-api/src/commonMain/kotlin/message/data/impl.kt index a8d86fc0b..137a44988 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/impl.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/impl.kt @@ -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 MessageChain.getImpl(key: MessageKey): M? { return this.asSequence().mapNotNull { key.safeCast.invoke(it) }.firstOrNull() } +/** + * @return [EmptyMessageChain] if [delegate] is empty, otherwise [MessageChainImpl] + */ +internal fun createMessageChainImplOptimized(delegate: List): 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 // 可以有重复 ConstrainSingle -): MessageChain = MessageChainImpl(delegate.constrainSingleMessages()) +): MessageChain = createMessageChainImplOptimized(delegate.constrainSingleMessages()) //////////////////////