mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-03 15:52:54 +08:00
Redesign MessageChain hierarchy: Add LinearMessageChainImpl and CombinedMessage
This commit is contained in:
parent
b40b681f81
commit
0c708c8197
@ -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 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 synthetic fun add (ILjava/lang/Object;)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 synthetic fun get (I)Ljava/lang/Object;
|
||||
public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage;
|
||||
public fun getHasConstrainSingle ()Z
|
||||
public fun getSize ()I
|
||||
public fun hashCode ()I
|
||||
public final fun indexOf (Ljava/lang/Object;)I
|
||||
|
@ -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 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 synthetic fun add (ILjava/lang/Object;)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 synthetic fun get (I)Ljava/lang/Object;
|
||||
public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage;
|
||||
public fun getHasConstrainSingle ()Z
|
||||
public fun getSize ()I
|
||||
public fun hashCode ()I
|
||||
public final fun indexOf (Ljava/lang/Object;)I
|
||||
|
@ -92,7 +92,7 @@ private val builtInSerializersModule by lazy {
|
||||
|
||||
// contextual(SingleMessage::class, SingleMessage.Serializer)
|
||||
contextual(MessageChain::class, MessageChain.Serializer)
|
||||
contextual(MessageChainImpl::class, MessageChainImpl.serializer())
|
||||
contextual(LinearMessageChainImpl::class, LinearMessageChainImpl.serializer())
|
||||
|
||||
contextual(ShowImageFlag::class, ShowImageFlag.Serializer)
|
||||
|
||||
|
@ -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
|
@ -240,7 +240,20 @@ public interface Message {
|
||||
* @see plus `+` 操作符重载
|
||||
*/
|
||||
@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] 按顺序连接到这个消息的尾部. */
|
||||
public operator fun plus(another: MessageChain): MessageChain = this + another as Message
|
||||
@ -277,14 +290,14 @@ public interface Message {
|
||||
|
||||
/**
|
||||
* @suppress 这是内部 API, 不要在任何情况下调用
|
||||
* @since MESSAGE_VISITOR
|
||||
* @since 2.12
|
||||
*/
|
||||
@MiraiInternalApi
|
||||
public fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R = visitor.visitMessage(this, data)
|
||||
|
||||
/**
|
||||
* @suppress 这是内部 API, 不要在任何情况下调用
|
||||
* @since MESSAGE_VISITOR
|
||||
* @since 2.12
|
||||
*/
|
||||
@MiraiInternalApi
|
||||
public fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) {
|
||||
|
@ -143,6 +143,11 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_AB
|
||||
* 例如用户发送了两条内容相同的消息, 但其中一条带有引用回复而另一条没有, 则两条消息的索引可能有变化 (当然内容顺序不会改变, 只是 [QuoteReply] 的位置有可能会变化).
|
||||
* 因此在使用直接索引访问时要格外注意兼容性, 故不推荐这种访问方案.
|
||||
*
|
||||
* ### 避免索引访问以提高性能
|
||||
*
|
||||
* 自 2.12 起, [MessageChain] 内部结构有性能优化. 该优化大幅降低元素数量多的 [MessageChain] 的连接的时间复杂度.
|
||||
* 性能优化默认生效, 但若使用 [get], [subList] 等 [List] 于 [Collection] 之外的方法时则会让该优化失效 (相比 2.12 以前不会丢失性能, 只是没有优化).
|
||||
*
|
||||
* ## 撤回和引用
|
||||
*
|
||||
* 要撤回消息, 查看 [MessageSource]
|
||||
@ -253,11 +258,6 @@ public sealed interface MessageChain :
|
||||
@MiraiInternalApi
|
||||
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].
|
||||
*
|
||||
@ -349,7 +349,7 @@ public sealed interface MessageChain :
|
||||
/**
|
||||
* 返回一个不含任何元素的 [MessageChain].
|
||||
*
|
||||
* @since 2.11
|
||||
* @since 2.12
|
||||
*/
|
||||
// Java: MessageUtils.emptyMessageChain()
|
||||
@Suppress("DEPRECATION")
|
||||
@ -363,14 +363,18 @@ public fun emptyMessageChain(): MessageChain = EmptyMessageChain
|
||||
"Please use emptyMessageChain()",
|
||||
replaceWith = ReplaceWith("emptyMessageChain()", "net.mamoe.mirai.message.data.emptyMessageChain")
|
||||
)
|
||||
@DeprecatedSinceMirai(warningSince = "2.11")
|
||||
public object EmptyMessageChain : MessageChain, List<SingleMessage> by emptyList() {
|
||||
@DeprecatedSinceMirai(warningSince = "2.12")
|
||||
public object EmptyMessageChain : MessageChain, List<SingleMessage> by emptyList(), MessageChainImpl {
|
||||
override val size: Int get() = 0
|
||||
|
||||
override fun toString(): String = ""
|
||||
override fun contentToString(): String = ""
|
||||
override fun serializeToMiraiCode(): String = ""
|
||||
|
||||
@MiraiInternalApi
|
||||
override val hasConstrainSingle: Boolean
|
||||
get() = false
|
||||
|
||||
@MiraiExperimentalApi
|
||||
override fun appendMiraiCodeTo(builder: StringBuilder) {
|
||||
}
|
||||
@ -486,7 +490,7 @@ public inline fun messageChainOf(vararg messages: Message): MessageChain = messa
|
||||
*/
|
||||
@JvmName("newChain")
|
||||
public fun Sequence<Message>.toMessageChain(): MessageChain =
|
||||
createMessageChainImplOptimized(this.constrainSingleMessages())
|
||||
LinearMessageChainImpl.create(ConstrainSingleHelper.constrainSingleMessages(this))
|
||||
|
||||
/**
|
||||
* 扁平化 [this] 并创建一个 [MessageChain].
|
||||
@ -525,11 +529,8 @@ public inline fun Array<out Message>.toMessageChain(): MessageChain = this.asSeq
|
||||
@JvmName("newChain")
|
||||
public fun Message.toMessageChain(): MessageChain = when (this) {
|
||||
is MessageChain -> (this as List<SingleMessage>).toMessageChain()
|
||||
else -> MessageChainImpl(
|
||||
listOf(
|
||||
this as? SingleMessage ?: error("Message is either MessageChain nor SingleMessage: $this")
|
||||
)
|
||||
)
|
||||
is SingleMessage -> LinearMessageChainImpl.create(listOf(this), this is ConstrainSingle)
|
||||
else -> error("Message is either MessageChain nor SingleMessage: $this")
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* 此源代码的使用受 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
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmMultifileClass
|
||||
@ -176,7 +176,7 @@ public class MessageChainBuilder private constructor(
|
||||
// avoid resolution to extensions
|
||||
public fun asMessageChain(): MessageChain {
|
||||
this.flushCache()
|
||||
return createMessageChainImplOptimized(this.constrainSingleMessages())
|
||||
return this.toMessageChain()
|
||||
}
|
||||
|
||||
/** 同 [asMessageChain] */
|
||||
|
@ -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_RESOURCE_ID_REGEX_1
|
||||
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.castOrNull
|
||||
import net.mamoe.mirai.utils.replaceAllKotlin
|
||||
|
||||
// region image
|
||||
@ -27,18 +29,6 @@ import net.mamoe.mirai.utils.replaceAllKotlin
|
||||
//// 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
|
||||
internal fun Message.contentEqualsStrictImpl(another: Message, ignoreCase: Boolean): Boolean {
|
||||
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
|
||||
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)
|
||||
)
|
||||
}
|
||||
}*/
|
||||
internal sealed interface MessageChainImpl : MessageChain {
|
||||
/**
|
||||
* 去重算法 v1 - 2.12:
|
||||
* 在连接时若只有 0-1 方包含 [ConstrainSingle], 则使用 [CombinedMessage] 优化性能. 否则使用旧版复杂去重算法构造 [LinearMessageChainImpl].
|
||||
*/
|
||||
@MiraiInternalApi
|
||||
val hasConstrainSingle: Boolean
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun Sequence<SingleMessage>.constrainSingleMessages(): List<SingleMessage> =
|
||||
constrainSingleMessagesImpl(this)
|
||||
|
||||
/**
|
||||
* - [Sequence.toMutableList]
|
||||
* - Replace in-place with marker null
|
||||
*/
|
||||
@MiraiExperimentalApi
|
||||
@JvmSynthetic
|
||||
internal fun constrainSingleMessagesImpl(sequence: Sequence<SingleMessage>): List<SingleMessage> {
|
||||
val list: MutableList<SingleMessage?> = sequence.toMutableList()
|
||||
|
||||
for (singleMessage in list.asReversed()) {
|
||||
if (singleMessage is ConstrainSingle) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
internal val Message.hasConstrainSingle: Boolean
|
||||
get() {
|
||||
if (this is SingleMessage) return this is ConstrainSingle
|
||||
// now `this` is MessageChain
|
||||
return this.castOrNull<MessageChainImpl>()?.hasConstrainSingle ?: true // for external type, assume they do
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ConstrainSingleHelper.constrainSingleMessages
|
||||
*/
|
||||
internal data class ConstrainSingleData(
|
||||
val value: List<SingleMessage>,
|
||||
val hasConstrainSingle: Boolean,
|
||||
)
|
||||
|
||||
internal object ConstrainSingleHelper {
|
||||
@JvmName("constrainSingleMessages_Sequence")
|
||||
internal fun constrainSingleMessages(sequence: Sequence<Message>): ConstrainSingleData =
|
||||
constrainSingleMessages(sequence.flatMap { it.toMessageChain() })
|
||||
|
||||
internal fun constrainSingleMessages(sequence: Sequence<SingleMessage>): ConstrainSingleData =
|
||||
constrainSingleMessagesImpl(sequence)
|
||||
|
||||
/**
|
||||
* - [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
|
||||
@Suppress("UNCHECKED_CAST", "DEPRECATION_ERROR", "DEPRECATION")
|
||||
internal fun <M : SingleMessage> MessageChain.getImpl(key: MessageKey<M>): M? {
|
||||
return this.asSequence().mapNotNull { key.safeCast.invoke(it) }.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [EmptyMessageChain] if [delegate] is empty, otherwise [MessageChainImpl]
|
||||
*/
|
||||
internal fun createMessageChainImplOptimized(delegate: List<SingleMessage>): MessageChain {
|
||||
return if (delegate.isEmpty()) emptyMessageChain()
|
||||
else MessageChainImpl(delegate)
|
||||
}
|
||||
@RequiresOptIn
|
||||
internal annotation class MessageChainConstructor
|
||||
|
||||
|
||||
/**
|
||||
* 使用 [Collection] 作为委托的 [MessageChain]
|
||||
*/
|
||||
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
|
||||
@Serializable(MessageChain.Serializer::class)
|
||||
internal data class MessageChainImpl constructor(
|
||||
internal class LinearMessageChainImpl @MessageChainConstructor private constructor(
|
||||
@JvmField
|
||||
internal val delegate: List<SingleMessage> // 必须 constrainSingleMessages, 且为 immutable
|
||||
) : Message, MessageChain, List<SingleMessage> by delegate {
|
||||
internal val delegate: List<SingleMessage>,
|
||||
override val hasConstrainSingle: Boolean
|
||||
) : Message, MessageChain, List<SingleMessage> by delegate, MessageChainImpl,
|
||||
DirectSizeAccess, DirectToStringAccess {
|
||||
override val size: Int get() = delegate.size
|
||||
override fun iterator(): Iterator<SingleMessage> = delegate.iterator()
|
||||
|
||||
@ -186,13 +148,81 @@ internal data class MessageChainImpl constructor(
|
||||
override fun contentToString(): String = contentToStringTemp
|
||||
|
||||
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
|
||||
internal fun MessageChainImplBySequence(
|
||||
delegate: Sequence<SingleMessage> // 可以有重复 ConstrainSingle
|
||||
): MessageChain = createMessageChainImplOptimized(delegate.constrainSingleMessages())
|
||||
override fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) {
|
||||
for (singleMessage in delegate) {
|
||||
singleMessage.accept(visitor, data)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////
|
||||
|
@ -11,158 +11,232 @@ package net.mamoe.mirai.message.data.visitor
|
||||
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.MiraiInternalApi
|
||||
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||
|
||||
/**
|
||||
* @suppress 这是内部 API, 请不要调用
|
||||
* @since MESSAGE_VISITOR
|
||||
* @since 2.12
|
||||
*/
|
||||
@MiraiInternalApi
|
||||
@NotStableForInheritance
|
||||
public interface MessageVisitor<in D, out 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)
|
||||
}
|
||||
|
||||
public fun visitMessageChain(messageChain: MessageChain, data: D): R {
|
||||
public override fun visitMessageChain(messageChain: MessageChain, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitMessageMetadata(message: MessageMetadata, data: D): R {
|
||||
public override fun visitMessageMetadata(message: MessageMetadata, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitMessageSource(message: MessageSource, data: D): R {
|
||||
public override fun visitMessageSource(message: MessageSource, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitCustomMessageMetadata(message: CustomMessageMetadata, data: D): R {
|
||||
public override fun visitCustomMessageMetadata(message: CustomMessageMetadata, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
public fun visitPlainText(message: PlainText, data: D): R {
|
||||
public override fun visitPlainText(message: PlainText, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitAtAll(message: AtAll, data: D): R {
|
||||
public override fun visitAtAll(message: AtAll, data: D): R {
|
||||
return visitMessageContent(message, data)
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
|
||||
public fun visitAudio(message: Audio, data: D): R {
|
||||
public override fun visitAudio(message: Audio, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitFlashImage(message: FlashImage, data: D): R {
|
||||
public override fun visitFlashImage(message: FlashImage, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitVipFace(message: VipFace, data: D): R {
|
||||
public override fun visitVipFace(message: VipFace, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitDice(message: Dice, data: D): R {
|
||||
public override fun visitDice(message: Dice, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitFileMessage(message: FileMessage, data: D): R {
|
||||
public override fun visitFileMessage(message: FileMessage, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitForwardMessage(message: ForwardMessage, data: D): R {
|
||||
public override fun visitForwardMessage(message: ForwardMessage, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitUnsupportedMessage(message: UnsupportedMessage, data: D): R {
|
||||
public override fun visitUnsupportedMessage(message: UnsupportedMessage, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitServiceMessage(message: ServiceMessage, data: D): R {
|
||||
public override fun visitServiceMessage(message: ServiceMessage, data: D): R {
|
||||
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)
|
||||
}
|
||||
|
||||
public fun visitLightApp(message: LightApp, data: D): R {
|
||||
public override fun visitLightApp(message: LightApp, data: D): R {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @suppress 这是内部 API, 请不要调用
|
||||
* @since 2.11
|
||||
* @since 2.12
|
||||
*/
|
||||
@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
|
||||
}
|
||||
|
||||
/**
|
||||
* @suppress 这是内部 API, 请不要调用
|
||||
* @since 2.11
|
||||
* @since 2.12
|
||||
*/
|
||||
@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)
|
@ -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 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* 此源代码的使用受 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
|
||||
* 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.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertIs
|
||||
|
||||
|
||||
@OptIn(MessageChainConstructor::class)
|
||||
internal class CombinedMessageTest {
|
||||
|
||||
private fun linearMessageChainOf(vararg values: SingleMessage) =
|
||||
LinearMessageChainImpl.create(values.toList(), values.any { it.hasConstrainSingle })
|
||||
|
||||
@Test
|
||||
fun testAsSequence() {
|
||||
var message: Message = PlainText("Hello ")
|
||||
message += "World"
|
||||
fun singlePlusSingleCreatesCombinedMessage() {
|
||||
kotlin.run {
|
||||
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(
|
||||
"Hello World",
|
||||
message.toMessageChain().asSequence().joinToString(separator = "")
|
||||
0,
|
||||
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
|
||||
fun testAsSequence2() {
|
||||
var message: Message = PlainText("Hello ")
|
||||
message += listOf(
|
||||
PlainText("W"),
|
||||
PlainText("o"),
|
||||
PlainText("r") + PlainText("ld")
|
||||
).toMessageChain()
|
||||
fun consistencyTest() {
|
||||
val first = createTestInstance()
|
||||
val second = createTestInstance()
|
||||
assertEquals(first, second)
|
||||
assertEquals(first.hashCode(), second.hashCode())
|
||||
assertEquals(first.toString(), second.toString())
|
||||
assertEquals(first.contentToString(), second.contentToString())
|
||||
assertEquals(first.size, second.size)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// functions
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Test
|
||||
fun subList() {
|
||||
assertEquals(
|
||||
"Hello World",
|
||||
message.toMessageChain().asSequence().joinToString(separator = "")
|
||||
createComplexCombined().slowList.value.subList(2, 4),
|
||||
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 = ", ",
|
||||
prefix: CharSequence = "",
|
||||
postfix: CharSequence = "",
|
||||
limit: Int = -1,
|
||||
truncated: CharSequence = "...",
|
||||
transform: ((T) -> CharSequence)? = null
|
||||
): String {
|
||||
return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// visiting
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fun <T, A : Appendable> Iterator<T>.joinTo(
|
||||
buffer: A,
|
||||
separator: CharSequence = ", ",
|
||||
prefix: CharSequence = "",
|
||||
postfix: CharSequence = "",
|
||||
limit: Int = -1,
|
||||
truncated: CharSequence = "...",
|
||||
transform: ((T) -> CharSequence)? = null
|
||||
): A {
|
||||
buffer.append(prefix)
|
||||
var count = 0
|
||||
for (element in this) {
|
||||
if (++count > 1) buffer.append(separator)
|
||||
if (limit < 0 || count <= limit) {
|
||||
buffer.appendElement(element, transform)
|
||||
} else break
|
||||
@Test
|
||||
fun acceptChildrenTest() {
|
||||
val list = buildList {
|
||||
complexCombined.acceptChildren(object : MessageVisitorUnit<Unit>() {
|
||||
override fun visitMessage(message: Message, data: Unit) {
|
||||
add(message)
|
||||
super.visitMessage(message, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
assertEquals(
|
||||
listOf(
|
||||
PlainText("foo"),
|
||||
createTestInstance()
|
||||
),
|
||||
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)?) {
|
||||
when {
|
||||
transform != null -> append(transform(element))
|
||||
element is CharSequence? -> append(element)
|
||||
element is Char -> append(element)
|
||||
else -> append(element.toString())
|
||||
@OptIn(MessageChainConstructor::class)
|
||||
@Test
|
||||
fun acceptChildrenRecursiveTest() {
|
||||
val list = buildList {
|
||||
complexCombined.accept(object : RecursiveMessageVisitor<Unit>() {
|
||||
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() }
|
||||
}
|
||||
}
|
@ -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() })
|
||||
}
|
@ -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 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* 此源代码的使用受 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
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
@ -72,7 +72,7 @@ internal class ConstrainSingleTest {
|
||||
|
||||
assertEquals(7, result.size)
|
||||
assertEquals(" [OK]ss p test", result.contentToString())
|
||||
result as MessageChainImpl
|
||||
result as LinearMessageChainImpl
|
||||
assertSame(new, result.delegate.toTypedArray()[2])
|
||||
}
|
||||
|
||||
@ -85,7 +85,8 @@ internal class ConstrainSingleTest {
|
||||
last
|
||||
)
|
||||
|
||||
val result = sequence.constrainSingleMessages()
|
||||
val (result, has) = ConstrainSingleHelper.constrainSingleMessages(sequence)
|
||||
assertTrue { has }
|
||||
assertEquals(result.count(), 1)
|
||||
assertSame(result.single(), last)
|
||||
}
|
||||
@ -100,7 +101,8 @@ internal class ConstrainSingleTest {
|
||||
last
|
||||
)
|
||||
|
||||
val result = sequence.constrainSingleMessages()
|
||||
val (result, has) = ConstrainSingleHelper.constrainSingleMessages(sequence)
|
||||
assertTrue { has }
|
||||
assertEquals(result.count(), 2)
|
||||
assertSame(result.first(), last)
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -319,9 +319,7 @@ internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessage
|
||||
)
|
||||
forward.nodeList.forEach { tmp.addAll(it.messageChain) }
|
||||
|
||||
// toMessageChain will lose some element
|
||||
@Suppress("INVISIBLE_MEMBER")
|
||||
createMessageChainImplOptimized(tmp).verifyLength(forward, contact)
|
||||
tmp.verifyLength(forward, contact)
|
||||
}
|
||||
|
||||
val resId = getMiraiImpl().uploadMessageHighway(
|
||||
|
@ -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) }
|
||||
|
||||
@ -55,14 +55,14 @@ internal fun Message.verifySendingValid() {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun MessageChain.verifyLength(
|
||||
internal fun Iterable<SingleMessage>.verifyLength(
|
||||
originalMessage: Message, target: Contact,
|
||||
): Int {
|
||||
val chain = this
|
||||
val length = estimateLength(target, 15001)
|
||||
if (length > 15000 || countImages() > 50) {
|
||||
throw MessageTooLargeException(
|
||||
target, originalMessage, this,
|
||||
target, originalMessage, this.toMessageChain(),
|
||||
"message(${
|
||||
chain.joinToString("", limit = 10).let { rsp ->
|
||||
if (rsp.length > 100) {
|
||||
|
@ -156,8 +156,9 @@ internal object LightMessageRefiner : MessageRefiner() {
|
||||
val source = this.sourceOrNull?.safeCast<IncomingMessageSourceInternal>() ?: return this
|
||||
val originalMessage = this
|
||||
source.originalMessageLazy = lazy {
|
||||
@Suppress("INVISIBLE_MEMBER")
|
||||
createMessageChainImplOptimized(originalMessage.filterNot { it is MessageSource })
|
||||
originalMessage.filterNot { it is MessageSource }.toMessageChain()
|
||||
// @Suppress("INVISIBLE_MEMBER")
|
||||
// createMessageChainImplOptimized(originalMessage.filterNot { it is MessageSource })
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
@ -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 ->
|
||||
it.estimateLength(target, up)
|
||||
}
|
||||
|
@ -151,8 +151,7 @@ internal class CleanupRubbishMessageElementsTest {
|
||||
}
|
||||
|
||||
private fun noMessageSource(c: MessageChain): MessageChain {
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
return createMessageChainImplOptimized(c.filterNot { it is MessageSource })
|
||||
return c.filterNot { it is MessageSource }.toMessageChain()
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
Loading…
Reference in New Issue
Block a user