Commit b8b749bf authored by Him188's avatar Him188

Completed CombinedMessage redesigning and constraining on concatenation

parent eaa1e96a
...@@ -7,6 +7,11 @@ plugins { ...@@ -7,6 +7,11 @@ plugins {
description = "Binary and source compatibility validator for mirai-core and mirai-core-qqandroid" description = "Binary and source compatibility validator for mirai-core and mirai-core-qqandroid"
repositories {
mavenCentral()
jcenter()
}
kotlin { kotlin {
sourceSets { sourceSets {
all { all {
...@@ -17,7 +22,19 @@ kotlin { ...@@ -17,7 +22,19 @@ kotlin {
main { main {
dependencies { dependencies {
api(kotlin("stdlib")) api(kotlin("stdlib"))
api(project(":mirai-core-qqandroid")) runtimeOnly(project(":mirai-core-qqandroid"))
compileOnly("net.mamoe:mirai-core-qqandroid-jvm:0.33.0")
api(kotlinx("coroutines-core", Versions.Kotlin.coroutines))
}
}
test {
dependencies {
api(kotlin("stdlib"))
api(kotlin("test"))
api(kotlin("test-junit"))
runtimeOnly(project(":mirai-core-qqandroid"))
compileOnly("net.mamoe:mirai-core-qqandroid-jvm:0.33.0")
api(kotlinx("coroutines-core", Versions.Kotlin.coroutines)) api(kotlinx("coroutines-core", Versions.Kotlin.coroutines))
} }
} }
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package compatibility
fun main() {
}
\ No newline at end of file
package compatibility
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(MiraiInternalAPI::class)
internal class CombinedMessageTest {
@Test
fun testAsSequence() {
var message: Message = "Hello ".toMessage()
message += "World"
assertEquals(
"Hello World",
(message as CombinedMessage).asSequence().joinToString(separator = "")
)
}
@Test
fun testAsSequence2() {
var message: Message = "Hello ".toMessage()
message += listOf(
PlainText("W"),
PlainText("o"),
PlainText("r") + PlainText("ld")
).asMessageChain()
assertEquals(
"Hello World",
(message as CombinedMessage).asSequence().joinToString(separator = "")
)
}
}
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()
}
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
}
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())
}
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package compatibility
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.PlainText
import org.junit.Test
internal class TestKotlinCompatibility {
@Test
fun testMessageChain() {
val x = PlainText("te") + PlainText("st")
println(Message::class.java.declaredMethods.joinToString("\n"))
println()
println(x::class.java.declaredMethods.joinToString("\n"))
}
}
\ No newline at end of file
...@@ -56,6 +56,7 @@ import kotlin.coroutines.CoroutineContext ...@@ -56,6 +56,7 @@ import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.random.Random import kotlin.random.Random
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
internal fun Bot.asQQAndroidBot(): QQAndroidBot { internal fun Bot.asQQAndroidBot(): QQAndroidBot {
...@@ -98,8 +99,9 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -98,8 +99,9 @@ internal abstract class QQAndroidBotBase constructor(
override val friends: ContactList<QQ> = ContactList(LockFreeLinkedList()) override val friends: ContactList<QQ> = ContactList(LockFreeLinkedList())
override lateinit var nick: String override val nick: String get() = selfInfo.nick
internal set
internal lateinit var selfInfo: JceFriendInfo
override val selfQQ: QQ by lazy { override val selfQQ: QQ by lazy {
@OptIn(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
......
...@@ -51,7 +51,7 @@ import kotlin.jvm.JvmSynthetic ...@@ -51,7 +51,7 @@ import kotlin.jvm.JvmSynthetic
internal inline class FriendInfoImpl( internal inline class FriendInfoImpl(
private val jceFriendInfo: net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo private val jceFriendInfo: net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo
) : FriendInfo { ) : FriendInfo {
override val nick: String get() = jceFriendInfo.nick ?: "" override val nick: String get() = jceFriendInfo.nick
override val uin: Long get() = jceFriendInfo.friendUin override val uin: Long get() = jceFriendInfo.friendUin
} }
......
...@@ -223,8 +223,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -223,8 +223,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
// self info // self info
data.selfInfo?.apply { data.selfInfo?.run {
bot.nick = nick ?: "" bot.selfInfo = this
// bot.remark = remark ?: "" // bot.remark = remark ?: ""
// bot.sex = sex // bot.sex = sex
} }
......
...@@ -109,7 +109,7 @@ internal class FriendInfo( ...@@ -109,7 +109,7 @@ internal class FriendInfo(
@JceId(11) val sqqOnLineStateV2: Byte? = null, @JceId(11) val sqqOnLineStateV2: Byte? = null,
@JceId(12) val sShowName: String? = "", @JceId(12) val sShowName: String? = "",
@JceId(13) val isRemark: Byte? = null, @JceId(13) val isRemark: Byte? = null,
@JceId(14) val nick: String? = "", @JceId(14) val nick: String = "",
@JceId(15) val specialFlag: Byte? = null, @JceId(15) val specialFlag: Byte? = null,
@JceId(16) val vecIMGroupID: ByteArray? = null, @JceId(16) val vecIMGroupID: ByteArray? = null,
@JceId(17) val vecMSFGroupID: ByteArray? = null, @JceId(17) val vecMSFGroupID: ByteArray? = null,
......
...@@ -87,6 +87,7 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { ...@@ -87,6 +87,7 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
/** /**
* 昵称 * 昵称
*/ */
@SinceMirai("0.33.1")
abstract val nick: String abstract val nick: String
/** /**
......
...@@ -24,7 +24,7 @@ import net.mamoe.mirai.message.ContactMessage ...@@ -24,7 +24,7 @@ import net.mamoe.mirai.message.ContactMessage
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.first import net.mamoe.mirai.message.data.firstIsInstance
import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.SinceMirai
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
...@@ -700,7 +700,7 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>( ...@@ -700,7 +700,7 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
@MessageDsl @MessageDsl
@SinceMirai("0.30.0") @SinceMirai("0.30.0")
inline fun <reified N : Message> has(noinline onEvent: @MessageDsl suspend M.(N) -> R): Ret = inline fun <reified N : Message> has(noinline onEvent: @MessageDsl suspend M.(N) -> R): Ret =
content({ message.any { it is N } }, { onEvent.invoke(this, message.first()) }) content({ message.any { it is N } }, { onEvent.invoke(this, message.firstIsInstance()) })
/** /**
* 如果 [mapper] 返回值非空, 就执行 [onEvent] * 如果 [mapper] 返回值非空, 就执行 [onEvent]
......
...@@ -357,8 +357,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContaining( ...@@ -357,8 +357,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContaining(
): M { ): M {
return subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) { return subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContaining) } takeIf { this.isContextIdenticalWith(this@nextMessageContaining) }
.takeIf { this.message.any<M>() } .takeIf { this.message.anyIsInstance<M>() }
}.message.first() }.message.firstIsInstance()
} }
@JvmSynthetic @JvmSynthetic
...@@ -370,8 +370,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingAsync( ...@@ -370,8 +370,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingAsync(
@Suppress("RemoveExplicitTypeArguments") @Suppress("RemoveExplicitTypeArguments")
subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) { subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingAsync) } takeIf { this.isContextIdenticalWith(this@nextMessageContainingAsync) }
.takeIf { this.message.any<M>() } .takeIf { this.message.anyIsInstance<M>() }
}.message.first<M>() }.message.firstIsInstance<M>()
} }
} }
...@@ -391,8 +391,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContainingOrN ...@@ -391,8 +391,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContainingOrN
): M? { ): M? {
return subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) { return subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) } takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) }
.takeIf { this.message.any<M>() } .takeIf { this.message.anyIsInstance<M>() }
}?.message?.first() }?.message?.firstIsInstance()
} }
@JvmSynthetic @JvmSynthetic
...@@ -403,8 +403,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNullAsync ...@@ -403,8 +403,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNullAsync
return this.bot.async(coroutineContext) { return this.bot.async(coroutineContext) {
subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) { subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNullAsync) } takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNullAsync) }
.takeIf { this.message.any<M>() } .takeIf { this.message.anyIsInstance<M>() }
}?.message?.first<M>() }?.message?.firstIsInstance<M>()
} }
} }
......
...@@ -17,6 +17,7 @@ package net.mamoe.mirai.message.data ...@@ -17,6 +17,7 @@ package net.mamoe.mirai.message.data
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.nameCardOrNick import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
...@@ -56,6 +57,7 @@ private constructor(val target: Long, val display: String) : ...@@ -56,6 +57,7 @@ private constructor(val target: Long, val display: String) :
} }
// 自动为消息补充 " " // 自动为消息补充 " "
@OptIn(MiraiInternalAPI::class)
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmName("followedBy") @JvmName("followedBy")
...@@ -67,7 +69,7 @@ private constructor(val target: Long, val display: String) : ...@@ -67,7 +69,7 @@ private constructor(val target: Long, val display: String) :
return followedByInternalForBinaryCompatibility(PlainText(" ") + tail) return followedByInternalForBinaryCompatibility(PlainText(" ") + tail)
} }
override fun followedBy(tail: Message): Message { override fun followedBy(tail: Message): MessageChain {
if (tail is PlainText && tail.stringValue.startsWith(' ')) { if (tail is PlainText && tail.stringValue.startsWith(' ')) {
return super.followedBy(tail) return super.followedBy(tail)
} }
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
...@@ -42,6 +43,7 @@ object AtAll : ...@@ -42,6 +43,7 @@ object AtAll :
override fun contentToString(): String = display override fun contentToString(): String = display
// 自动为消息补充 " " // 自动为消息补充 " "
@OptIn(MiraiInternalAPI::class)
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("followedBy") @JvmName("followedBy")
...@@ -53,7 +55,7 @@ object AtAll : ...@@ -53,7 +55,7 @@ object AtAll :
return followedByInternalForBinaryCompatibility(PlainText(" ") + tail) return followedByInternalForBinaryCompatibility(PlainText(" ") + tail)
} }
override fun followedBy(tail: Message): Message { override fun followedBy(tail: Message): MessageChain {
if (tail is PlainText && tail.stringValue.startsWith(' ')) { if (tail is PlainText && tail.stringValue.startsWith(' ')) {
return super.followedBy(tail) return super.followedBy(tail)
} }
......
...@@ -14,11 +14,13 @@ package net.mamoe.mirai.message.data ...@@ -14,11 +14,13 @@ package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.PlannedRemoval
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/** /**
* 链接的两个消息. * 快速链接的两个消息 (避免构造新的 list).
* *
* 不要直接构造 [CombinedMessage], 使用 [Message.plus] * 不要直接构造 [CombinedMessage], 使用 [Message.plus]
* 要连接多个 [Message], 使用 [buildMessageChain] * 要连接多个 [Message], 使用 [buildMessageChain]
...@@ -27,64 +29,68 @@ import kotlin.jvm.JvmName ...@@ -27,64 +29,68 @@ import kotlin.jvm.JvmName
* *
* Left-biased list * Left-biased list
*/ */
@MiraiInternalAPI("this API is going to be internal")
class CombinedMessage class CombinedMessage
@Deprecated(message = "use Message.plus", level = DeprecationLevel.ERROR) internal constructor(
@MiraiInternalAPI("CombinedMessage 构造器可能会在将来被改动") constructor( internal val left: Message, // 必须已经完成 constrain single
@MiraiExperimentalAPI("CombinedMessage.left 可能会在将来被改动") internal val tail: Message
val left: SingleMessage, ) : Message, MessageChain {
@MiraiExperimentalAPI("CombinedMessage.tail 可能会在将来被改动")
val tail: SingleMessage
) : Iterable<SingleMessage>, Message {
/*
// 不要把它用作 local function, 会编译错误
@OptIn(MiraiExperimentalAPI::class)
private suspend fun SequenceScope<Message>.yieldCombinedOrElements(message: Message) {
when (message) {
is CombinedMessage -> {
// fast path, 避免创建新的 iterator, 也不会挂起协程
yieldCombinedOrElements(message.left)
yieldCombinedOrElements(message.tail)
}
is Iterable<*> -> {
// 更好的性能, 因为协程不会挂起.
// 这可能会导致爆栈 (十万个元素), 但作为消息序列足够了.
message.forEach {
yieldCombinedOrElements(
it as? Message ?: error(
"A Message implementing Iterable must implement Iterable<Message>, " +
"whereas got ${it!!::class.simpleName}"
)
)
}
}
else -> {
check(message is SingleMessage) {
"unsupported Message type. " +
"A Message must be a CombinedMessage, a Iterable<Message> or a SingleMessage"
}
yield(message)
}
}
}
*/
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
fun asSequence(): Sequence<SingleMessage> = sequence { fun asSequence(): Sequence<SingleMessage> = sequence {
yield(left) yieldCombinedOrElementsFlatten(this@CombinedMessage)
yield(tail)
} }
override fun iterator(): Iterator<SingleMessage> { override fun iterator(): Iterator<SingleMessage> {
return asSequence().iterator() return asSequence().iterator()
} }
@PlannedRemoval("1.0.0")
@Deprecated(
"有歧义, 自行使用 contentToString() 比较",
ReplaceWith("this.contentToString() == other"),
DeprecationLevel.HIDDEN
)
override fun contains(sub: String): Boolean {
return contentToString().contains(sub)
}
override val size: Int = when {
left === EmptyMessageChain && tail !== EmptyMessageChain -> 1
left === EmptyMessageChain && tail === EmptyMessageChain -> 0
left !== EmptyMessageChain && tail === EmptyMessageChain -> 1
left !== EmptyMessageChain && tail !== EmptyMessageChain -> 2
else -> error("stub")
}
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
override fun toString(): String { override fun toString(): String {
return tail.toString() + left.toString() return tail.toString() + left.toString()
} }
override fun contentToString(): String { override fun contentToString(): String {
return toString() return left.contentToString() + tail.contentToString()
}
}
@JvmSynthetic
// 不要把它用作 local function, 会编译错误
@OptIn(MiraiExperimentalAPI::class, MiraiInternalAPI::class)
private suspend fun SequenceScope<SingleMessage>.yieldCombinedOrElementsFlatten(message: Message) {
when (message) {
is CombinedMessage -> {
// fast path, 避免创建新的 iterator, 也不会挂起协程
yieldCombinedOrElementsFlatten(message.left)
yieldCombinedOrElementsFlatten(message.tail)
}
is MessageChain -> {
yieldAll(message)
}
else -> {
check(message is SingleMessage) {
"unsupported Message type: ${message::class}" +
"A Message must be a CombinedMessage, a Iterable<Message> or a SingleMessage"
}
yield(message)
}
} }
} }
...@@ -15,6 +15,7 @@ import net.mamoe.mirai.contact.Contact ...@@ -15,6 +15,7 @@ import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
...@@ -24,16 +25,16 @@ import kotlin.jvm.JvmSynthetic ...@@ -24,16 +25,16 @@ import kotlin.jvm.JvmSynthetic
* 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] 等. * 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] 等.
* *
* [消息][Message] 分为 * [消息][Message] 分为
* - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource] * - [SingleMessage]:
* - [MessageContent] 单个消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] 等. * - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource], [引用回复][QuoteReply].
* - [CombinedMessage] 通过 [plus] 连接的两个消息. 可通过 [asMessageChain] 转换为 [MessageChain] * - [MessageContent] 含内容的消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] 等.
* - [MessageChain] 不可变消息链, 链表形式链接的多个 [SingleMessage] 实例. * - [MessageChain]: 不可变消息链, 链表形式链接的多个 [SingleMessage] 实例.
* *
* #### 在 Kotlin 使用 [Message]: * #### 在 Kotlin 使用 [Message]:
* 这与使用 [String] 的使用非常类似. * 这与使用 [String] 的使用非常类似.
* *
* 比较 [Message] 与 [String] (使用 infix [Message.eq]): * 比较 [SingleMessage] 与 [String]:
* `if(event eq "你好") qq.sendMessage(event)` * `if(message.contentToString() == "你好") qq.sendMessage(event)`
* *
* 连接 [Message] 与 [Message], [String], (使用 operator [Message.plus]): * 连接 [Message] 与 [Message], [String], (使用 operator [Message.plus]):
* ```kotlin * ```kotlin
...@@ -65,6 +66,7 @@ import kotlin.jvm.JvmSynthetic ...@@ -65,6 +66,7 @@ import kotlin.jvm.JvmSynthetic
* *
* @see Contact.sendMessage 发送消息 * @see Contact.sendMessage 发送消息
*/ */
@OptIn(MiraiInternalAPI::class)
interface Message { interface Message {
/** /**
* 类型 Key. * 类型 Key.
...@@ -75,25 +77,16 @@ interface Message { ...@@ -75,25 +77,16 @@ interface Message {
*/ */
interface Key<out M : Message> { interface Key<out M : Message> {
/** /**
* 此 [Key] 指代的 [Message] 类型名. 一般为 `class.simpleName` * 此 [Key] 指代的 [Message] 类型名. 一般为 `class.simpleName`, 如 "QuoteReply", "PlainText"
*/ */
@SinceMirai("0.34.0") @SinceMirai("0.34.0")
val typeName: String val typeName: String
} }
infix fun eq(other: Message): Boolean = this.toString() == other.toString()
/**
* 将 [toString] 与 [other] 比较
*/
infix fun eq(other: String): Boolean = this.toString() == other
operator fun contains(sub: String): Boolean = false
/** /**
* 把 `this` 连接到 [tail] 的头部. 类似于字符串相加. * 把 `this` 连接到 [tail] 的头部. 类似于字符串相加.
* *
* 连接后无法保证 [ConstrainSingle] 的元素单独存在. 需在 * 连接后可以保证 [ConstrainSingle] 的元素单独存在.
* *
* 例: * 例:
* ```kotlin * ```kotlin
...@@ -111,17 +104,67 @@ interface Message { ...@@ -111,17 +104,67 @@ interface Message {
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
@JvmSynthetic // in java they should use `plus` instead @JvmSynthetic // in java they should use `plus` instead
fun followedBy(tail: Message): Message { fun followedBy(tail: Message): MessageChain {
TODO() when {
if (this is SingleMessage && tail is SingleMessage) { this is SingleMessage && tail is SingleMessage -> {
if (this is ConstrainSingle<*> && tail is ConstrainSingle<*>) { if (this is ConstrainSingle<*> && tail is ConstrainSingle<*>) {
return if (this.key == tail.key) { 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)) {
val iterator = this.iterator()
var tailUsed = false
return MessageChainImplByCollection(
constrainSingleMessagesImpl {
if (iterator.hasNext()) {
iterator.next()
} else if (!tailUsed) {
tailUsed = true
tail tail
} else { } else null
CombinedMessage(this, tail)
} }
)
} }
return CombinedMessage(this, tail)
}
else -> { // both chain
this as MessageChain
tail as MessageChain
var iterator = this.iterator()
var tailUsed = false
return MessageChainImplByCollection(
constrainSingleMessagesImpl {
if (iterator.hasNext()) {
iterator.next()
} else if (!tailUsed) {
tailUsed = true
iterator = tail.iterator()
iterator.next()
} else null
}
)
}
} }
} }
...@@ -147,43 +190,79 @@ interface Message { ...@@ -147,43 +190,79 @@ interface Message {
@SinceMirai("0.34.0") @SinceMirai("0.34.0")
fun contentToString(): String fun contentToString(): String
operator fun plus(another: Message): Message = this.followedBy(another) operator fun plus(another: Message): MessageChain = this.followedBy(another)
// avoid resolution ambiguity // don't remove! avoid resolution ambiguity between `CharSequence` and `Message`
operator fun plus(another: SingleMessage): Message = this.followedBy(another) operator fun plus(another: SingleMessage): MessageChain = this.followedBy(another)
operator fun plus(another: String): Message = this.followedBy(another.toMessage()) operator fun plus(another: String): MessageChain = this.followedBy(another.toMessage())
// `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)` // `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)`
operator fun plus(another: CharSequence): Message = this.followedBy(another.toString().toMessage()) operator fun plus(another: CharSequence): MessageChain = this.followedBy(another.toString().toMessage())
//////////////////////////////////////
// FOR BINARY COMPATIBILITY UNTIL 1.0.0 // FOR BINARY COMPATIBILITY UNTIL 1.0.0
//////////////////////////////////////
@PlannedRemoval("1.0.0")
@JvmSynthetic
@Deprecated(
"有歧义, 自行使用 contentToString() 比较",
ReplaceWith("this.contentToString() == other"),
DeprecationLevel.HIDDEN
)
infix fun eq(other: Message): Boolean = this.toString() == other.toString()
/**
* 将 [contentToString] 与 [other] 比较
*/
@PlannedRemoval("1.0.0")
@JvmSynthetic
@Deprecated(
"有歧义, 自行使用 contentToString() 比较",
ReplaceWith("this.contentToString() == other"),
DeprecationLevel.HIDDEN
)
infix fun eq(other: String): Boolean = this.contentToString() == other
@PlannedRemoval("1.0.0")
@JvmSynthetic
@Deprecated(
"有歧义, 自行使用 contentToString() 比较",
ReplaceWith("this.contentToString() == other"),
DeprecationLevel.HIDDEN
)
operator fun contains(sub: String): Boolean = false
@PlannedRemoval("1.0.0")
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("followedBy") @JvmName("followedBy")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmSynthetic @JvmSynthetic
fun followedBy1(tail: Message): CombinedMessage = this.followedByInternalForBinaryCompatibility(tail) fun followedBy1(tail: Message): CombinedMessage = this.followedByInternalForBinaryCompatibility(tail)
@PlannedRemoval("1.0.0")
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("plus") @JvmName("plus")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmSynthetic @JvmSynthetic
fun plus1(another: Message): CombinedMessage = this.followedByInternalForBinaryCompatibility(another) fun plus1(another: Message): CombinedMessage = this.followedByInternalForBinaryCompatibility(another)
@PlannedRemoval("1.0.0")
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("plus") @JvmName("plus")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmSynthetic @JvmSynthetic
fun plus1(another: SingleMessage): CombinedMessage = this.followedByInternalForBinaryCompatibility(another) fun plus1(another: SingleMessage): CombinedMessage = this.followedByInternalForBinaryCompatibility(another)
@PlannedRemoval("1.0.0")
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("plus") @JvmName("plus")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmSynthetic @JvmSynthetic
fun plus1(another: String): CombinedMessage = this.followedByInternalForBinaryCompatibility(another.toMessage()) fun plus1(another: String): CombinedMessage = this.followedByInternalForBinaryCompatibility(another.toMessage())
@PlannedRemoval("1.0.0")
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("plus") @JvmName("plus")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
...@@ -192,24 +271,24 @@ interface Message { ...@@ -192,24 +271,24 @@ interface Message {
this.followedByInternalForBinaryCompatibility(another.toString().toMessage()) this.followedByInternalForBinaryCompatibility(another.toString().toMessage())
} }
@OptIn(MiraiInternalAPI::class)
private fun Message.hasDuplicationOfConstrain(key: Message.Key<*>): Boolean {
return when (this) {
is SingleMessage -> (this as? ConstrainSingle<*>)?.key == key
is CombinedMessage -> return this.left.hasDuplicationOfConstrain(key) || this.tail.hasDuplicationOfConstrain(key)
is SingleMessageChainImpl -> (this.delegate as? ConstrainSingle<*>)?.key == key
is MessageChainImplByCollection -> this.delegate.any { (it as? ConstrainSingle<*>)?.key == key }
is MessageChainImplBySequence -> this.any { (it as? ConstrainSingle<*>)?.key == key }
else -> error("stub")
}
}
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
@JvmSynthetic @JvmSynthetic
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
internal fun Message.followedByInternalForBinaryCompatibility(tail: Message): CombinedMessage { internal fun Message.followedByInternalForBinaryCompatibility(tail: Message): CombinedMessage {
TODO() return CombinedMessage(EmptyMessageChain, this.followedBy(tail))
if (this is ConstrainSingle<*>) {
}
if (this is SingleMessage && tail is SingleMessage) {
if (this is ConstrainSingle<*> && tail is ConstrainSingle<*>
&& this.key == tail.key
) return CombinedMessage(EmptyMessageChain, tail)
return CombinedMessage(left = this, tail = tail)
} else {
// return CombinedMessage(left = this.constrain)
}
} }
@JvmSynthetic @JvmSynthetic
...@@ -218,7 +297,8 @@ suspend inline fun <C : Contact> Message.sendTo(contact: C): MessageReceipt<C> { ...@@ -218,7 +297,8 @@ suspend inline fun <C : Contact> Message.sendTo(contact: C): MessageReceipt<C> {
return contact.sendMessage(this) as MessageReceipt<C> return contact.sendMessage(this) as MessageReceipt<C>
} }
fun Message.repeat(count: Int): MessageChain { // inline: for future removal
inline fun Message.repeat(count: Int): MessageChain {
if (this is ConstrainSingle<*>) { if (this is ConstrainSingle<*>) {
// fast-path // fast-path
return SingleMessageChainImpl(this) return SingleMessageChainImpl(this)
...@@ -231,7 +311,21 @@ fun Message.repeat(count: Int): MessageChain { ...@@ -231,7 +311,21 @@ fun Message.repeat(count: Int): MessageChain {
@JvmSynthetic @JvmSynthetic
inline operator fun Message.times(count: Int): MessageChain = this.repeat(count) inline operator fun Message.times(count: Int): MessageChain = this.repeat(count)
interface SingleMessage : Message, CharSequence, Comparable<String> @Suppress("OverridingDeprecatedMember")
interface SingleMessage : Message, CharSequence, Comparable<String> {
override operator fun contains(sub: String): Boolean = sub in this.contentToString()
/**
* 比较两个消息的 [contentToString]
*/
override infix fun eq(other: Message): Boolean = this.contentToString() == other.contentToString()
/**
* 将 [contentToString] 与 [other] 比较
*/
override infix fun eq(other: String): Boolean = this.contentToString() == other
}
/** /**
* 消息元数据, 即不含内容的元素. * 消息元数据, 即不含内容的元素.
...@@ -251,7 +345,7 @@ interface MessageMetadata : SingleMessage { ...@@ -251,7 +345,7 @@ interface MessageMetadata : SingleMessage {
*/ */
@SinceMirai("0.34.0") @SinceMirai("0.34.0")
@MiraiExperimentalAPI @MiraiExperimentalAPI
interface ConstrainSingle<M : Message> : SingleMessage { interface ConstrainSingle<M : Message> : MessageMetadata {
val key: Message.Key<M> val key: Message.Key<M>
} }
...@@ -263,6 +357,7 @@ interface MessageContent : SingleMessage ...@@ -263,6 +357,7 @@ interface MessageContent : SingleMessage
/** /**
* 将 [this] 发送给指定联系人 * 将 [this] 发送给指定联系人
*/ */
@JvmSynthetic
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
suspend inline fun <C : Contact> MessageChain.sendTo(contact: C): MessageReceipt<C> = suspend inline fun <C : Contact> MessageChain.sendTo(contact: C): MessageReceipt<C> =
contact.sendMessage(this) as MessageReceipt<C> contact.sendMessage(this) as MessageReceipt<C>
\ No newline at end of file
...@@ -40,10 +40,16 @@ import kotlin.reflect.KProperty ...@@ -40,10 +40,16 @@ import kotlin.reflect.KProperty
* @see flatten 扁平化 * @see flatten 扁平化
*/ */
interface MessageChain : Message, Iterable<SingleMessage> { interface MessageChain : Message, Iterable<SingleMessage> {
@PlannedRemoval("1.0.0")
@Deprecated(
"有歧义, 自行使用 contentToString() 比较",
ReplaceWith("this.contentToString() == other"),
DeprecationLevel.HIDDEN
)
override operator fun contains(sub: String): Boolean override operator fun contains(sub: String): Boolean
/** /**
* 元素数量 * 元素数量. [EmptyMessageChain] 不参加计数.
*/ */
@SinceMirai("0.31.1") @SinceMirai("0.31.1")
val size: Int val size: Int
...@@ -104,20 +110,20 @@ inline fun MessageChain.foreachContent(block: (Message) -> Unit) { ...@@ -104,20 +110,20 @@ inline fun MessageChain.foreachContent(block: (Message) -> Unit) {
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
*/ */
@JvmSynthetic @JvmSynthetic
inline fun <reified M : Message?> MessageChain.firstOrNull(): M? = this.firstOrNull { it is M } as M? inline fun <reified M : Message?> MessageChain.firstIsInstanceOrNull(): M? = this.firstOrNull { it is M } as M?
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
* @throws [NoSuchElementException] 如果找不到该类型的实例 * @throws [NoSuchElementException] 如果找不到该类型的实例
*/ */
@JvmSynthetic @JvmSynthetic
inline fun <reified M : Message> MessageChain.first(): M = this.first { it is M } as M inline fun <reified M : Message> MessageChain.firstIsInstance(): M = this.first { it is M } as M
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
*/ */
@JvmSynthetic @JvmSynthetic
inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is M } inline fun <reified M : Message> MessageChain.anyIsInstance(): Boolean = this.any { it is M }
/** /**
...@@ -127,35 +133,35 @@ inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is ...@@ -127,35 +133,35 @@ inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is
@JvmSynthetic @JvmSynthetic
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) { fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) {
At -> firstOrNull<At>() At -> firstIsInstanceOrNull<At>()
AtAll -> firstOrNull<AtAll>() AtAll -> firstIsInstanceOrNull<AtAll>()
PlainText -> firstOrNull<PlainText>() PlainText -> firstIsInstanceOrNull<PlainText>()
Image -> firstOrNull<Image>() Image -> firstIsInstanceOrNull<Image>()
OnlineImage -> firstOrNull<OnlineImage>() OnlineImage -> firstIsInstanceOrNull<OnlineImage>()
OfflineImage -> firstOrNull<OfflineImage>() OfflineImage -> firstIsInstanceOrNull<OfflineImage>()
GroupImage -> firstOrNull<GroupImage>() GroupImage -> firstIsInstanceOrNull<GroupImage>()
FriendImage -> firstOrNull<FriendImage>() FriendImage -> firstIsInstanceOrNull<FriendImage>()
Face -> firstOrNull<Face>() Face -> firstIsInstanceOrNull<Face>()
QuoteReply -> firstOrNull<QuoteReply>() QuoteReply -> firstIsInstanceOrNull<QuoteReply>()
MessageSource -> firstOrNull<MessageSource>() MessageSource -> firstIsInstanceOrNull<MessageSource>()
OnlineMessageSource -> firstOrNull<OnlineMessageSource>() OnlineMessageSource -> firstIsInstanceOrNull<OnlineMessageSource>()
OfflineMessageSource -> firstOrNull<OfflineMessageSource>() OfflineMessageSource -> firstIsInstanceOrNull<OfflineMessageSource>()
OnlineMessageSource.Outgoing -> firstOrNull<OnlineMessageSource.Outgoing>() OnlineMessageSource.Outgoing -> firstIsInstanceOrNull<OnlineMessageSource.Outgoing>()
OnlineMessageSource.Outgoing.ToGroup -> firstOrNull<OnlineMessageSource.Outgoing.ToGroup>() OnlineMessageSource.Outgoing.ToGroup -> firstIsInstanceOrNull<OnlineMessageSource.Outgoing.ToGroup>()
OnlineMessageSource.Outgoing.ToFriend -> firstOrNull<OnlineMessageSource.Outgoing.ToFriend>() OnlineMessageSource.Outgoing.ToFriend -> firstIsInstanceOrNull<OnlineMessageSource.Outgoing.ToFriend>()
OnlineMessageSource.Incoming -> firstOrNull<OnlineMessageSource.Incoming>() OnlineMessageSource.Incoming -> firstIsInstanceOrNull<OnlineMessageSource.Incoming>()
OnlineMessageSource.Incoming.FromGroup -> firstOrNull<OnlineMessageSource.Incoming.FromGroup>() OnlineMessageSource.Incoming.FromGroup -> firstIsInstanceOrNull<OnlineMessageSource.Incoming.FromGroup>()
OnlineMessageSource.Incoming.FromFriend -> firstOrNull<OnlineMessageSource.Incoming.FromFriend>() OnlineMessageSource.Incoming.FromFriend -> firstIsInstanceOrNull<OnlineMessageSource.Incoming.FromFriend>()
OnlineMessageSource -> firstOrNull<OnlineMessageSource>() OnlineMessageSource -> firstIsInstanceOrNull<OnlineMessageSource>()
XmlMessage -> firstOrNull<XmlMessage>() XmlMessage -> firstIsInstanceOrNull<XmlMessage>()
JsonMessage -> firstOrNull<JsonMessage>() JsonMessage -> firstIsInstanceOrNull<JsonMessage>()
RichMessage -> firstOrNull<RichMessage>() RichMessage -> firstIsInstanceOrNull<RichMessage>()
LightApp -> firstOrNull<LightApp>() LightApp -> firstIsInstanceOrNull<LightApp>()
PokeMessage -> firstOrNull<PokeMessage>() PokeMessage -> firstIsInstanceOrNull<PokeMessage>()
HummerMessage -> firstOrNull<HummerMessage>() HummerMessage -> firstIsInstanceOrNull<HummerMessage>()
FlashImage -> firstOrNull<FlashImage>() FlashImage -> firstIsInstanceOrNull<FlashImage>()
GroupFlashImage -> firstOrNull<GroupFlashImage>() GroupFlashImage -> firstIsInstanceOrNull<GroupFlashImage>()
FriendFlashImage -> firstOrNull<FriendFlashImage>() FriendFlashImage -> firstIsInstanceOrNull<FriendFlashImage>()
else -> null else -> null
} as M? } as M?
...@@ -191,7 +197,8 @@ inline fun <M : Message> MessageChain.any(key: Message.Key<M>): Boolean = firstO ...@@ -191,7 +197,8 @@ inline fun <M : Message> MessageChain.any(key: Message.Key<M>): Boolean = firstO
* val image: Image by message * val image: Image by message
*/ */
@JvmSynthetic @JvmSynthetic
inline operator fun <reified T : Message> MessageChain.getValue(thisRef: Any?, property: KProperty<*>): T = this.first() inline operator fun <reified T : Message> MessageChain.getValue(thisRef: Any?, property: KProperty<*>): T =
this.firstIsInstance()
/** /**
* 可空的委托 * 可空的委托
...@@ -215,7 +222,8 @@ inline class OrNullDelegate<out R : Message?>(private val value: Any?) { ...@@ -215,7 +222,8 @@ inline class OrNullDelegate<out R : Message?>(private val value: Any?) {
* @see orElse 提供一个不存在则使用默认值的委托 * @see orElse 提供一个不存在则使用默认值的委托
*/ */
@JvmSynthetic @JvmSynthetic
inline fun <reified T : Message> MessageChain.orNull(): OrNullDelegate<T?> = OrNullDelegate(this.firstOrNull<T>()) inline fun <reified T : Message> MessageChain.orNull(): OrNullDelegate<T?> =
OrNullDelegate(this.firstIsInstanceOrNull<T>())
/** /**
* 提供一个类型的 [Message] 的委托, 若不存在这个类型的 [Message] 则委托会提供 `null` * 提供一个类型的 [Message] 的委托, 若不存在这个类型的 [Message] 则委托会提供 `null`
...@@ -234,7 +242,7 @@ inline fun <reified T : Message> MessageChain.orNull(): OrNullDelegate<T?> = OrN ...@@ -234,7 +242,7 @@ inline fun <reified T : Message> MessageChain.orNull(): OrNullDelegate<T?> = OrN
inline fun <reified T : Message?> MessageChain.orElse( inline fun <reified T : Message?> MessageChain.orElse(
lazyDefault: () -> T lazyDefault: () -> T
): OrNullDelegate<T> = ): OrNullDelegate<T> =
OrNullDelegate<T>(this.firstOrNull<T>() ?: lazyDefault()) OrNullDelegate<T>(this.firstIsInstanceOrNull<T>() ?: lazyDefault())
// endregion delegate // endregion delegate
...@@ -250,6 +258,7 @@ inline fun <reified T : Message?> MessageChain.orElse( ...@@ -250,6 +258,7 @@ inline fun <reified T : Message?> MessageChain.orElse(
@JvmName("newChain") @JvmName("newChain")
@JsName("newChain") @JsName("newChain")
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@OptIn(MiraiInternalAPI::class)
fun Message.asMessageChain(): MessageChain = when (this) { fun Message.asMessageChain(): MessageChain = when (this) {
is MessageChain -> this is MessageChain -> this
is CombinedMessage -> (this as Iterable<Message>).asMessageChain() is CombinedMessage -> (this as Iterable<Message>).asMessageChain()
...@@ -281,6 +290,7 @@ fun Iterable<SingleMessage>.asMessageChain(): MessageChain = ...@@ -281,6 +290,7 @@ fun Iterable<SingleMessage>.asMessageChain(): MessageChain =
inline fun MessageChain.asMessageChain(): MessageChain = this // 避免套娃 inline fun MessageChain.asMessageChain(): MessageChain = this // 避免套娃
@JvmSynthetic @JvmSynthetic
@OptIn(MiraiInternalAPI::class)
fun CombinedMessage.asMessageChain(): MessageChain { fun CombinedMessage.asMessageChain(): MessageChain {
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
if (left is SingleMessage && this.tail is SingleMessage) { if (left is SingleMessage && this.tail is SingleMessage) {
...@@ -377,6 +387,7 @@ inline fun Sequence<SingleMessage>.flatten(): Sequence<SingleMessage> = this // ...@@ -377,6 +387,7 @@ inline fun Sequence<SingleMessage>.flatten(): Sequence<SingleMessage> = this //
* - 其他: 返回 `sequenceOf(this)` * - 其他: 返回 `sequenceOf(this)`
*/ */
fun Message.flatten(): Sequence<SingleMessage> { fun Message.flatten(): Sequence<SingleMessage> {
@OptIn(MiraiInternalAPI::class)
return when (this) { return when (this) {
is MessageChain -> this.asSequence() is MessageChain -> this.asSequence()
is CombinedMessage -> this.flatten() // already constrained single. is CombinedMessage -> this.flatten() // already constrained single.
...@@ -385,6 +396,7 @@ fun Message.flatten(): Sequence<SingleMessage> { ...@@ -385,6 +396,7 @@ fun Message.flatten(): Sequence<SingleMessage> {
} }
@JvmSynthetic // make Java user happier with less methods @JvmSynthetic // make Java user happier with less methods
@OptIn(MiraiInternalAPI::class)
fun CombinedMessage.flatten(): Sequence<SingleMessage> { fun CombinedMessage.flatten(): Sequence<SingleMessage> {
// already constrained single. // already constrained single.
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
...@@ -400,17 +412,12 @@ inline fun MessageChain.flatten(): Sequence<SingleMessage> = this.asSequence() / ...@@ -400,17 +412,12 @@ inline fun MessageChain.flatten(): Sequence<SingleMessage> = this.asSequence() /
/** /**
* 不含任何元素的 [MessageChain] * 不含任何元素的 [MessageChain]
*/ */
object EmptyMessageChain : MessageChain, Iterator<SingleMessage>, @MiraiExperimentalAPI SingleMessage { object EmptyMessageChain : MessageChain, Iterator<SingleMessage> {
override fun contains(sub: String): Boolean = sub.isEmpty() override fun contains(sub: String): Boolean = sub.isEmpty()
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 val length: Int get() = 0
override fun get(index: Int): Char = ""[index]
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = "".subSequence(startIndex, endIndex)
override fun compareTo(other: String): Int = "".compareTo(other)
override fun iterator(): Iterator<SingleMessage> = this override fun iterator(): Iterator<SingleMessage> = this
override fun hasNext(): Boolean = false override fun hasNext(): Boolean = false
override fun next(): SingleMessage = throw NoSuchElementException("EmptyMessageChain is empty.") override fun next(): SingleMessage = throw NoSuchElementException("EmptyMessageChain is empty.")
...@@ -495,20 +502,21 @@ internal inline fun <T> List<T>.indexOfFirst(offset: Int, predicate: (T) -> Bool ...@@ -495,20 +502,21 @@ internal inline fun <T> List<T>.indexOfFirst(offset: Int, predicate: (T) -> Bool
*/ */
@PublishedApi @PublishedApi
internal class MessageChainImplByCollection constructor( internal class MessageChainImplByCollection constructor(
private val delegate: Collection<SingleMessage> // 必须 constrainSingleMessages, 且为 immutable internal val delegate: Collection<SingleMessage> // 必须 constrainSingleMessages, 且为 immutable
) : Message, Iterable<SingleMessage>, MessageChain { ) : Message, Iterable<SingleMessage>, MessageChain {
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()
private var toStringTemp: String? = null private var toStringTemp: String? = null
override fun toString(): String = get() = field ?: this.delegate.joinToString("") { it.toString() }.also { field = it }
toStringTemp ?: this.delegate.joinToString("") { it.toString() }.also { toStringTemp = it }
private var contentToStringTemp: String? = null override fun toString(): String = toStringTemp!!
override fun contentToString(): String =
contentToStringTemp ?: this.delegate.joinToString("") { it.contentToString() }.also { contentToStringTemp = it }
private var contentToStringTemp: String? = null
get() = field ?: this.delegate.joinToString("") { it.contentToString() }.also { field = it }
override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } override fun contentToString(): String = contentToStringTemp!!
override operator fun contains(sub: String): Boolean = sub in contentToStringTemp!!
} }
/** /**
...@@ -525,17 +533,17 @@ internal class MessageChainImplBySequence constructor( ...@@ -525,17 +533,17 @@ internal class MessageChainImplBySequence constructor(
*/ */
private val collected: List<SingleMessage> by lazy { delegate.constrainSingleMessages() } private val collected: List<SingleMessage> by lazy { delegate.constrainSingleMessages() }
override fun iterator(): Iterator<SingleMessage> = collected.iterator() override fun iterator(): Iterator<SingleMessage> = collected.iterator()
private var toStringTemp: String? = null private var toStringTemp: String? = null
override fun toString(): String = get() = field ?: this.joinToString("") { it.toString() }.also { field = it }
toStringTemp ?: this.collected.joinToString("") { it.toString() }.also { toStringTemp = it }
private var contentToStringTemp: String? = null override fun toString(): String = toStringTemp!!
override fun contentToString(): String =
contentToStringTemp ?: this.collected.joinToString("") { it.contentToString() }
.also { contentToStringTemp = it }
private var contentToStringTemp: String? = null
get() = field ?: this.joinToString("") { it.contentToString() }.also { field = it }
override operator fun contains(sub: String): Boolean = collected.any { it.contains(sub) } override fun contentToString(): String = contentToStringTemp!!
override operator fun contains(sub: String): Boolean = sub in contentToStringTemp!!
} }
/** /**
...@@ -543,7 +551,7 @@ internal class MessageChainImplBySequence constructor( ...@@ -543,7 +551,7 @@ internal class MessageChainImplBySequence constructor(
*/ */
@PublishedApi @PublishedApi
internal class SingleMessageChainImpl constructor( internal class SingleMessageChainImpl constructor(
private val delegate: SingleMessage internal val delegate: SingleMessage
) : Message, Iterable<SingleMessage>, MessageChain { ) : Message, Iterable<SingleMessage>, MessageChain {
override val size: Int get() = 1 override val size: Int get() = 1
override fun toString(): String = this.delegate.toString() override fun toString(): String = this.delegate.toString()
......
...@@ -30,7 +30,6 @@ class PlainText(val stringValue: String) : ...@@ -30,7 +30,6 @@ class PlainText(val stringValue: String) :
@Suppress("unused") @Suppress("unused")
constructor(charSequence: CharSequence) : this(charSequence.toString()) constructor(charSequence: CharSequence) : this(charSequence.toString())
override operator fun contains(sub: String): Boolean = sub in stringValue
override fun toString(): String = stringValue override fun toString(): String = stringValue
override fun contentToString(): String = stringValue override fun contentToString(): String = stringValue
......
...@@ -97,30 +97,6 @@ open class BotConfiguration { ...@@ -97,30 +97,6 @@ open class BotConfiguration {
fun fileBasedDeviceInfo(filename: String = "device.json") { fun fileBasedDeviceInfo(filename: String = "device.json") {
deviceInfo = getFileBasedDeviceInfoSupplier(filename) deviceInfo = getFileBasedDeviceInfoSupplier(filename)
} }
@PlannedRemoval("0.34.0")
@Deprecated(
"use fileBasedDeviceInfo(filepath", level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("fileBasedDeviceInfo")
)
operator fun FileBasedDeviceInfo.unaryPlus() {
fileBasedDeviceInfo(this.filepath)
}
}
/**
* 使用文件系统存储设备信息.
*/
@PlannedRemoval("0.34.0")
@Deprecated(
"use fileBasedDeviceInfo(filepath", level = DeprecationLevel.ERROR
)
inline class FileBasedDeviceInfo(val filepath: String) {
/**
* 使用 "device.json" 存储设备信息
*/
companion object ByDeviceDotJson
} }
@OptIn(ExperimentalMultiplatform::class) @OptIn(ExperimentalMultiplatform::class)
......
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
@OptIn(MiraiInternalAPI::class)
internal class CombinedMessageTest { internal class CombinedMessageTest {
@Test @Test
fun testAsSequence() { fun testAsSequence() {
var message: Message = "Hello ".toMessage() var message: Message = "Hello ".toMessage()
...@@ -32,84 +33,6 @@ internal class CombinedMessageTest { ...@@ -32,84 +33,6 @@ internal class CombinedMessageTest {
(message as CombinedMessage).asSequence().joinToString(separator = "") (message as CombinedMessage).asSequence().joinToString(separator = "")
) )
} }
private val toAdd = "1".toMessage()
@OptIn(ExperimentalTime::class)
@Test
fun speedTest() = repeat(100) {
var count = 1L
repeat(Int.MAX_VALUE) {
count++
}
var combineMessage: Message = toAdd
println(
"init combine ok " + measureTime {
repeat(1000) {
combineMessage += toAdd
}
}.inMilliseconds
)
val list = mutableListOf<Message>()
println(
"init messageChain ok " + measureTime {
repeat(1000) {
list += toAdd
}
}.inMilliseconds
)
measureTime {
list.joinToString(separator = "")
}.let { time ->
println("list foreach: ${time.inMilliseconds} ms")
}
measureTime {
(combineMessage as CombinedMessage).iterator().joinToString(separator = "")
}.let { time ->
println("combined iterate: ${time.inMilliseconds} ms")
}
measureTime {
(combineMessage as CombinedMessage).asSequence().joinToString(separator = "")
}.let { time ->
println("combined sequence: ${time.inMilliseconds} ms")
}
repeat(5) {
println()
}
}
@OptIn(ExperimentalTime::class)
@Test
fun testFastIteration() {
println("start!")
println("start!")
println("start!")
println("start!")
var combineMessage: Message = toAdd
println(
"init combine ok " + measureTime {
repeat(1000) {
combineMessage += toAdd
}
}.inMilliseconds
)
measureTime {
(combineMessage as CombinedMessage).iterator().joinToString(separator = "")
}.let { time ->
println("combine: ${time.inMilliseconds} ms")
}
}
} }
fun <T> Iterator<T>.joinToString( fun <T> Iterator<T>.joinToString(
...@@ -140,7 +63,7 @@ fun <T, A : Appendable> Iterator<T>.joinTo( ...@@ -140,7 +63,7 @@ fun <T, A : Appendable> Iterator<T>.joinTo(
buffer.appendElement(element, transform) buffer.appendElement(element, transform)
} else break } else break
} }
if (limit >= 0 && count > limit) buffer.append(truncated) if (limit in 0 until count) buffer.append(truncated)
buffer.append(postfix) buffer.append(postfix)
return buffer return buffer
} }
......
...@@ -10,9 +10,11 @@ ...@@ -10,9 +10,11 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertSame import kotlin.test.assertSame
import kotlin.test.assertTrue
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
...@@ -25,7 +27,7 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM ...@@ -25,7 +27,7 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM
override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>" override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>"
override fun contentToString(): String { override fun contentToString(): String {
TODO("Not yet implemented") return ""
} }
override val key: Message.Key<TestConstrainSingleMessage> override val key: Message.Key<TestConstrainSingleMessage>
...@@ -46,16 +48,75 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM ...@@ -46,16 +48,75 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM
} }
} }
@OptIn(MiraiExperimentalAPI::class)
internal class ConstrainSingleTest { internal class ConstrainSingleTest {
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiInternalAPI::class)
@Test
fun testCombine() {
val result = PlainText("te") + PlainText("st")
assertTrue(result is CombinedMessage)
assertEquals("te", result.left.contentToString())
assertEquals("st", result.tail.contentToString())
assertEquals(2, result.size)
assertEquals("test", result.contentToString())
}
@Test @Test
fun testConstrainSingleInPlus() { fun testSinglePlusChain() {
val result = PlainText("te") + buildMessageChain {
add(TestConstrainSingleMessage())
add("st")
}
assertTrue(result is MessageChainImplByCollection)
assertEquals(3, result.size)
assertEquals(result.contentToString(), "test")
}
@Test
fun testSinglePlusChainConstrain() {
val chain = buildMessageChain {
add(TestConstrainSingleMessage())
add("st")
}
val result = TestConstrainSingleMessage() + chain
assertSame(chain, result)
assertEquals(2, result.size)
assertEquals(result.contentToString(), "st")
assertTrue { result.first() is TestConstrainSingleMessage }
}
@Test
fun testSinglePlusSingle() {
val new = TestConstrainSingleMessage()
val combined = (TestConstrainSingleMessage() + new)
assertTrue(combined is SingleMessageChainImpl)
assertSame(new, combined.delegate)
}
@Test
fun testChainPlusSingle() {
val new = TestConstrainSingleMessage() val new = TestConstrainSingleMessage()
val combined = (TestConstrainSingleMessage() + new) as CombinedMessage
assertEquals(combined.left, EmptyMessageChain) val result = buildMessageChain {
assertSame(combined.tail, new) add(" ")
add(Face(Face.hao))
add(TestConstrainSingleMessage())
add(
PlainText("ss")
+ " "
)
} + buildMessageChain {
add(PlainText("p "))
add(new)
add(PlainText("test"))
}
assertEquals(7, result.size)
assertEquals(" [表情]ss p test", result.contentToString())
result as MessageChainImplByCollection
assertSame(new, result.delegate.toTypedArray()[2])
} }
@Test // net.mamoe.mirai/message/data/MessageChain.kt:441 @Test // net.mamoe.mirai/message/data/MessageChain.kt:441
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment