mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-09 17:47:02 +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 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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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 `+` 操作符重载
|
* @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) {
|
||||||
|
@ -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")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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] */
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
|
@ -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)
|
@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 许可证的约束, 可以在以下链接找到该许可证.
|
* 此源代码的使用受 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)
|
||||||
}
|
}
|
||||||
|
@ -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) }
|
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(
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user