diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt index 0a5f6d91c..919b13ae3 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt @@ -43,6 +43,9 @@ private constructor(val target: Long, val display: String) : override fun contentToString(): String = this.display companion object Key : Message.Key<At> { + override val typeName: String + get() = "At" + /** * 构造一个 [At], 仅供内部使用, 否则可能造成消息无法发出的问题. */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt index 3357a5b51..1df5075bd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt @@ -33,6 +33,8 @@ object AtAll : @SinceMirai("0.31.2") const val display = displayA + override val typeName: String + get() = "AtAll" @Suppress("SpellCheckingInspection") override fun toString(): String = "[mirai:atall]" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt index 9f0ed43c5..3f60b9fd0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt @@ -32,6 +32,9 @@ class Face private constructor(val id: Int, private val stringValue: String) : */ @Suppress("SpellCheckingInspection", "unused") companion object IdList : Message.Key<Face> { + override val typeName: String + get() = "Face" + const val unknown: Int = 0xff const val jingya: Int = 0 const val piezui: Int = 1 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt index 76d36985b..5db564603 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt @@ -27,7 +27,10 @@ import kotlin.jvm.JvmSynthetic */ @SinceMirai("0.31.0") sealed class HummerMessage : MessageContent { - companion object Key : Message.Key<HummerMessage> + companion object Key : Message.Key<HummerMessage> { + override val typeName: String + get() = "HummerMessage" + } // has service type etc. } @@ -47,6 +50,9 @@ class PokeMessage @MiraiInternalAPI(message = "使用伴生对象中的常量") val id: Int ) : HummerMessage() { companion object Types : Message.Key<PokeMessage> { + override val typeName: String + get() = "PokeMessage" + /** 戳一戳 */ @JvmField val Poke = PokeMessage(1, -1) @@ -130,6 +136,9 @@ sealed class FlashImage : MessageContent, HummerMessage() { operator fun invoke(imageId: String): FlashImage { return invoke(Image(imageId)) } + + override val typeName: String + get() = "FlashImage" } /** @@ -170,7 +179,10 @@ inline fun FriendImage.flash(): FriendFlashImage = FlashImage(this) as FriendFla */ @SinceMirai("0.33.0") class GroupFlashImage(override val image: GroupImage) : FlashImage() { - companion object Key : Message.Key<GroupFlashImage> + companion object Key : Message.Key<GroupFlashImage> { + override val typeName: String + get() = "GroupFlashImage" + } } /** @@ -178,5 +190,8 @@ class GroupFlashImage(override val image: GroupImage) : FlashImage() { */ @SinceMirai("0.33.0") class FriendFlashImage(override val image: FriendImage) : FlashImage() { - companion object Key : Message.Key<FriendFlashImage> + companion object Key : Message.Key<FriendFlashImage> { + override val typeName: String + get() = "FriendFlashImage" + } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt index 20785d8d8..a680235c7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt @@ -33,7 +33,10 @@ import kotlin.jvm.JvmSynthetic * @see Image.flash 转换普通图片为闪照 */ interface Image : Message, MessageContent { - companion object Key : Message.Key<Image> + companion object Key : Message.Key<Image> { + override val typeName: String + get() = "Image" + } /** * 图片的 id. @@ -92,7 +95,10 @@ sealed class AbstractImage : Image { * 一般由 [Contact.uploadImage] 得到 */ interface OnlineImage : Image { - companion object Key : Message.Key<OnlineImage> + companion object Key : Message.Key<OnlineImage> { + override val typeName: String + get() = "OnlineImage" + } /** * 原图下载链接. 包含域名 @@ -124,7 +130,10 @@ suspend fun Image.queryUrl(): String { * 一般由 [Contact.uploadImage] 得到 */ interface OfflineImage : Image { - companion object Key : Message.Key<OfflineImage> + companion object Key : Message.Key<OfflineImage> { + override val typeName: String + get() = "OfflineImage" + } } /** @@ -149,7 +158,10 @@ suspend fun OfflineImage.queryUrl(): String { // CustomFace @OptIn(MiraiInternalAPI::class) sealed class GroupImage : AbstractImage() { - companion object Key : Message.Key<GroupImage> + companion object Key : Message.Key<GroupImage> { + override val typeName: String + get() = "GroupImage" + } abstract val filepath: String abstract val fileId: Int @@ -222,7 +234,10 @@ abstract class OnlineGroupImage : GroupImage(), OnlineImage */ // NotOnlineImage @OptIn(MiraiInternalAPI::class) sealed class FriendImage : AbstractImage() { - companion object Key : Message.Key<FriendImage> + companion object Key : Message.Key<FriendImage> { + override val typeName: String + get() = "FriendImage" + } abstract val resourceId: String abstract val md5: ByteArray diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt index 03dc6410d..9a2222a4c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt @@ -26,9 +26,9 @@ import kotlin.jvm.JvmSynthetic * - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource] * - [MessageContent] 单个消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] 等. * - [CombinedMessage] 通过 [plus] 连接的两个消息. 可通过 [asMessageChain] 转换为 [MessageChain] - * - [MessageChain] 不可变消息链, 即 [List] 形式链接的多个 [Message] 实例. + * - [MessageChain] 不可变消息链, 链表形式链接的多个 [SingleMessage] 实例. * - * **在 Kotlin 使用 [Message]** + * #### 在 Kotlin 使用 [Message]: * 这与使用 [String] 的使用非常类似. * * 比较 [Message] 与 [String] (使用 infix [Message.eq]): @@ -72,7 +72,13 @@ interface Message { * * @param M 指代持有这个 Key 的消息类型 */ - interface Key<out M : Message> + interface Key<out M : Message> { + /** + * 此 [Key] 指代的 [Message] 类型名. 一般为 `class.simpleName` + */ + @SinceMirai("0.34.0") + val typeName: String + } infix fun eq(other: Message): Boolean = this.toString() == other.toString() @@ -105,7 +111,7 @@ interface Message { if (this is ConstrainSingle<*> && tail is ConstrainSingle<*> && this.key == tail.key ) { - return CombinedMessage(EmptyMessageChain, this) + return CombinedMessage(EmptyMessageChain, tail) } return CombinedMessage(left = this, tail = tail) } @@ -119,6 +125,8 @@ interface Message { * [FriendImage]: "[mirai:image:/f8f1ab55-bf8e-4236-b55e-955848d7069f]" * [PokeMessage]: "[mirai:poke:1,-1]" * [MessageChain]: 直接无间隔地连接所有元素. + * + * @see contentToString */ override fun toString(): String @@ -141,17 +149,23 @@ interface Message { operator fun plus(another: CharSequence): CombinedMessage = this.followedBy(another.toString().toMessage()) } +@JvmSynthetic @Suppress("UNCHECKED_CAST") suspend inline fun <C : Contact> Message.sendTo(contact: C): MessageReceipt<C> { return contact.sendMessage(this) as MessageReceipt<C> } fun Message.repeat(count: Int): MessageChain { + if (this is ConstrainSingle<*>) { + // fast-path + return SingleMessageChainImpl(this) + } return buildMessageChain(count) { add(this@repeat) } } +@JvmSynthetic inline operator fun Message.times(count: Int): MessageChain = this.repeat(count) interface SingleMessage : Message, CharSequence, Comparable<String> @@ -168,13 +182,13 @@ interface MessageMetadata : SingleMessage { } /** - * 约束一个 [MessageChain] 中只存在这一种类型的元素. 新元素将会替换旧元素, 但不会保持原顺序. + * 约束一个 [MessageChain] 中只存在这一种类型的元素. 新元素将会替换旧元素, 保持原顺序. * * **MiraiExperimentalAPI**: 此 API 可能在将来版本修改 */ @SinceMirai("0.34.0") @MiraiExperimentalAPI -interface ConstrainSingle<M : Message> { +interface ConstrainSingle<M : Message> : SingleMessage { val key: Message.Key<M> } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index 457706a46..17d5c595f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -26,7 +26,7 @@ import kotlin.reflect.KProperty /** * 消息链. - * 它的一般实现为 [MessageChainImplByIterable] 或 [MessageChainImplBySequence], + * 它的一般实现为 [MessageChainImplByCollection] 或 [MessageChainImplBySequence], * 替代 `null` 情况的实现为 [NullMessageChain], * 空的实现为 [EmptyMessageChain] * @@ -170,7 +170,7 @@ fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) @JvmSynthetic @Suppress("UNCHECKED_CAST") inline fun <M : Message> MessageChain.first(key: Message.Key<M>): M = - firstOrNull(key) ?: throw NoSuchElementException("no such element: $key") + firstOrNull(key) ?: throw NoSuchElementException("Message type $key not found in chain $this") /** * 获取第一个 [M] 类型的 [Message] 实例 @@ -264,7 +264,8 @@ fun Message.asMessageChain(): MessageChain = when (this) { * 直接将 [this] 委托为一个 [MessageChain] */ @JvmSynthetic -inline fun Collection<SingleMessage>.asMessageChain(): MessageChain = MessageChainImplByCollection(this) +fun Collection<SingleMessage>.asMessageChain(): MessageChain = + MessageChainImplByCollection(this.constrainSingleMessages()) /** * 将 [this] [扁平化后][flatten] 委托为一个 [MessageChain] @@ -277,7 +278,8 @@ inline fun Collection<Message>.asMessageChain(): MessageChain = MessageChainImpl * 直接将 [this] 委托为一个 [MessageChain] */ @JvmSynthetic -inline fun Iterable<SingleMessage>.asMessageChain(): MessageChain = MessageChainImplByIterable(this) +fun Iterable<SingleMessage>.asMessageChain(): MessageChain = + MessageChainImplByCollection(this.constrainSingleMessages()) @JvmSynthetic inline fun MessageChain.asMessageChain(): MessageChain = this // 避免套娃 @@ -386,6 +388,7 @@ fun Message.flatten(): Sequence<SingleMessage> { } } +@JvmSynthetic // make Java user happier with less methods fun CombinedMessage.flatten(): Sequence<SingleMessage> { // already constrained single. if (this.isFlat()) { @@ -394,6 +397,7 @@ fun CombinedMessage.flatten(): Sequence<SingleMessage> { } else return this.asSequence().flatten() } +@JvmSynthetic // make Java user happier with less methods inline fun MessageChain.flatten(): Sequence<SingleMessage> = this.asSequence() // fast path // endregion converters @@ -402,7 +406,15 @@ inline fun MessageChain.flatten(): Sequence<SingleMessage> = this.asSequence() / /** * 不含任何元素的 [MessageChain] */ -object EmptyMessageChain : MessageChain by MessageChainImplByCollection(emptyList()) +object EmptyMessageChain : MessageChain, Iterator<SingleMessage> { + override fun contains(sub: String): Boolean = sub.isEmpty() + override val size: Int get() = 0 + override fun toString(): String = "" + override fun contentToString(): String = "" + override fun iterator(): Iterator<SingleMessage> = this + override fun hasNext(): Boolean = false + override fun next(): SingleMessage = throw NoSuchElementException("EmptyMessageChain is empty.") +} /** * Null 的 [MessageChain]. @@ -427,22 +439,45 @@ object NullMessageChain : MessageChain { // region implementations -/** - * 使用 [Iterable] 作为委托的 [MessageChain] - */ -@PublishedApi -internal class MessageChainImplByIterable constructor( - private val delegate: Iterable<SingleMessage> -) : Message, Iterable<SingleMessage>, MessageChain { - override val size: Int by lazy { delegate.count() } - override fun iterator(): Iterator<SingleMessage> = delegate.iterator() - private var toStringTemp: String? = null - override fun toString(): String = - toStringTemp ?: this.delegate.joinToString("") { it.toString() }.also { toStringTemp = it } +@Suppress("DuplicatedCode") // we don't have pattern matching +@OptIn(MiraiExperimentalAPI::class) +internal fun Sequence<SingleMessage>.constrainSingleMessages(): List<SingleMessage> { + val list = ArrayList<SingleMessage>(4) + val singleList = ArrayList<Message.Key<*>?>(4) - override fun contentToString(): String = toString() + for (singleMessage in this) { + if (singleMessage is ConstrainSingle<*>) { + val key = singleMessage.key + val index = singleList.indexOf(key) + if (index != -1) { + list[index] = singleMessage + continue + } else { + singleList.add(list.size, key) + } + } + list.add(singleMessage) + } + return list +} - override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } +@Suppress("DuplicatedCode") // we don't have pattern matching +@OptIn(MiraiExperimentalAPI::class) +internal fun Iterable<SingleMessage>.constrainSingleMessages(): List<SingleMessage> { + val list = ArrayList<SingleMessage>() + + for (singleMessage in this) { + if (singleMessage is ConstrainSingle<*>) { + val key = singleMessage.key + val index = list.indexOfFirst { it is ConstrainSingle<*> && it.key == key } + if (index != -1) { + list[index] = singleMessage + continue + } + } + list.add(singleMessage) + } + return list } /** @@ -450,7 +485,7 @@ internal class MessageChainImplByIterable constructor( */ @PublishedApi internal class MessageChainImplByCollection constructor( - private val delegate: Collection<SingleMessage> + private val delegate: Collection<SingleMessage> // 必须 constrainSingleMessages, 且为 immutable ) : Message, Iterable<SingleMessage>, MessageChain { override val size: Int get() = delegate.size override fun iterator(): Iterator<SingleMessage> = delegate.iterator() @@ -458,7 +493,10 @@ internal class MessageChainImplByCollection constructor( override fun toString(): String = toStringTemp ?: this.delegate.joinToString("") { it.toString() }.also { toStringTemp = it } - override fun contentToString(): String = toString() + private var contentToStringTemp: String? = null + override fun contentToString(): String = + contentToStringTemp ?: this.delegate.joinToString("") { it.contentToString() }.also { contentToStringTemp = it } + override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } } @@ -468,20 +506,24 @@ internal class MessageChainImplByCollection constructor( */ @PublishedApi internal class MessageChainImplBySequence constructor( - delegate: Sequence<SingleMessage> + delegate: Sequence<SingleMessage> // 可以有重复 ConstrainSingle ) : Message, Iterable<SingleMessage>, MessageChain { override val size: Int by lazy { collected.size } /** * [Sequence] 可能只能消耗一遍, 因此需要先转为 [List] */ - private val collected: List<SingleMessage> by lazy { delegate.toList() } + private val collected: List<SingleMessage> by lazy { delegate.constrainSingleMessages() } override fun iterator(): Iterator<SingleMessage> = collected.iterator() private var toStringTemp: String? = null override fun toString(): String = toStringTemp ?: this.collected.joinToString("") { it.toString() }.also { toStringTemp = it } - override fun contentToString(): String = toString() + private var contentToStringTemp: String? = null + override fun contentToString(): String = + contentToStringTemp ?: this.collected.joinToString("") { it.contentToString() } + .also { contentToStringTemp = it } + override operator fun contains(sub: String): Boolean = collected.any { it.contains(sub) } } @@ -495,7 +537,7 @@ internal class SingleMessageChainImpl constructor( ) : Message, Iterable<SingleMessage>, MessageChain { override val size: Int get() = 1 override fun toString(): String = this.delegate.toString() - override fun contentToString(): String = this.delegate.toString() + override fun contentToString(): String = this.delegate.contentToString() override fun iterator(): Iterator<SingleMessage> = iterator { yield(delegate) } override operator fun contains(sub: String): Boolean = sub in delegate } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt index 71873a84e..ec8b63724 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt @@ -42,7 +42,10 @@ import kotlin.jvm.JvmSynthetic */ @SinceMirai("0.33.0") sealed class MessageSource : Message, MessageMetadata { - companion object Key : Message.Key<MessageSource> + companion object Key : Message.Key<MessageSource> { + override val typeName: String + get() = "MessageSource" + } /** * 所属 [Bot] @@ -105,7 +108,10 @@ sealed class MessageSource : Message, MessageMetadata { @SinceMirai("0.33.0") @OptIn(MiraiExperimentalAPI::class) sealed class OnlineMessageSource : MessageSource(), ConstrainSingle<OnlineMessageSource> { - companion object Key : Message.Key<OnlineMessageSource> + companion object Key : Message.Key<OnlineMessageSource> { + override val typeName: String + get() = "OnlineMessageSource" + } override val key: Message.Key<OnlineMessageSource> get() = Key @@ -131,7 +137,10 @@ sealed class OnlineMessageSource : MessageSource(), ConstrainSingle<OnlineMessag * 由 [机器人主动发送消息][Contact.sendMessage] 产生的 [MessageSource] */ sealed class Outgoing : OnlineMessageSource() { - companion object Key : Message.Key<Outgoing> + companion object Key : Message.Key<Outgoing> { + override val typeName: String + get() = "OnlineMessageSource.Outgoing" + } abstract override val sender: Bot abstract override val target: Contact @@ -140,7 +149,10 @@ sealed class OnlineMessageSource : MessageSource(), ConstrainSingle<OnlineMessag final override val targetId: Long get() = target.id abstract class ToFriend : Outgoing() { - companion object Key : Message.Key<ToFriend> + companion object Key : Message.Key<ToFriend> { + override val typeName: String + get() = "OnlineMessageSource.Outgoing.ToFriend" + } abstract override val target: QQ final override val subject: QQ get() = target @@ -148,7 +160,10 @@ sealed class OnlineMessageSource : MessageSource(), ConstrainSingle<OnlineMessag } abstract class ToGroup : Outgoing() { - companion object Key : Message.Key<ToGroup> + companion object Key : Message.Key<ToGroup> { + override val typeName: String + get() = "OnlineMessageSource.Outgoing.ToGroup" + } abstract override val target: Group final override val subject: Group get() = target @@ -160,7 +175,10 @@ sealed class OnlineMessageSource : MessageSource(), ConstrainSingle<OnlineMessag * 接收到的一条消息的 [MessageSource] */ sealed class Incoming : OnlineMessageSource() { - companion object Key : Message.Key<Incoming> + companion object Key : Message.Key<Incoming> { + override val typeName: String + get() = "OnlineMessageSource.Incoming" + } abstract override val sender: QQ // out QQ abstract override val target: Bot @@ -169,7 +187,10 @@ sealed class OnlineMessageSource : MessageSource(), ConstrainSingle<OnlineMessag final override val targetId: Long get() = target.id abstract class FromFriend : Incoming() { - companion object Key : Message.Key<FromFriend> + companion object Key : Message.Key<FromFriend> { + override val typeName: String + get() = "OnlineMessageSource.Incoming.FromFriend" + } abstract override val sender: QQ final override val subject: QQ get() = sender @@ -177,7 +198,10 @@ sealed class OnlineMessageSource : MessageSource(), ConstrainSingle<OnlineMessag } abstract class FromGroup : Incoming() { - companion object Key : Message.Key<FromGroup> + companion object Key : Message.Key<FromGroup> { + override val typeName: String + get() = "OnlineMessageSource.Incoming.FromGroup" + } abstract override val sender: Member final override val subject: Group get() = sender.group @@ -224,7 +248,10 @@ inline fun MessageSource.recallIn( */ @SinceMirai("0.33.0") abstract class OfflineMessageSource : MessageSource() { - companion object Key : Message.Key<OfflineMessageSource> + companion object Key : Message.Key<OfflineMessageSource> { + override val typeName: String + get() = "OfflineMessageSource" + } enum class Kind { GROUP, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt index 3ce2b2661..11ec8289c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt @@ -42,7 +42,10 @@ class PlainText(val stringValue: String) : return stringValue.hashCode() } - companion object Key : Message.Key<PlainText> + companion object Key : Message.Key<PlainText> { + override val typeName: String + get() = "PlainText" + } } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt index 6c6310ae1..e19fce819 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt @@ -21,6 +21,8 @@ import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName +import kotlin.jvm.JvmOverloads +import kotlin.jvm.JvmSynthetic /** @@ -34,8 +36,10 @@ import kotlin.jvm.JvmName @OptIn(MiraiExperimentalAPI::class) @SinceMirai("0.33.0") class QuoteReply(val source: MessageSource) : Message, MessageMetadata, ConstrainSingle<QuoteReply> { - // TODO: 2020/4/4 Metadata or Content? - companion object Key : Message.Key<QuoteReply> + companion object Key : Message.Key<QuoteReply> { + override val typeName: String + get() = "QuoteReply" + } override val key: Message.Key<QuoteReply> get() = Key @@ -43,8 +47,10 @@ class QuoteReply(val source: MessageSource) : Message, MessageMetadata, Constrai override fun contentToString(): String = "" } +@JvmSynthetic suspend inline fun QuoteReply.recall() = this.source.recall() +@JvmOverloads inline fun QuoteReply.recallIn( millis: Long, coroutineContext: CoroutineContext = EmptyCoroutineContext diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt index 8574d641d..e8159a540 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt @@ -36,14 +36,6 @@ interface RichMessage : MessageContent { @SinceMirai("0.30.0") companion object Templates : Message.Key<RichMessage> { - /** - * 合并转发. - */ - @MiraiExperimentalAPI - fun mergedForward(): Nothing { - TODO() - } - /** * 长消息. * @@ -100,6 +92,9 @@ interface RichMessage : MessageContent { } } } + + override val typeName: String + get() = "RichMessage" } val content: String @@ -118,7 +113,10 @@ interface RichMessage : MessageContent { @SinceMirai("0.27.0") @OptIn(MiraiExperimentalAPI::class) class JsonMessage(override val content: String) : RichMessage { - companion object Key : Message.Key<JsonMessage> + companion object Key : Message.Key<JsonMessage> { + override val typeName: String + get() = "JsonMessage" + } // serviceId = 1 override fun toString(): String = "[mirai:json:$content]" @@ -133,7 +131,10 @@ class JsonMessage(override val content: String) : RichMessage { @SinceMirai("0.27.0") class LightApp constructor(override val content: String) : RichMessage { - companion object Key : Message.Key<LightApp> + companion object Key : Message.Key<LightApp> { + override val typeName: String + get() = "LightApp" + } override fun toString(): String = "[mirai:app:$content]" } @@ -147,7 +148,10 @@ class LightApp constructor(override val content: String) : RichMessage { @SinceMirai("0.27.0") @OptIn(MiraiExperimentalAPI::class) class XmlMessage constructor(override val content: String) : RichMessage { - companion object Key : Message.Key<XmlMessage> + companion object Key : Message.Key<XmlMessage> { + override val typeName: String + get() = "XmlMessage" + } // override val serviceId: Int get() = 60 @@ -161,7 +165,10 @@ class XmlMessage constructor(override val content: String) : RichMessage { @MiraiExperimentalAPI @MiraiInternalAPI class LongMessage(override val content: String, val resId: String) : RichMessage { - companion object Key : Message.Key<XmlMessage> + companion object Key : Message.Key<XmlMessage> { + override val typeName: String + get() = "LongMessage" + } // serviceId = 35 override fun toString(): String = "[mirai:long:$content]" diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ConstrainSingleTest.kt b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ConstrainSingleTest.kt new file mode 100644 index 000000000..f9c44fc1b --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ConstrainSingleTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2020 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 kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertSame + + +internal class ConstrainSingleTest { + + @OptIn(MiraiExperimentalAPI::class) + internal class TestMessage : ConstrainSingle<TestMessage>, Any() { + companion object Key : Message.Key<TestMessage> { + override val typeName: String + get() = "TestMessage" + } + + override fun toString(): String = super.toString() + + override fun contentToString(): String { + TODO("Not yet implemented") + } + + override val key: Message.Key<TestMessage> + get() = Key + override val length: Int + get() = TODO("Not yet implemented") + + override fun get(index: Int): Char { + TODO("Not yet implemented") + } + + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { + TODO("Not yet implemented") + } + + override fun compareTo(other: String): Int { + TODO("Not yet implemented") + } + } + + @OptIn(MiraiExperimentalAPI::class) + @Test + fun testConstrainSingleInPlus() { + val new = TestMessage() + val combined = TestMessage() + new + + assertEquals(combined.left, EmptyMessageChain) + assertSame(combined.tail, new) + } + + @Test // net.mamoe.mirai/message/data/MessageChain.kt:441 + fun testConstrainSingleInSequence() { + val last = TestMessage() + val sequence: Sequence<SingleMessage> = sequenceOf( + TestMessage(), + TestMessage(), + last + ) + + val result = sequence.constrainSingleMessages() + assertEquals(result.count(), 1) + assertSame(result.single(), last) + } + + @Test // net.mamoe.mirai/message/data/MessageChain.kt:441 + fun testConstrainSingleOrderInSequence() { + val last = TestMessage() + val sequence: Sequence<SingleMessage> = sequenceOf( + TestMessage(), // last should replace here + PlainText("test"), + TestMessage(), + last + ) + + val result = sequence.constrainSingleMessages() + assertEquals(result.count(), 2) + assertSame(result.first(), last) + } +} \ No newline at end of file