Redesign MessageChain hierarchy: Add LinearMessageChainImpl and CombinedMessage

This commit is contained in:
Him188 2022-01-20 23:10:50 +00:00
parent b40b681f81
commit 0c708c8197
20 changed files with 1458 additions and 246 deletions

View File

@ -3608,7 +3608,7 @@ public final class net/mamoe/mirai/message/data/Dice$Key : net/mamoe/mirai/messa
public final fun serializer ()Lkotlinx/serialization/KSerializer; public final fun serializer ()Lkotlinx/serialization/KSerializer;
} }
public final class net/mamoe/mirai/message/data/EmptyMessageChain : java/util/List, kotlin/jvm/internal/markers/KMappedMarker, net/mamoe/mirai/message/data/MessageChain { public final class net/mamoe/mirai/message/data/EmptyMessageChain : java/util/List, kotlin/jvm/internal/markers/KMappedMarker, net/mamoe/mirai/message/data/MessageChain, net/mamoe/mirai/message/data/MessageChainImpl {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/EmptyMessageChain; public static final field INSTANCE Lnet/mamoe/mirai/message/data/EmptyMessageChain;
public synthetic fun add (ILjava/lang/Object;)V public synthetic fun add (ILjava/lang/Object;)V
public fun add (ILnet/mamoe/mirai/message/data/SingleMessage;)V public fun add (ILnet/mamoe/mirai/message/data/SingleMessage;)V
@ -3624,6 +3624,7 @@ public final class net/mamoe/mirai/message/data/EmptyMessageChain : java/util/Li
public fun equals (Ljava/lang/Object;)Z public fun equals (Ljava/lang/Object;)Z
public synthetic fun get (I)Ljava/lang/Object; public synthetic fun get (I)Ljava/lang/Object;
public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage; public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage;
public fun getHasConstrainSingle ()Z
public fun getSize ()I public fun getSize ()I
public fun hashCode ()I public fun hashCode ()I
public final fun indexOf (Ljava/lang/Object;)I public final fun indexOf (Ljava/lang/Object;)I

View File

@ -3608,7 +3608,7 @@ public final class net/mamoe/mirai/message/data/Dice$Key : net/mamoe/mirai/messa
public final fun serializer ()Lkotlinx/serialization/KSerializer; public final fun serializer ()Lkotlinx/serialization/KSerializer;
} }
public final class net/mamoe/mirai/message/data/EmptyMessageChain : java/util/List, kotlin/jvm/internal/markers/KMappedMarker, net/mamoe/mirai/message/data/MessageChain { public final class net/mamoe/mirai/message/data/EmptyMessageChain : java/util/List, kotlin/jvm/internal/markers/KMappedMarker, net/mamoe/mirai/message/data/MessageChain, net/mamoe/mirai/message/data/MessageChainImpl {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/EmptyMessageChain; public static final field INSTANCE Lnet/mamoe/mirai/message/data/EmptyMessageChain;
public synthetic fun add (ILjava/lang/Object;)V public synthetic fun add (ILjava/lang/Object;)V
public fun add (ILnet/mamoe/mirai/message/data/SingleMessage;)V public fun add (ILnet/mamoe/mirai/message/data/SingleMessage;)V
@ -3624,6 +3624,7 @@ public final class net/mamoe/mirai/message/data/EmptyMessageChain : java/util/Li
public fun equals (Ljava/lang/Object;)Z public fun equals (Ljava/lang/Object;)Z
public synthetic fun get (I)Ljava/lang/Object; public synthetic fun get (I)Ljava/lang/Object;
public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage; public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage;
public fun getHasConstrainSingle ()Z
public fun getSize ()I public fun getSize ()I
public fun hashCode ()I public fun hashCode ()I
public final fun indexOf (Ljava/lang/Object;)I public final fun indexOf (Ljava/lang/Object;)I

View File

@ -92,7 +92,7 @@ private val builtInSerializersModule by lazy {
// contextual(SingleMessage::class, SingleMessage.Serializer) // contextual(SingleMessage::class, SingleMessage.Serializer)
contextual(MessageChain::class, MessageChain.Serializer) contextual(MessageChain::class, MessageChain.Serializer)
contextual(MessageChainImpl::class, MessageChainImpl.serializer()) contextual(LinearMessageChainImpl::class, LinearMessageChainImpl.serializer())
contextual(ShowImageFlag::class, ShowImageFlag.Serializer) contextual(ShowImageFlag::class, ShowImageFlag.Serializer)

View File

@ -0,0 +1,207 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.message.data
import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor
import net.mamoe.mirai.message.data.visitor.accept
import net.mamoe.mirai.utils.MiraiInternalApi
/**
* One after one, hierarchically.
* @since 2.12
*/
@MiraiInternalApi
public class CombinedMessage @MessageChainConstructor constructor(
@MiraiInternalApi public val element: Message,
@MiraiInternalApi public val tail: Message,
@MiraiInternalApi public override val hasConstrainSingle: Boolean
) : MessageChainImpl, List<SingleMessage> {
override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R {
return visitor.visitCombinedMessage(this, data)
}
override fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) {
element.accept(visitor, data)
tail.accept(visitor, data)
}
override val size: Int by lazy {
if (slowList.isInitialized()) return@lazy slowList.value.size
var size = 0
val visitor = object : RecursiveMessageVisitor<Unit>() {
override fun visitMessageChain(messageChain: MessageChain, data: Unit) {
if (messageChain is DirectSizeAccess) {
size += messageChain.size
return
}
return super.visitMessageChain(messageChain, data)
}
override fun visitSingleMessage(message: SingleMessage, data: Unit) {
size++
}
}
element.accept(visitor)
tail.accept(visitor)
size
}
override fun isEmpty(): Boolean {
if (slowList.isInitialized()) return slowList.value.isEmpty()
return element is MessageChain && element.isEmpty() && tail is MessageChain && tail.isEmpty()
}
override fun contains(element: SingleMessage): Boolean {
if (slowList.isInitialized()) return slowList.value.contains(element)
if (this.element == element) return true
if (this.tail == element) return true
if (this.element is MessageChain && this.element.contains(element)) return true
if (this.tail is MessageChain && this.tail.contains(element)) return true
return false
}
private val toStringTemp: String by lazy {
if (slowList.isInitialized()) return@lazy slowList.value.toString()
buildString {
accept(object : RecursiveMessageVisitor<Unit>() {
override fun visitSingleMessage(message: SingleMessage, data: Unit) {
append(message.toString())
}
override fun visitMessageChain(messageChain: MessageChain, data: Unit) {
if (messageChain is DirectToStringAccess) {
append(messageChain.toString())
} else {
super.visitMessageChain(messageChain, data)
}
}
})
}
}
private val contentToStingTemp: String by lazy {
if (slowList.isInitialized()) return@lazy slowList.value.contentToString()
buildString {
accept(object : RecursiveMessageVisitor<Unit>() {
override fun visitSingleMessage(message: SingleMessage, data: Unit) {
append(message.contentToString())
}
override fun visitMessageChain(messageChain: MessageChain, data: Unit) {
if (messageChain is DirectToStringAccess) {
append(messageChain.contentToString())
} else {
super.visitMessageChain(messageChain, data)
}
}
})
}
}
override fun toString(): String = toStringTemp
override fun contentToString(): String = contentToStingTemp
override fun containsAll(elements: Collection<SingleMessage>): Boolean {
if (slowList.isInitialized()) return slowList.value.containsAll(elements)
if (elements.isEmpty()) return true
if (this.isEmpty()) return false
val remaining = elements.toMutableList()
accept(object : RecursiveMessageVisitor<Unit>() {
override fun isFinished(): Boolean = remaining.isEmpty()
override fun visitSingleMessage(message: SingleMessage, data: Unit) {
remaining.remove(message)
}
})
return remaining.isEmpty()
}
override fun iterator(): Iterator<SingleMessage> {
if (slowList.isInitialized()) return slowList.value.iterator()
suspend fun SequenceScope<SingleMessage>.yieldMessage(element: Message) {
when (element) {
is SingleMessage -> {
yield(element)
}
is MessageChain -> {
yieldAll(element.iterator())
}
}
}
return iterator {
yieldMessage(element)
yieldMessage(tail)
}
}
override fun subList(fromIndex: Int, toIndex: Int): List<SingleMessage> {
if (slowList.isInitialized()) return slowList.value.subList(fromIndex, toIndex)
if (fromIndex < 0 || fromIndex > toIndex) throw IndexOutOfBoundsException("fromIndex: $fromIndex, toIndex: $toIndex")
return buildList {
accept(object : RecursiveMessageVisitor<Unit>() {
private var currentIndex = 0
override fun isFinished(): Boolean = currentIndex >= toIndex
override fun visitSingleMessage(message: SingleMessage, data: Unit) {
if (isFinished()) return
if (currentIndex >= fromIndex) {
add(message)
}
currentIndex++
}
})
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CombinedMessage
if (element != other.element) return false
if (tail != other.tail) return false
if (hasConstrainSingle != other.hasConstrainSingle) return false
return true
}
override fun hashCode(): Int {
var result = element.hashCode()
result = 31 * result + tail.hashCode()
result = 31 * result + hasConstrainSingle.hashCode()
return result
}
///////////////////////////////////////////////////////////////////////////
// slow operations
///////////////////////////////////////////////////////////////////////////
internal val slowList: Lazy<MessageChain> = lazy {
sequenceOf(element, tail).toMessageChain()
}
// [MessageChain] implements [RandomAccess] so we should ensure that property here.
override fun get(index: Int): SingleMessage = slowList.value[index]
override fun indexOf(element: SingleMessage): Int = slowList.value.indexOf(element)
override fun lastIndexOf(element: SingleMessage): Int = slowList.value.lastIndexOf(element)
override fun listIterator(): ListIterator<SingleMessage> = slowList.value.listIterator()
override fun listIterator(index: Int): ListIterator<SingleMessage> = slowList.value.listIterator(index)
}
internal interface DirectSizeAccess : MessageChain
internal interface DirectToStringAccess : MessageChain

View File

@ -240,7 +240,20 @@ public interface Message {
* @see plus `+` 操作符重载 * @see plus `+` 操作符重载
*/ */
@JvmSynthetic // in java they should use `plus` instead @JvmSynthetic // in java they should use `plus` instead
public fun followedBy(tail: Message): MessageChain = followedByImpl(tail) public fun followedBy(tail: Message): MessageChain {
var constrainSingleCount = 0
if (this.hasConstrainSingle) constrainSingleCount++
if (tail.hasConstrainSingle) constrainSingleCount++
return if (constrainSingleCount == 0) {
// Future optimize:
// When constrainSingleCount == 1, see if we can connect by CombinedMessage,
// this need some kind of replacement of `hasConstrainSingle` with more information about MessageKeys.
@OptIn(MessageChainConstructor::class)
CombinedMessage(this, tail, false)
} else {
LinearMessageChainImpl.combineCreate(this, tail)
}
}
/** 将 [another] 按顺序连接到这个消息的尾部. */ /** 将 [another] 按顺序连接到这个消息的尾部. */
public operator fun plus(another: MessageChain): MessageChain = this + another as Message public operator fun plus(another: MessageChain): MessageChain = this + another as Message
@ -277,14 +290,14 @@ public interface Message {
/** /**
* @suppress 这是内部 API, 不要在任何情况下调用 * @suppress 这是内部 API, 不要在任何情况下调用
* @since MESSAGE_VISITOR * @since 2.12
*/ */
@MiraiInternalApi @MiraiInternalApi
public fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R = visitor.visitMessage(this, data) public fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R = visitor.visitMessage(this, data)
/** /**
* @suppress 这是内部 API, 不要在任何情况下调用 * @suppress 这是内部 API, 不要在任何情况下调用
* @since MESSAGE_VISITOR * @since 2.12
*/ */
@MiraiInternalApi @MiraiInternalApi
public fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) { public fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) {

View File

@ -143,6 +143,11 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_AB
* 例如用户发送了两条内容相同的消息, 但其中一条带有引用回复而另一条没有, 则两条消息的索引可能有变化 (当然内容顺序不会改变, 只是 [QuoteReply] 的位置有可能会变化). * 例如用户发送了两条内容相同的消息, 但其中一条带有引用回复而另一条没有, 则两条消息的索引可能有变化 (当然内容顺序不会改变, 只是 [QuoteReply] 的位置有可能会变化).
* 因此在使用直接索引访问时要格外注意兼容性, 故不推荐这种访问方案. * 因此在使用直接索引访问时要格外注意兼容性, 故不推荐这种访问方案.
* *
* ### 避免索引访问以提高性能
*
* 2.12 , [MessageChain] 内部结构有性能优化. 该优化大幅降低元素数量多的 [MessageChain] 的连接的时间复杂度.
* 性能优化默认生效, 但若使用 [get], [subList] [List] [Collection] 之外的方法时则会让该优化失效 (相比 2.12 以前不会丢失性能, 只是没有优化).
*
* ## 撤回和引用 * ## 撤回和引用
* *
* 要撤回消息, 查看 [MessageSource] * 要撤回消息, 查看 [MessageSource]
@ -253,11 +258,6 @@ public sealed interface MessageChain :
@MiraiInternalApi @MiraiInternalApi
override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R = visitor.visitMessageChain(this, data) override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R = visitor.visitMessageChain(this, data)
@MiraiInternalApi
override fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) {
forEach { it.accept(visitor, data) }
}
/** /**
* [MessageChain] 作为 `List<SingleMessage>` 序列化. 使用 [多态序列化][Polymorphic]. * [MessageChain] 作为 `List<SingleMessage>` 序列化. 使用 [多态序列化][Polymorphic].
* *
@ -349,7 +349,7 @@ public sealed interface MessageChain :
/** /**
* 返回一个不含任何元素的 [MessageChain]. * 返回一个不含任何元素的 [MessageChain].
* *
* @since 2.11 * @since 2.12
*/ */
// Java: MessageUtils.emptyMessageChain() // Java: MessageUtils.emptyMessageChain()
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -363,14 +363,18 @@ public fun emptyMessageChain(): MessageChain = EmptyMessageChain
"Please use emptyMessageChain()", "Please use emptyMessageChain()",
replaceWith = ReplaceWith("emptyMessageChain()", "net.mamoe.mirai.message.data.emptyMessageChain") replaceWith = ReplaceWith("emptyMessageChain()", "net.mamoe.mirai.message.data.emptyMessageChain")
) )
@DeprecatedSinceMirai(warningSince = "2.11") @DeprecatedSinceMirai(warningSince = "2.12")
public object EmptyMessageChain : MessageChain, List<SingleMessage> by emptyList() { public object EmptyMessageChain : MessageChain, List<SingleMessage> by emptyList(), MessageChainImpl {
override val size: Int get() = 0 override val size: Int get() = 0
override fun toString(): String = "" override fun toString(): String = ""
override fun contentToString(): String = "" override fun contentToString(): String = ""
override fun serializeToMiraiCode(): String = "" override fun serializeToMiraiCode(): String = ""
@MiraiInternalApi
override val hasConstrainSingle: Boolean
get() = false
@MiraiExperimentalApi @MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) { override fun appendMiraiCodeTo(builder: StringBuilder) {
} }
@ -486,7 +490,7 @@ public inline fun messageChainOf(vararg messages: Message): MessageChain = messa
*/ */
@JvmName("newChain") @JvmName("newChain")
public fun Sequence<Message>.toMessageChain(): MessageChain = public fun Sequence<Message>.toMessageChain(): MessageChain =
createMessageChainImplOptimized(this.constrainSingleMessages()) LinearMessageChainImpl.create(ConstrainSingleHelper.constrainSingleMessages(this))
/** /**
* 扁平化 [this] 并创建一个 [MessageChain]. * 扁平化 [this] 并创建一个 [MessageChain].
@ -525,11 +529,8 @@ public inline fun Array<out Message>.toMessageChain(): MessageChain = this.asSeq
@JvmName("newChain") @JvmName("newChain")
public fun Message.toMessageChain(): MessageChain = when (this) { public fun Message.toMessageChain(): MessageChain = when (this) {
is MessageChain -> (this as List<SingleMessage>).toMessageChain() is MessageChain -> (this as List<SingleMessage>).toMessageChain()
else -> MessageChainImpl( is SingleMessage -> LinearMessageChainImpl.create(listOf(this), this is ConstrainSingle)
listOf( else -> error("Message is either MessageChain nor SingleMessage: $this")
this as? SingleMessage ?: error("Message is either MessageChain nor SingleMessage: $this")
)
)
} }

View File

@ -1,10 +1,10 @@
/* /*
* Copyright 2019-2021 Mamoe Technologies and contributors. * Copyright 2019-2022 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * 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 * https://github.com/mamoe/mirai/blob/dev/LICENSE
*/ */
@file:JvmMultifileClass @file:JvmMultifileClass
@ -176,7 +176,7 @@ public class MessageChainBuilder private constructor(
// avoid resolution to extensions // avoid resolution to extensions
public fun asMessageChain(): MessageChain { public fun asMessageChain(): MessageChain {
this.flushCache() this.flushCache()
return createMessageChainImplOptimized(this.constrainSingleMessages()) return this.toMessageChain()
} }
/** 同 [asMessageChain] */ /** 同 [asMessageChain] */

View File

@ -17,8 +17,10 @@ import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1 import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2 import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2
import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.asImmutable import net.mamoe.mirai.utils.asImmutable
import net.mamoe.mirai.utils.castOrNull
import net.mamoe.mirai.utils.replaceAllKotlin import net.mamoe.mirai.utils.replaceAllKotlin
// region image // region image
@ -27,18 +29,6 @@ import net.mamoe.mirai.utils.replaceAllKotlin
//// IMPLEMENTATIONS //// //// IMPLEMENTATIONS ////
///////////////////////// /////////////////////////
@Suppress("unused", "UNUSED_PARAMETER")
private fun Message.hasDuplicationOfConstrain(key: MessageKey<*>): Boolean {
return true
/*
return when (this) {
is SingleMessage -> (this as? ConstrainSingle)?.key == key
is CombinedMessage -> return this.left.hasDuplicationOfConstrain(key) || this.tail.hasDuplicationOfConstrain(key)
is MessageChainImplByCollection -> this.delegate.any { (it as? ConstrainSingle)?.key == key }
else -> error("stub")
}*/
}
@JvmSynthetic @JvmSynthetic
internal fun Message.contentEqualsStrictImpl(another: Message, ignoreCase: Boolean): Boolean { internal fun Message.contentEqualsStrictImpl(another: Message, ignoreCase: Boolean): Boolean {
if (!this.contentToString().equals(another.contentToString(), ignoreCase = ignoreCase)) return false if (!this.contentToString().equals(another.contentToString(), ignoreCase = ignoreCase)) return false
@ -65,117 +55,89 @@ internal fun Message.contentEqualsStrictImpl(another: Message, ignoreCase: Boole
} }
} }
@JvmSynthetic
internal fun Message.followedByImpl(tail: Message): MessageChain {
return MessageChainImplBySequence(this.toMessageChain().asSequence() + tail.toMessageChain().asSequence())
/*
when {
this is SingleMessage && tail is SingleMessage -> {
if (this is ConstrainSingle && tail is ConstrainSingle) {
if (this.key == tail.key)
return SingleMessageChainImpl(tail)
}
return CombinedMessage(this, tail)
}
this is SingleMessage -> { // tail is not internal sealed interface MessageChainImpl : MessageChain {
tail as MessageChain /**
* 去重算法 v1 - 2.12:
if (this is ConstrainSingle) { * 在连接时若只有 0-1 方包含 [ConstrainSingle], 则使用 [CombinedMessage] 优化性能. 否则使用旧版复杂去重算法构造 [LinearMessageChainImpl].
val key = this.key */
if (tail.any { (it as? ConstrainSingle)?.key == key }) { @MiraiInternalApi
return tail val hasConstrainSingle: Boolean
}
}
return CombinedMessage(this, tail)
}
tail is SingleMessage -> {
this as MessageChain
if (tail is ConstrainSingle && this.hasDuplicationOfConstrain(tail.key)) {
return MessageChainImplByCollection(constrainSingleMessagesImpl(this.asSequence() + tail))
}
return CombinedMessage(this, tail)
}
else -> { // both chain
this as MessageChain
tail as MessageChain
return MessageChainImplByCollection(
constrainSingleMessagesImpl(this.asSequence() + tail)
)
}
}*/
} }
internal val Message.hasConstrainSingle: Boolean
@JvmSynthetic get() {
internal fun Sequence<SingleMessage>.constrainSingleMessages(): List<SingleMessage> = if (this is SingleMessage) return this is ConstrainSingle
constrainSingleMessagesImpl(this) // now `this` is MessageChain
return this.castOrNull<MessageChainImpl>()?.hasConstrainSingle ?: true // for external type, assume they do
/** }
* - [Sequence.toMutableList]
* - Replace in-place with marker null /**
*/ * @see ConstrainSingleHelper.constrainSingleMessages
@MiraiExperimentalApi */
@JvmSynthetic internal data class ConstrainSingleData(
internal fun constrainSingleMessagesImpl(sequence: Sequence<SingleMessage>): List<SingleMessage> { val value: List<SingleMessage>,
val list: MutableList<SingleMessage?> = sequence.toMutableList() val hasConstrainSingle: Boolean,
)
for (singleMessage in list.asReversed()) {
if (singleMessage is ConstrainSingle) { internal object ConstrainSingleHelper {
val key = singleMessage.key.topmostKey @JvmName("constrainSingleMessages_Sequence")
val firstOccurrence = list.first { it != null && key.isInstance(it) } // may be singleMessage itself internal fun constrainSingleMessages(sequence: Sequence<Message>): ConstrainSingleData =
list.replaceAllKotlin { constrainSingleMessages(sequence.flatMap { it.toMessageChain() })
when {
it == null -> null internal fun constrainSingleMessages(sequence: Sequence<SingleMessage>): ConstrainSingleData =
it === firstOccurrence -> singleMessage constrainSingleMessagesImpl(sequence)
key.isInstance(it) -> null // remove duplicates
else -> it /**
} * - [Sequence.toMutableList]
} * - Replace in-place with marker null
} */
private fun constrainSingleMessagesImpl(sequence: Sequence<SingleMessage>): ConstrainSingleData {
val list: MutableList<SingleMessage?> = sequence.toMutableList()
var hasConstrainSingle = false
for (singleMessage in list.asReversed()) {
if (singleMessage is ConstrainSingle) {
hasConstrainSingle = true
val key = singleMessage.key.topmostKey
val firstOccurrence = list.first { it != null && key.isInstance(it) } // may be singleMessage itself
list.replaceAllKotlin {
when {
it == null -> null
it === firstOccurrence -> singleMessage
key.isInstance(it) -> null // remove duplicates
else -> it
}
}
}
}
return ConstrainSingleData(list.filterNotNull().asImmutable(), hasConstrainSingle)
} }
return list.filterNotNull().asImmutable()
} }
@JvmSynthetic
internal fun Iterable<SingleMessage>.constrainSingleMessages(): List<SingleMessage> =
constrainSingleMessagesImpl(this.asSequence())
@JvmName("constrainSingleMessages_Sequence")
@JvmSynthetic
internal fun Sequence<Message>.constrainSingleMessages(): List<SingleMessage> =
this.flatMap { it.toMessageChain() }.constrainSingleMessages()
@JvmSynthetic @JvmSynthetic
@Suppress("UNCHECKED_CAST", "DEPRECATION_ERROR", "DEPRECATION") @Suppress("UNCHECKED_CAST", "DEPRECATION_ERROR", "DEPRECATION")
internal fun <M : SingleMessage> MessageChain.getImpl(key: MessageKey<M>): M? { internal fun <M : SingleMessage> MessageChain.getImpl(key: MessageKey<M>): M? {
return this.asSequence().mapNotNull { key.safeCast.invoke(it) }.firstOrNull() return this.asSequence().mapNotNull { key.safeCast.invoke(it) }.firstOrNull()
} }
/** @RequiresOptIn
* @return [EmptyMessageChain] if [delegate] is empty, otherwise [MessageChainImpl] internal annotation class MessageChainConstructor
*/
internal fun createMessageChainImplOptimized(delegate: List<SingleMessage>): MessageChain {
return if (delegate.isEmpty()) emptyMessageChain()
else MessageChainImpl(delegate)
}
/** /**
* 使用 [Collection] 作为委托的 [MessageChain] * 使用 [Collection] 作为委托的 [MessageChain]
*/ */
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(MessageChain.Serializer::class) @Serializable(MessageChain.Serializer::class)
internal data class MessageChainImpl constructor( internal class LinearMessageChainImpl @MessageChainConstructor private constructor(
@JvmField @JvmField
internal val delegate: List<SingleMessage> // 必须 constrainSingleMessages, 且为 immutable internal val delegate: List<SingleMessage>,
) : Message, MessageChain, List<SingleMessage> by delegate { override val hasConstrainSingle: Boolean
) : Message, MessageChain, List<SingleMessage> by delegate, MessageChainImpl,
DirectSizeAccess, DirectToStringAccess {
override val size: Int get() = delegate.size override val size: Int get() = delegate.size
override fun iterator(): Iterator<SingleMessage> = delegate.iterator() override fun iterator(): Iterator<SingleMessage> = delegate.iterator()
@ -186,13 +148,81 @@ internal data class MessageChainImpl constructor(
override fun contentToString(): String = contentToStringTemp override fun contentToString(): String = contentToStringTemp
override fun hashCode(): Int = delegate.hashCode() override fun hashCode(): Int = delegate.hashCode()
override fun equals(other: Any?): Boolean = other is MessageChainImpl && other.delegate == this.delegate override fun equals(other: Any?): Boolean = other is LinearMessageChainImpl && other.delegate == this.delegate
}
@Suppress("FunctionName") // source compatibility with 1.x override fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) {
internal fun MessageChainImplBySequence( for (singleMessage in delegate) {
delegate: Sequence<SingleMessage> // 可以有重复 ConstrainSingle singleMessage.accept(visitor, data)
): MessageChain = createMessageChainImplOptimized(delegate.constrainSingleMessages()) }
}
companion object {
fun combineCreate(message: Message, tail: Message): MessageChain {
return create(
ConstrainSingleHelper.constrainSingleMessages(
message.toMessageChain().asSequence() + tail.toMessageChain().asSequence()
)
)
/*
when {
this is SingleMessage && tail is SingleMessage -> {
if (this is ConstrainSingle && tail is ConstrainSingle) {
if (this.key == tail.key)
return SingleMessageChainImpl(tail)
}
return CombinedMessage(this, tail)
}
this is SingleMessage -> { // tail is not
tail as MessageChain
if (this is ConstrainSingle) {
val key = this.key
if (tail.any { (it as? ConstrainSingle)?.key == key }) {
return tail
}
}
return CombinedMessage(this, tail)
}
tail is SingleMessage -> {
this as MessageChain
if (tail is ConstrainSingle && this.hasDuplicationOfConstrain(tail.key)) {
return MessageChainImplByCollection(constrainSingleMessagesImpl(this.asSequence() + tail))
}
return CombinedMessage(this, tail)
}
else -> { // both chain
this as MessageChain
tail as MessageChain
return MessageChainImplByCollection(
constrainSingleMessagesImpl(this.asSequence() + tail)
)
}
}*/
}
/**
* @param delegate must be immutable
*/
@OptIn(MessageChainConstructor::class)
fun create(delegate: List<SingleMessage>, hasConstrainSingle: Boolean): MessageChain {
return if (delegate.isEmpty()) {
emptyMessageChain()
} else {
LinearMessageChainImpl(delegate, hasConstrainSingle)
}
}
fun create(data: ConstrainSingleData): MessageChain {
return create(data.value, data.hasConstrainSingle)
}
}
}
////////////////////// //////////////////////

View File

@ -11,158 +11,232 @@ package net.mamoe.mirai.message.data.visitor
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.NotStableForInheritance
/** /**
* @suppress 这是内部 API, 请不要调用 * @suppress 这是内部 API, 请不要调用
* @since MESSAGE_VISITOR * @since 2.12
*/ */
@MiraiInternalApi @MiraiInternalApi
@NotStableForInheritance
public interface MessageVisitor<in D, out R> { public interface MessageVisitor<in D, out R> {
public fun visitMessage(message: Message, data: D): R public fun visitMessage(message: Message, data: D): R
public fun visitSingleMessage(message: SingleMessage, data: D): R
public fun visitMessageChain(messageChain: MessageChain, data: D): R
public fun visitCombinedMessage(message: CombinedMessage, data: D): R
public fun visitSingleMessage(message: SingleMessage, data: D): R { public fun visitMessageContent(message: MessageContent, data: D): R
public fun visitMessageMetadata(message: MessageMetadata, data: D): R
public fun visitMessageOrigin(message: MessageOrigin, data: D): R
public fun visitMessageSource(message: MessageSource, data: D): R
public fun visitQuoteReply(message: QuoteReply, data: D): R
public fun visitCustomMessageMetadata(message: CustomMessageMetadata, data: D): R
public fun visitShowImageFlag(message: ShowImageFlag, data: D): R
public fun visitPlainText(message: PlainText, data: D): R
public fun visitAt(message: At, data: D): R
public fun visitAtAll(message: AtAll, data: D): R
@Suppress("DEPRECATION_ERROR")
public fun visitVoice(message: net.mamoe.mirai.message.data.Voice, data: D): R
public fun visitAudio(message: Audio, data: D): R
public fun visitHummerMessage(message: HummerMessage, data: D): R
public fun visitFlashImage(message: FlashImage, data: D): R
public fun visitPokeMessage(message: PokeMessage, data: D): R
public fun visitVipFace(message: VipFace, data: D): R
public fun visitMarketFace(message: MarketFace, data: D): R
public fun visitDice(message: Dice, data: D): R
public fun visitFace(message: Face, data: D): R
public fun visitFileMessage(message: FileMessage, data: D): R
public fun visitImage(message: Image, data: D): R
public fun visitForwardMessage(message: ForwardMessage, data: D): R
public fun visitMusicShare(message: MusicShare, data: D): R
public fun visitRichMessage(message: RichMessage, data: D): R
public fun visitServiceMessage(message: ServiceMessage, data: D): R
public fun visitSimpleServiceMessage(message: SimpleServiceMessage, data: D): R
public fun visitLightApp(message: LightApp, data: D): R
public fun visitAbstractServiceMessage(message: AbstractServiceMessage, data: D): R
public fun visitUnsupportedMessage(message: UnsupportedMessage, data: D): R
}
/**
* @suppress 这是内部 API, 请不要调用
* @since 2.12
*/
@MiraiInternalApi
public abstract class AbstractMessageVisitor<in D, out R> : MessageVisitor<D, R> {
public override fun visitSingleMessage(message: SingleMessage, data: D): R {
return visitMessage(message, data) return visitMessage(message, data)
} }
public fun visitMessageChain(messageChain: MessageChain, data: D): R { public override fun visitMessageChain(messageChain: MessageChain, data: D): R {
return visitMessage(messageChain, data) return visitMessage(messageChain, data)
} }
public override fun visitCombinedMessage(message: CombinedMessage, data: D): R {
return visitMessageChain(message, data)
}
public fun visitMessageContent(message: MessageContent, data: D): R {
public override fun visitMessageContent(message: MessageContent, data: D): R {
return visitSingleMessage(message, data) return visitSingleMessage(message, data)
} }
public fun visitMessageMetadata(message: MessageMetadata, data: D): R { public override fun visitMessageMetadata(message: MessageMetadata, data: D): R {
return visitSingleMessage(message, data) return visitSingleMessage(message, data)
} }
public fun visitMessageOrigin(message: MessageOrigin, data: D): R { public override fun visitMessageOrigin(message: MessageOrigin, data: D): R {
return visitMessageMetadata(message, data) return visitMessageMetadata(message, data)
} }
public fun visitMessageSource(message: MessageSource, data: D): R { public override fun visitMessageSource(message: MessageSource, data: D): R {
return visitMessageMetadata(message, data) return visitMessageMetadata(message, data)
} }
public fun visitQuoteReply(message: QuoteReply, data: D): R { public override fun visitQuoteReply(message: QuoteReply, data: D): R {
return visitMessageMetadata(message, data) return visitMessageMetadata(message, data)
} }
public fun visitCustomMessageMetadata(message: CustomMessageMetadata, data: D): R { public override fun visitCustomMessageMetadata(message: CustomMessageMetadata, data: D): R {
return visitMessageMetadata(message, data) return visitMessageMetadata(message, data)
} }
public fun visitShowImageFlag(message: ShowImageFlag, data: D): R { public override fun visitShowImageFlag(message: ShowImageFlag, data: D): R {
return visitMessageMetadata(message, data) return visitMessageMetadata(message, data)
} }
public fun visitPlainText(message: PlainText, data: D): R { public override fun visitPlainText(message: PlainText, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitAt(message: At, data: D): R { public override fun visitAt(message: At, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitAtAll(message: AtAll, data: D): R { public override fun visitAtAll(message: AtAll, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
public fun visitVoice(message: Voice, data: D): R { public override fun visitVoice(message: net.mamoe.mirai.message.data.Voice, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitAudio(message: Audio, data: D): R { public override fun visitAudio(message: Audio, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitHummerMessage(message: HummerMessage, data: D): R { public override fun visitHummerMessage(message: HummerMessage, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitFlashImage(message: FlashImage, data: D): R { public override fun visitFlashImage(message: FlashImage, data: D): R {
return visitHummerMessage(message, data) return visitHummerMessage(message, data)
} }
public fun visitPokeMessage(message: PokeMessage, data: D): R { public override fun visitPokeMessage(message: PokeMessage, data: D): R {
return visitHummerMessage(message, data) return visitHummerMessage(message, data)
} }
public fun visitVipFace(message: VipFace, data: D): R { public override fun visitVipFace(message: VipFace, data: D): R {
return visitHummerMessage(message, data) return visitHummerMessage(message, data)
} }
public fun visitMarketFace(message: MarketFace, data: D): R { public override fun visitMarketFace(message: MarketFace, data: D): R {
return visitHummerMessage(message, data) return visitHummerMessage(message, data)
} }
public fun visitDice(message: Dice, data: D): R { public override fun visitDice(message: Dice, data: D): R {
return visitMarketFace(message, data) return visitMarketFace(message, data)
} }
public fun visitFace(message: Face, data: D): R { public override fun visitFace(message: Face, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitFileMessage(message: FileMessage, data: D): R { public override fun visitFileMessage(message: FileMessage, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitImage(message: Image, data: D): R { public override fun visitImage(message: Image, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitForwardMessage(message: ForwardMessage, data: D): R { public override fun visitForwardMessage(message: ForwardMessage, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitMusicShare(message: MusicShare, data: D): R { public override fun visitMusicShare(message: MusicShare, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitUnsupportedMessage(message: UnsupportedMessage, data: D): R { public override fun visitUnsupportedMessage(message: UnsupportedMessage, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitRichMessage(message: RichMessage, data: D): R { public override fun visitRichMessage(message: RichMessage, data: D): R {
return visitMessageContent(message, data) return visitMessageContent(message, data)
} }
public fun visitServiceMessage(message: ServiceMessage, data: D): R { public override fun visitServiceMessage(message: ServiceMessage, data: D): R {
return visitRichMessage(message, data) return visitRichMessage(message, data)
} }
public fun visitSimpleServiceMessage(message: SimpleServiceMessage, data: D): R { public override fun visitSimpleServiceMessage(message: SimpleServiceMessage, data: D): R {
return visitServiceMessage(message, data) return visitServiceMessage(message, data)
} }
public fun visitLightApp(message: LightApp, data: D): R { public override fun visitLightApp(message: LightApp, data: D): R {
return visitRichMessage(message, data) return visitRichMessage(message, data)
} }
public fun visitAbstractServiceMessage(message: AbstractServiceMessage, data: D): R { public override fun visitAbstractServiceMessage(message: AbstractServiceMessage, data: D): R {
return visitServiceMessage(message, data) return visitServiceMessage(message, data)
} }
} }
/** /**
* @suppress 这是内部 API, 请不要调用 * @suppress 这是内部 API, 请不要调用
* @since 2.11 * @since 2.12
*/ */
@MiraiInternalApi @MiraiInternalApi
public interface MessageVisitorUnit<in D> : MessageVisitor<D, Unit> { public abstract class RecursiveMessageVisitor<D> : MessageVisitorUnit<D>() {
protected open fun isFinished(): Boolean = false
override fun visitMessage(message: Message, data: D) {
if (isFinished()) return
message.acceptChildren(this, data)
}
}
/**
* @suppress 这是内部 API, 请不要调用
* @since 2.12
*/
@MiraiInternalApi
public abstract class MessageVisitorUnit<in D> : AbstractMessageVisitor<D, Unit>() {
override fun visitMessage(message: Message, data: D): Unit = Unit override fun visitMessage(message: Message, data: D): Unit = Unit
} }
/** /**
* @suppress 这是内部 API, 请不要调用 * @suppress 这是内部 API, 请不要调用
* @since 2.11 * @since 2.12
*/ */
@MiraiInternalApi @MiraiInternalApi
public fun <R> Message.accept(visitor: MessageVisitor<Unit, R>): R = this.accept(visitor, Unit) public fun <R> Message.accept(visitor: MessageVisitor<Unit, R>): R = this.accept(visitor, Unit)
/**
* @suppress 这是内部 API, 请不要调用
* @since 2.12
*/
@MiraiInternalApi
public fun Message.acceptChildren(visitor: MessageVisitor<Unit, *>): Unit = this.acceptChildren(visitor, Unit)

View File

@ -1,85 +1,220 @@
/* /*
* Copyright 2019-2021 Mamoe Technologies and contributors. * Copyright 2019-2022 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * 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 * https://github.com/mamoe/mirai/blob/dev/LICENSE
*/ */
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.message.data.visitor.MessageVisitorUnit
import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor
import net.mamoe.mirai.message.data.visitor.accept
import net.mamoe.mirai.message.data.visitor.acceptChildren
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertIs
@OptIn(MessageChainConstructor::class)
internal class CombinedMessageTest { internal class CombinedMessageTest {
private fun linearMessageChainOf(vararg values: SingleMessage) =
LinearMessageChainImpl.create(values.toList(), values.any { it.hasConstrainSingle })
@Test @Test
fun testAsSequence() { fun singlePlusSingleCreatesCombinedMessage() {
var message: Message = PlainText("Hello ") kotlin.run {
message += "World" val chain = PlainText("fo").plus(PlainText("o"))
assertIs<CombinedMessage>(chain)
assertEquals(PlainText("fo"), chain.element)
assertEquals(PlainText("o"), chain.tail)
assertEquals(false, chain.hasConstrainSingle)
}
kotlin.run {
val chain = PlainText("fo").plus(LightApp("c"))
assertIs<LinearMessageChainImpl>(chain)
assertEquals(LightApp("c"), chain.single())
assertEquals(true, chain.hasConstrainSingle)
}
}
@Test
fun singlePlusChainCreatesCombinedMessage() {
val chain = PlainText("fo").plus(linearMessageChainOf(PlainText("o")))
assertIs<CombinedMessage>(chain)
assertEquals(PlainText("fo"), chain.element)
assertEquals(linearMessageChainOf(PlainText("o")), chain.tail)
assertEquals(false, chain.hasConstrainSingle)
}
@Test
fun chainPlusSingleCreatesCombinedMessage() {
val chain = linearMessageChainOf(PlainText("o")).plus(PlainText("fo"))
assertIs<CombinedMessage>(chain)
assertEquals(linearMessageChainOf(PlainText("o")), chain.element)
assertEquals(PlainText("fo"), chain.tail)
assertEquals(false, chain.hasConstrainSingle)
}
private fun createTestInstance(): CombinedMessage {
return CombinedMessage(
buildMessageChain {
+PlainText("bar")
+emptyMessageChain()
+buildMessageChain {
+AtAll
+PlainText("zoo")
}
},
buildMessageChain {
+buildMessageChain {
+At(2)
}
+At(1)
},
false
)
}
private fun createComplexCombined() = CombinedMessage(
PlainText("foo"),
createTestInstance(),
false
)
private val complexCombined = createComplexCombined()
///////////////////////////////////////////////////////////////////////////
// properties
///////////////////////////////////////////////////////////////////////////
@Test
fun sizeTest() {
assertEquals(0, CombinedMessage(messageChainOf(), messageChainOf(), false).size)
assertEquals( assertEquals(
"Hello World", 0,
message.toMessageChain().asSequence().joinToString(separator = "") CombinedMessage(messageChainOf(), CombinedMessage(messageChainOf(), messageChainOf(), false), false).size
)
createTestInstance().run {
assertEquals(5, size)
assertFalse { slowList.isInitialized() }
}
}
@Test
fun slowListTest() {
assertEquals(messageChainOf(), CombinedMessage(messageChainOf(), messageChainOf(), false).slowList.value)
assertEquals(
listOf(
PlainText("foo"),
PlainText("bar"),
AtAll,
PlainText("zoo"),
At(2),
At(1),
),
complexCombined.slowList.value.toList()
) )
} }
@Test @Test
fun testAsSequence2() { fun consistencyTest() {
var message: Message = PlainText("Hello ") val first = createTestInstance()
message += listOf( val second = createTestInstance()
PlainText("W"), assertEquals(first, second)
PlainText("o"), assertEquals(first.hashCode(), second.hashCode())
PlainText("r") + PlainText("ld") assertEquals(first.toString(), second.toString())
).toMessageChain() assertEquals(first.contentToString(), second.contentToString())
assertEquals(first.size, second.size)
}
///////////////////////////////////////////////////////////////////////////
// functions
///////////////////////////////////////////////////////////////////////////
@Test
fun subList() {
assertEquals( assertEquals(
"Hello World", createComplexCombined().slowList.value.subList(2, 4),
message.toMessageChain().asSequence().joinToString(separator = "") complexCombined.subList(2, 4)
)
assertFalse {
complexCombined.slowList.isInitialized()
}
complexCombined.slowList.value // initialize
assertEquals(
createComplexCombined().slowList.value.subList(2, 4),
complexCombined.subList(2, 4)
) )
} }
}
fun <T> Iterator<T>.joinToString( ///////////////////////////////////////////////////////////////////////////
separator: CharSequence = ", ", // visiting
prefix: CharSequence = "", ///////////////////////////////////////////////////////////////////////////
postfix: CharSequence = "",
limit: Int = -1,
truncated: CharSequence = "...",
transform: ((T) -> CharSequence)? = null
): String {
return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}
fun <T, A : Appendable> Iterator<T>.joinTo( @Test
buffer: A, fun acceptChildrenTest() {
separator: CharSequence = ", ", val list = buildList {
prefix: CharSequence = "", complexCombined.acceptChildren(object : MessageVisitorUnit<Unit>() {
postfix: CharSequence = "", override fun visitMessage(message: Message, data: Unit) {
limit: Int = -1, add(message)
truncated: CharSequence = "...", super.visitMessage(message, data)
transform: ((T) -> CharSequence)? = null }
): A { })
buffer.append(prefix) }
var count = 0 assertEquals(
for (element in this) { listOf(
if (++count > 1) buffer.append(separator) PlainText("foo"),
if (limit < 0 || count <= limit) { createTestInstance()
buffer.appendElement(element, transform) ),
} else break list
)
} }
if (limit in 0 until count) buffer.append(truncated)
buffer.append(postfix)
return buffer
}
internal fun <T> Appendable.appendElement(element: T, transform: ((T) -> CharSequence)?) { @OptIn(MessageChainConstructor::class)
when { @Test
transform != null -> append(transform(element)) fun acceptChildrenRecursiveTest() {
element is CharSequence? -> append(element) val list = buildList {
element is Char -> append(element) complexCombined.accept(object : RecursiveMessageVisitor<Unit>() {
else -> append(element.toString()) override fun visitMessage(message: Message, data: Unit) {
println("visitMessage: (${message::class.simpleName}) $message")
super.visitMessage(message, data)
}
override fun visitSingleMessage(message: SingleMessage, data: Unit) {
add(message)
super.visitSingleMessage(message, data)
}
})
}
assertEquals(
listOf(
PlainText("foo"),
PlainText("bar"),
AtAll,
PlainText("zoo"),
At(2),
At(1),
),
list
)
}
@Test
fun hierarchicalIterator() {
assertEquals(
listOf(
PlainText("foo"),
PlainText("bar"),
AtAll,
PlainText("zoo"),
At(2),
At(1),
),
complexCombined.iterator().asSequence().toList()
)
assertFalse { complexCombined.slowList.isInitialized() }
} }
} }

View File

@ -0,0 +1,177 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.safeCast
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
internal class ConstrainSingleHelperTest {
@Test
fun linearPlains() {
val list = listOf(PlainText("1"), PlainText("2"))
ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run {
assertEquals(list, value)
assertFalse { hasConstrainSingle }
}
}
@Test
fun linearNonConstrains() {
val list = listOf(PlainText("1"), At(2))
ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run {
assertEquals(list, value)
assertFalse { hasConstrainSingle }
}
}
@Test
fun hierarchicalNonConstrains() {
val list = listOf(PlainText("1"), (PlainText("1") + At(2)))
ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run {
assertEquals(listOf(PlainText("1"), PlainText("1"), At(2)), value)
assertFalse { hasConstrainSingle }
}
}
@Test
fun singleConstrain() {
val list = listOf(Dice(2))
ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run {
assertEquals(list, value)
assertTrue { hasConstrainSingle }
}
}
@Test
fun linearDuplicatedConstrains() {
val list = listOf(Dice(2), Dice(3))
ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run {
assertEquals(1, value.size)
assertEquals(Dice(3), value.first())
assertTrue { hasConstrainSingle }
}
}
@Test
fun linearMultipleDuplicatedConstrains() {
val list = listOf(PlainText("a"), LightApp("aa"), Dice(2), Dice(3), LightApp("bb"), PlainText("c"))
ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run {
assertEquals(1, value.size)
assertEquals(listOf(LightApp("bb")), value)
assertTrue { hasConstrainSingle }
}
}
@Test
fun hierarchicalDuplicatedConstrains() {
val list = listOf(
MySingle(1), // before first MessageContent that can be replaced by the last ConstrainSingle
PlainText("a"),
LightApp("aa"),
LinearMessageChainImpl.create( // without processing ConstrainSingle
listOf(
PlainText("ab"),
LightApp("aab"),
Dice(5),
Dice(3),
LightApp("bbb"),
PlainText("cb")
), true
),
Dice(3),
LightApp("bb"), // last ConstrainSingle
PlainText("c")
)
ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run {
assertEquals(2, value.size)
assertEquals(listOf(MySingle(1), LightApp("bb")), value)
assertTrue { hasConstrainSingle }
}
}
@Test
fun hierarchicalDuplicatedConstrains3() {
val list = listOf(
PlainText("a"),
LightApp("aa"),
LinearMessageChainImpl.create( // without processing ConstrainSingle
listOf(
PlainText("ab"),
LightApp("aab"),
Dice(5),
Dice(3),
LightApp("bbb"),
PlainText("cb")
), true
),
Dice(3),
MySingle(1), // after first MessageContent that can be replaced by the last ConstrainSingle
LightApp("bb"), // last ConstrainSingle
PlainText("c")
)
ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run {
assertEquals(2, value.size)
assertEquals(listOf(LightApp("bb"), MySingle(1)), value)
assertTrue { hasConstrainSingle }
}
}
@Test
fun hierarchicalDuplicatedConstrains2() {
val list = listOf(
PlainText("a"),
LightApp("aa"),
Dice(3),
LightApp("bb"),
PlainText("c"),
LinearMessageChainImpl.create( // without processing ConstrainSingle
listOf(
PlainText("a"),
LightApp("aa"),
Dice(2),
MySingle(1), // after first MessageContent that can be replaced by the last ConstrainSingle
Dice(3),
LightApp("foo"), // last ConstrainSingle
PlainText("c")
), true
),
)
ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run {
assertEquals(2, value.size)
assertEquals(listOf(LightApp("foo"), MySingle(1)), value)
assertTrue { hasConstrainSingle }
}
}
}
private class MySingle(
val value: Int
) : MessageMetadata, ConstrainSingle {
override val key: MessageKey<MySingle>
get() = Key
override fun toString(): String {
return "MySingle@${this.hashCode()}"
}
override fun equals(other: Any?): Boolean {
return other is MySingle && other.value == this.value
}
override fun hashCode(): Int {
return value
}
private object Key : AbstractMessageKey<MySingle>({ it.safeCast() })
}

View File

@ -1,10 +1,10 @@
/* /*
* Copyright 2019-2021 Mamoe Technologies and contributors. * Copyright 2019-2022 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * 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 * https://github.com/mamoe/mirai/blob/dev/LICENSE
*/ */
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
@ -72,7 +72,7 @@ internal class ConstrainSingleTest {
assertEquals(7, result.size) assertEquals(7, result.size)
assertEquals(" [OK]ss p test", result.contentToString()) assertEquals(" [OK]ss p test", result.contentToString())
result as MessageChainImpl result as LinearMessageChainImpl
assertSame(new, result.delegate.toTypedArray()[2]) assertSame(new, result.delegate.toTypedArray()[2])
} }
@ -85,7 +85,8 @@ internal class ConstrainSingleTest {
last last
) )
val result = sequence.constrainSingleMessages() val (result, has) = ConstrainSingleHelper.constrainSingleMessages(sequence)
assertTrue { has }
assertEquals(result.count(), 1) assertEquals(result.count(), 1)
assertSame(result.single(), last) assertSame(result.single(), last)
} }
@ -100,7 +101,8 @@ internal class ConstrainSingleTest {
last last
) )
val result = sequence.constrainSingleMessages() val (result, has) = ConstrainSingleHelper.constrainSingleMessages(sequence)
assertTrue { has }
assertEquals(result.count(), 2) assertEquals(result.count(), 2)
assertSame(result.first(), last) assertSame(result.first(), last)
} }

View File

@ -0,0 +1,87 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.message.data
import net.mamoe.mirai.message.data.visitor.MessageVisitorUnit
import net.mamoe.mirai.message.data.visitor.acceptChildren
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
internal class LinearMessageChainImplTest {
private val complexLinearChain = buildMessageChain {
+PlainText("foo")
+buildMessageChain {
+PlainText("bar")
+emptyMessageChain()
+buildMessageChain {
+AtAll
+PlainText("zoo")
}
}
+buildMessageChain {
+buildMessageChain {
+At(2)
}
+At(1)
}
}
@Test
fun buildMessageChainCreatesLinearMessageChainImpl() {
assertIs<LinearMessageChainImpl>(complexLinearChain)
}
@Test
fun hierarchicalIterator() {
assertEquals(
listOf(
PlainText("foo"),
PlainText("bar"),
AtAll,
PlainText("zoo"),
At(2),
At(1),
),
complexLinearChain.iterator().asSequence().toList()
)
}
@Test
fun sizeTest() {
assertEquals(0, messageChainOf().size)
assertEquals(6, complexLinearChain.size)
assertEquals(6, complexLinearChain.count())
}
@Test
fun acceptChildrenTest() {
val list = buildList {
complexLinearChain.acceptChildren(object : MessageVisitorUnit<Unit>() {
override fun visitMessage(message: Message, data: Unit) {
add(message)
super.visitMessage(message, data)
}
})
}
assertEquals(
listOf(
PlainText("foo"),
PlainText("bar"),
AtAll,
PlainText("zoo"),
At(2),
At(1),
),
list
)
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.message.data
import org.junit.jupiter.api.Test
import kotlin.test.assertIs
internal class MessageChainImplTest {
@OptIn(MessageChainConstructor::class)
@Test
fun allInternalImplementationsOfMessageChainAreMessageChainImpl() {
assertIs<MessageChainImpl>(CombinedMessage(AtAll, AtAll, false))
assertIs<MessageChainImpl>(emptyMessageChain())
val linear = LinearMessageChainImpl.create(listOf(AtAll), true)
assertIs<LinearMessageChainImpl>(linear)
assertIs<MessageChainImpl>(linear)
}
}

View File

@ -0,0 +1,461 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.FileSupported
import net.mamoe.mirai.message.data.visitor.AbstractMessageVisitor
import net.mamoe.mirai.message.data.visitor.accept
import net.mamoe.mirai.utils.MiraiExperimentalApi
import org.junit.jupiter.api.Test
import kotlin.test.assertContentEquals
internal class MessageVisitorTest {
object GetCalledMethodNames : AbstractMessageVisitor<Unit, Array<String>>() {
override fun visitMessage(message: Message, data: Unit): Array<String> {
return arrayOf("visitMessage")
}
override fun visitSingleMessage(message: SingleMessage, data: Unit): Array<String> {
return arrayOf("visitSingleMessage") + super.visitSingleMessage(message, data)
}
override fun visitMessageChain(messageChain: MessageChain, data: Unit): Array<String> {
return arrayOf("visitMessageChain") + super.visitMessageChain(messageChain, data)
}
override fun visitCombinedMessage(message: CombinedMessage, data: Unit): Array<String> {
return arrayOf("visitCombinedMessage") + super.visitCombinedMessage(message, data)
}
override fun visitMessageContent(message: MessageContent, data: Unit): Array<String> {
return arrayOf("visitMessageContent") + super.visitMessageContent(message, data)
}
override fun visitMessageMetadata(message: MessageMetadata, data: Unit): Array<String> {
return arrayOf("visitMessageMetadata") + super.visitMessageMetadata(message, data)
}
override fun visitMessageOrigin(message: MessageOrigin, data: Unit): Array<String> {
return arrayOf("visitMessageOrigin") + super.visitMessageOrigin(message, data)
}
override fun visitMessageSource(message: MessageSource, data: Unit): Array<String> {
return arrayOf("visitMessageSource") + super.visitMessageSource(message, data)
}
override fun visitQuoteReply(message: QuoteReply, data: Unit): Array<String> {
return arrayOf("visitQuoteReply") + super.visitQuoteReply(message, data)
}
override fun visitCustomMessageMetadata(message: CustomMessageMetadata, data: Unit): Array<String> {
return arrayOf("visitCustomMessageMetadata") + super.visitCustomMessageMetadata(message, data)
}
override fun visitShowImageFlag(message: ShowImageFlag, data: Unit): Array<String> {
return arrayOf("visitShowImageFlag") + super.visitShowImageFlag(message, data)
}
override fun visitPlainText(message: PlainText, data: Unit): Array<String> {
return arrayOf("visitPlainText") + super.visitPlainText(message, data)
}
override fun visitAt(message: At, data: Unit): Array<String> {
return arrayOf("visitAt") + super.visitAt(message, data)
}
override fun visitAtAll(message: AtAll, data: Unit): Array<String> {
return arrayOf("visitAtAll") + super.visitAtAll(message, data)
}
@Suppress("DEPRECATION_ERROR")
override fun visitVoice(message: Voice, data: Unit): Array<String> {
return arrayOf("visitVoice") + super.visitVoice(message, data)
}
override fun visitAudio(message: Audio, data: Unit): Array<String> {
return arrayOf("visitAudio") + super.visitAudio(message, data)
}
override fun visitHummerMessage(message: HummerMessage, data: Unit): Array<String> {
return arrayOf("visitHummerMessage") + super.visitHummerMessage(message, data)
}
override fun visitFlashImage(message: FlashImage, data: Unit): Array<String> {
return arrayOf("visitFlashImage") + super.visitFlashImage(message, data)
}
override fun visitPokeMessage(message: PokeMessage, data: Unit): Array<String> {
return arrayOf("visitPokeMessage") + super.visitPokeMessage(message, data)
}
override fun visitVipFace(message: VipFace, data: Unit): Array<String> {
return arrayOf("visitVipFace") + super.visitVipFace(message, data)
}
override fun visitMarketFace(message: MarketFace, data: Unit): Array<String> {
return arrayOf("visitMarketFace") + super.visitMarketFace(message, data)
}
override fun visitDice(message: Dice, data: Unit): Array<String> {
return arrayOf("visitDice") + super.visitDice(message, data)
}
override fun visitFace(message: Face, data: Unit): Array<String> {
return arrayOf("visitFace") + super.visitFace(message, data)
}
override fun visitFileMessage(message: FileMessage, data: Unit): Array<String> {
return arrayOf("visitFileMessage") + super.visitFileMessage(message, data)
}
override fun visitImage(message: Image, data: Unit): Array<String> {
return arrayOf("visitImage") + super.visitImage(message, data)
}
override fun visitForwardMessage(message: ForwardMessage, data: Unit): Array<String> {
return arrayOf("visitForwardMessage") + super.visitForwardMessage(message, data)
}
override fun visitMusicShare(message: MusicShare, data: Unit): Array<String> {
return arrayOf("visitMusicShare") + super.visitMusicShare(message, data)
}
override fun visitUnsupportedMessage(message: UnsupportedMessage, data: Unit): Array<String> {
return arrayOf("visitUnsupportedMessage") + super.visitUnsupportedMessage(message, data)
}
override fun visitRichMessage(message: RichMessage, data: Unit): Array<String> {
return arrayOf("visitRichMessage") + super.visitRichMessage(message, data)
}
override fun visitServiceMessage(message: ServiceMessage, data: Unit): Array<String> {
return arrayOf("visitServiceMessage") + super.visitServiceMessage(message, data)
}
override fun visitSimpleServiceMessage(message: SimpleServiceMessage, data: Unit): Array<String> {
return arrayOf("visitSimpleServiceMessage") + super.visitSimpleServiceMessage(message, data)
}
override fun visitLightApp(message: LightApp, data: Unit): Array<String> {
return arrayOf("visitLightApp") + super.visitLightApp(message, data)
}
override fun visitAbstractServiceMessage(message: AbstractServiceMessage, data: Unit): Array<String> {
return arrayOf("visitAbstractServiceMessage") + super.visitAbstractServiceMessage(message, data)
}
}
@OptIn(MessageChainConstructor::class)
@Test
fun visitMessageChain() {
assertContentEquals(
arrayOf(
"visitMessageChain",
"visitMessage",
),
messageChainOf(PlainText("1")).accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitMessageChain",
"visitMessage",
),
emptyMessageChain().accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitCombinedMessage",
"visitMessageChain",
"visitMessage",
),
CombinedMessage(AtAll, AtAll, false).accept(GetCalledMethodNames)
)
}
@Test
fun visitSingleMessageContent() {
assertContentEquals(
arrayOf(
"visitMessage",
),
object : Message {
@Suppress("RedundantOverride") // false positive
override fun toString(): String = super.toString()
override fun contentToString(): String = super.toString()
}.accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitSingleMessage",
"visitMessage",
),
object : SingleMessage {
@Suppress("RedundantOverride") // false positive
override fun toString(): String = super.toString()
override fun contentToString(): String = super.toString()
}.accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
object : MessageContent {
@Suppress("RedundantOverride") // false positive
override fun toString(): String = super.toString()
override fun contentToString(): String = super.toString()
}.accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitPlainText",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
PlainText("1").accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitAt",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
At(1).accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitAtAll",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
AtAll.accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitVoice",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
@Suppress("DEPRECATION_ERROR")
Voice("", byteArrayOf(), 1, 1, "").accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitAudio",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
object : OfflineAudio {
override val filename: String get() = ""
override val fileMd5: ByteArray get() = byteArrayOf()
override val fileSize: Long get() = 1
override val codec: AudioCodec get() = AudioCodec.AMR
override val extraData: ByteArray? get() = null
override fun toString(): String = "test"
}.accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitFlashImage",
"visitHummerMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
FlashImage(createImage()).accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitPokeMessage",
"visitHummerMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
PokeMessage.BiXin.accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitVipFace",
"visitHummerMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
VipFace(VipFace.AiXin, 1).accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitMarketFace",
"visitHummerMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
object : MarketFace {
override val name: String get() = "f"
override val id: Int get() = 1
override fun toString(): String = "ok"
}.accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitDice",
"visitMarketFace",
"visitHummerMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
Dice(1).accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitFace",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
Face(Face.AI_NI).accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitFileMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
object : FileMessage {
override val id: String get() = ""
override val internalId: Int get() = 1
override val name: String get() = ""
override val size: Long get() = 1
override suspend fun toAbsoluteFile(contact: FileSupported): Nothing =
throw UnsupportedOperationException()
override fun toString(): String = ""
}.accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitImage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
createImage().accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitForwardMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
ForwardMessage(listOf(), "", "", "", "", listOf()).accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitMusicShare",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
MusicShare(
kind = MusicKind.NeteaseCloudMusic,
title = "ファッション",
summary = "rinahamu/Yunomi",
brief = "",
jumpUrl = "https://music.163.com/song/1338728297/?userid=324076307",
pictureUrl = "https://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg",
musicUrl = "https://music.163.com/song/media/outer/url?id=1338728297&userid=324076307"
).accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitUnsupportedMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
object : UnsupportedMessage {
override val struct: ByteArray get() = byteArrayOf()
override fun toString(): String = "test"
}.accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitSimpleServiceMessage",
"visitServiceMessage",
"visitRichMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
SimpleServiceMessage(1, "str").accept(GetCalledMethodNames)
)
assertContentEquals(
arrayOf(
"visitLightApp",
"visitRichMessage",
"visitMessageContent",
"visitSingleMessage",
"visitMessage",
),
LightApp("str").accept(GetCalledMethodNames)
)
}
}
private fun createImage(): Image {
return object : Image {
override val imageId: String get() = "{88914B32-B758-74ED-B00D-CAA6D2A5D7F6}.jpg"
override val width: Int get() = 1
override val height: Int get() = 1
override val size: Long get() = 1
override val imageType: ImageType get() = ImageType.APNG
override fun toString(): String = "test"
override fun contentToString(): String = "test"
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
}
}
}

View File

@ -319,9 +319,7 @@ internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessage
) )
forward.nodeList.forEach { tmp.addAll(it.messageChain) } forward.nodeList.forEach { tmp.addAll(it.messageChain) }
// toMessageChain will lose some element tmp.verifyLength(forward, contact)
@Suppress("INVISIBLE_MEMBER")
createMessageChainImplOptimized(tmp).verifyLength(forward, contact)
} }
val resId = getMiraiImpl().uploadMessageHighway( val resId = getMiraiImpl().uploadMessageHighway(

View File

@ -30,7 +30,7 @@ internal fun Contact.logMessageSent(message: Message) {
} }
} }
internal fun MessageChain.countImages(): Int = this.count { it is Image } internal fun Iterable<SingleMessage>.countImages(): Int = this.count { it is Image }
private val logger by lazy { MiraiLogger.Factory.create(SendMessageHandler::class) } private val logger by lazy { MiraiLogger.Factory.create(SendMessageHandler::class) }
@ -55,14 +55,14 @@ internal fun Message.verifySendingValid() {
} }
} }
internal fun MessageChain.verifyLength( internal fun Iterable<SingleMessage>.verifyLength(
originalMessage: Message, target: Contact, originalMessage: Message, target: Contact,
): Int { ): Int {
val chain = this val chain = this
val length = estimateLength(target, 15001) val length = estimateLength(target, 15001)
if (length > 15000 || countImages() > 50) { if (length > 15000 || countImages() > 50) {
throw MessageTooLargeException( throw MessageTooLargeException(
target, originalMessage, this, target, originalMessage, this.toMessageChain(),
"message(${ "message(${
chain.joinToString("", limit = 10).let { rsp -> chain.joinToString("", limit = 10).let { rsp ->
if (rsp.length > 100) { if (rsp.length > 100) {

View File

@ -156,8 +156,9 @@ internal object LightMessageRefiner : MessageRefiner() {
val source = this.sourceOrNull?.safeCast<IncomingMessageSourceInternal>() ?: return this val source = this.sourceOrNull?.safeCast<IncomingMessageSourceInternal>() ?: return this
val originalMessage = this val originalMessage = this
source.originalMessageLazy = lazy { source.originalMessageLazy = lazy {
@Suppress("INVISIBLE_MEMBER") originalMessage.filterNot { it is MessageSource }.toMessageChain()
createMessageChainImplOptimized(originalMessage.filterNot { it is MessageSource }) // @Suppress("INVISIBLE_MEMBER")
// createMessageChainImplOptimized(originalMessage.filterNot { it is MessageSource })
} }
return this return this
} }

View File

@ -44,7 +44,7 @@ internal fun String.toIpV4Long(): Long {
} }
} }
internal fun MessageChain.estimateLength(target: ContactOrBot, upTo: Int): Int = internal fun Iterable<SingleMessage>.estimateLength(target: ContactOrBot, upTo: Int): Int =
sumUpTo(upTo) { it, up -> sumUpTo(upTo) { it, up ->
it.estimateLength(target, up) it.estimateLength(target, up)
} }

View File

@ -151,8 +151,7 @@ internal class CleanupRubbishMessageElementsTest {
} }
private fun noMessageSource(c: MessageChain): MessageChain { private fun noMessageSource(c: MessageChain): MessageChain {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return c.filterNot { it is MessageSource }.toMessageChain()
return createMessageChainImplOptimized(c.filterNot { it is MessageSource })
} }
//endregion //endregion