mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-07 00:20:09 +08:00
Completed CombinedMessage redesigning and constraining on concatenation
This commit is contained in:
parent
eaa1e96ab5
commit
b8b749bf65
@ -7,6 +7,11 @@ plugins {
|
||||
|
||||
description = "Binary and source compatibility validator for mirai-core and mirai-core-qqandroid"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
@ -17,7 +22,19 @@ kotlin {
|
||||
main {
|
||||
dependencies {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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() {
|
||||
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
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())
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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"))
|
||||
}
|
||||
}
|
@ -56,6 +56,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.random.Random
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
internal fun Bot.asQQAndroidBot(): QQAndroidBot {
|
||||
@ -98,8 +99,9 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
|
||||
override val friends: ContactList<QQ> = ContactList(LockFreeLinkedList())
|
||||
|
||||
override lateinit var nick: String
|
||||
internal set
|
||||
override val nick: String get() = selfInfo.nick
|
||||
|
||||
internal lateinit var selfInfo: JceFriendInfo
|
||||
|
||||
override val selfQQ: QQ by lazy {
|
||||
@OptIn(LowLevelAPI::class)
|
||||
|
@ -51,7 +51,7 @@ import kotlin.jvm.JvmSynthetic
|
||||
internal inline class FriendInfoImpl(
|
||||
private val jceFriendInfo: net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo
|
||||
) : FriendInfo {
|
||||
override val nick: String get() = jceFriendInfo.nick ?: ""
|
||||
override val nick: String get() = jceFriendInfo.nick
|
||||
override val uin: Long get() = jceFriendInfo.friendUin
|
||||
}
|
||||
|
||||
|
@ -223,8 +223,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
}
|
||||
|
||||
// self info
|
||||
data.selfInfo?.apply {
|
||||
bot.nick = nick ?: ""
|
||||
data.selfInfo?.run {
|
||||
bot.selfInfo = this
|
||||
// bot.remark = remark ?: ""
|
||||
// bot.sex = sex
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ internal class FriendInfo(
|
||||
@JceId(11) val sqqOnLineStateV2: Byte? = null,
|
||||
@JceId(12) val sShowName: String? = "",
|
||||
@JceId(13) val isRemark: Byte? = null,
|
||||
@JceId(14) val nick: String? = "",
|
||||
@JceId(14) val nick: String = "",
|
||||
@JceId(15) val specialFlag: Byte? = null,
|
||||
@JceId(16) val vecIMGroupID: ByteArray? = null,
|
||||
@JceId(17) val vecMSFGroupID: ByteArray? = null,
|
||||
|
@ -87,6 +87,7 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@SinceMirai("0.33.1")
|
||||
abstract val nick: String
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@ import net.mamoe.mirai.message.ContactMessage
|
||||
import net.mamoe.mirai.message.FriendMessage
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
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 kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.InvocationKind
|
||||
@ -700,7 +700,7 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
|
||||
@MessageDsl
|
||||
@SinceMirai("0.30.0")
|
||||
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]
|
||||
|
@ -357,8 +357,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContaining(
|
||||
): M {
|
||||
return subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) {
|
||||
takeIf { this.isContextIdenticalWith(this@nextMessageContaining) }
|
||||
.takeIf { this.message.any<M>() }
|
||||
}.message.first()
|
||||
.takeIf { this.message.anyIsInstance<M>() }
|
||||
}.message.firstIsInstance()
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
@ -370,8 +370,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingAsync(
|
||||
@Suppress("RemoveExplicitTypeArguments")
|
||||
subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) {
|
||||
takeIf { this.isContextIdenticalWith(this@nextMessageContainingAsync) }
|
||||
.takeIf { this.message.any<M>() }
|
||||
}.message.first<M>()
|
||||
.takeIf { this.message.anyIsInstance<M>() }
|
||||
}.message.firstIsInstance<M>()
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,8 +391,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContainingOrN
|
||||
): M? {
|
||||
return subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
|
||||
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) }
|
||||
.takeIf { this.message.any<M>() }
|
||||
}?.message?.first()
|
||||
.takeIf { this.message.anyIsInstance<M>() }
|
||||
}?.message?.firstIsInstance()
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
@ -403,8 +403,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNullAsync
|
||||
return this.bot.async(coroutineContext) {
|
||||
subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
|
||||
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNullAsync) }
|
||||
.takeIf { this.message.any<M>() }
|
||||
}?.message?.first<M>()
|
||||
.takeIf { this.message.anyIsInstance<M>() }
|
||||
}?.message?.firstIsInstance<M>()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ package net.mamoe.mirai.message.data
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.nameCardOrNick
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmStatic
|
||||
@ -56,6 +57,7 @@ private constructor(val target: Long, val display: String) :
|
||||
}
|
||||
|
||||
// 自动为消息补充 " "
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@JvmName("followedBy")
|
||||
@ -67,7 +69,7 @@ private constructor(val target: Long, val display: String) :
|
||||
return followedByInternalForBinaryCompatibility(PlainText(" ") + tail)
|
||||
}
|
||||
|
||||
override fun followedBy(tail: Message): Message {
|
||||
override fun followedBy(tail: Message): MessageChain {
|
||||
if (tail is PlainText && tail.stringValue.startsWith(' ')) {
|
||||
return super.followedBy(tail)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
@ -42,6 +43,7 @@ object AtAll :
|
||||
override fun contentToString(): String = display
|
||||
|
||||
// 自动为消息补充 " "
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@JvmName("followedBy")
|
||||
@ -53,7 +55,7 @@ object AtAll :
|
||||
return followedByInternalForBinaryCompatibility(PlainText(" ") + tail)
|
||||
}
|
||||
|
||||
override fun followedBy(tail: Message): Message {
|
||||
override fun followedBy(tail: Message): MessageChain {
|
||||
if (tail is PlainText && tail.stringValue.startsWith(' ')) {
|
||||
return super.followedBy(tail)
|
||||
}
|
||||
|
@ -14,11 +14,13 @@ package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
* 链接的两个消息.
|
||||
* 快速链接的两个消息 (避免构造新的 list).
|
||||
*
|
||||
* 不要直接构造 [CombinedMessage], 使用 [Message.plus]
|
||||
* 要连接多个 [Message], 使用 [buildMessageChain]
|
||||
@ -27,64 +29,68 @@ import kotlin.jvm.JvmName
|
||||
*
|
||||
* Left-biased list
|
||||
*/
|
||||
@MiraiInternalAPI("this API is going to be internal")
|
||||
class CombinedMessage
|
||||
@Deprecated(message = "use Message.plus", level = DeprecationLevel.ERROR)
|
||||
@MiraiInternalAPI("CombinedMessage 构造器可能会在将来被改动") constructor(
|
||||
@MiraiExperimentalAPI("CombinedMessage.left 可能会在将来被改动")
|
||||
val left: SingleMessage,
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
internal constructor(
|
||||
internal val left: Message, // 必须已经完成 constrain single
|
||||
internal val tail: Message
|
||||
) : Message, MessageChain {
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
fun asSequence(): Sequence<SingleMessage> = sequence {
|
||||
yield(left)
|
||||
yield(tail)
|
||||
yieldCombinedOrElementsFlatten(this@CombinedMessage)
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<SingleMessage> {
|
||||
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)
|
||||
override fun toString(): String {
|
||||
return tail.toString() + left.toString()
|
||||
}
|
||||
|
||||
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
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
@ -24,16 +25,16 @@ import kotlin.jvm.JvmSynthetic
|
||||
* 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] 等.
|
||||
*
|
||||
* [消息][Message] 分为
|
||||
* - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource]
|
||||
* - [MessageContent] 单个消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] 等.
|
||||
* - [CombinedMessage] 通过 [plus] 连接的两个消息. 可通过 [asMessageChain] 转换为 [MessageChain]
|
||||
* - [MessageChain] 不可变消息链, 链表形式链接的多个 [SingleMessage] 实例.
|
||||
* - [SingleMessage]:
|
||||
* - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource], [引用回复][QuoteReply].
|
||||
* - [MessageContent] 含内容的消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] 等.
|
||||
* - [MessageChain]: 不可变消息链, 链表形式链接的多个 [SingleMessage] 实例.
|
||||
*
|
||||
* #### 在 Kotlin 使用 [Message]:
|
||||
* 这与使用 [String] 的使用非常类似.
|
||||
*
|
||||
* 比较 [Message] 与 [String] (使用 infix [Message.eq]):
|
||||
* `if(event eq "你好") qq.sendMessage(event)`
|
||||
* 比较 [SingleMessage] 与 [String]:
|
||||
* `if(message.contentToString() == "你好") qq.sendMessage(event)`
|
||||
*
|
||||
* 连接 [Message] 与 [Message], [String], (使用 operator [Message.plus]):
|
||||
* ```kotlin
|
||||
@ -65,6 +66,7 @@ import kotlin.jvm.JvmSynthetic
|
||||
*
|
||||
* @see Contact.sendMessage 发送消息
|
||||
*/
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
interface Message {
|
||||
/**
|
||||
* 类型 Key.
|
||||
@ -75,25 +77,16 @@ interface Message {
|
||||
*/
|
||||
interface Key<out M : Message> {
|
||||
/**
|
||||
* 此 [Key] 指代的 [Message] 类型名. 一般为 `class.simpleName`
|
||||
* 此 [Key] 指代的 [Message] 类型名. 一般为 `class.simpleName`, 如 "QuoteReply", "PlainText"
|
||||
*/
|
||||
@SinceMirai("0.34.0")
|
||||
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] 的头部. 类似于字符串相加.
|
||||
*
|
||||
* 连接后无法保证 [ConstrainSingle] 的元素单独存在. 需在
|
||||
* 连接后可以保证 [ConstrainSingle] 的元素单独存在.
|
||||
*
|
||||
* 例:
|
||||
* ```kotlin
|
||||
@ -111,17 +104,67 @@ interface Message {
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@JvmSynthetic // in java they should use `plus` instead
|
||||
fun followedBy(tail: Message): Message {
|
||||
TODO()
|
||||
if (this is SingleMessage && tail is SingleMessage) {
|
||||
if (this is ConstrainSingle<*> && tail is ConstrainSingle<*>) {
|
||||
return if (this.key == tail.key) {
|
||||
tail
|
||||
} else {
|
||||
CombinedMessage(this, tail)
|
||||
fun followedBy(tail: Message): MessageChain {
|
||||
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)) {
|
||||
val iterator = this.iterator()
|
||||
var tailUsed = false
|
||||
return MessageChainImplByCollection(
|
||||
constrainSingleMessagesImpl {
|
||||
if (iterator.hasNext()) {
|
||||
iterator.next()
|
||||
} else if (!tailUsed) {
|
||||
tailUsed = true
|
||||
tail
|
||||
} else null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
@SinceMirai("0.34.0")
|
||||
fun contentToString(): String
|
||||
|
||||
operator fun plus(another: Message): Message = this.followedBy(another)
|
||||
operator fun plus(another: Message): MessageChain = this.followedBy(another)
|
||||
|
||||
// avoid resolution ambiguity
|
||||
operator fun plus(another: SingleMessage): Message = this.followedBy(another)
|
||||
// don't remove! avoid resolution ambiguity between `CharSequence` and `Message`
|
||||
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)`
|
||||
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
|
||||
//////////////////////////////////////
|
||||
|
||||
@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")
|
||||
@JvmName("followedBy")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@JvmSynthetic
|
||||
fun followedBy1(tail: Message): CombinedMessage = this.followedByInternalForBinaryCompatibility(tail)
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@JvmName("plus")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@JvmSynthetic
|
||||
fun plus1(another: Message): CombinedMessage = this.followedByInternalForBinaryCompatibility(another)
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@JvmName("plus")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@JvmSynthetic
|
||||
fun plus1(another: SingleMessage): CombinedMessage = this.followedByInternalForBinaryCompatibility(another)
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@JvmName("plus")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@JvmSynthetic
|
||||
fun plus1(another: String): CombinedMessage = this.followedByInternalForBinaryCompatibility(another.toMessage())
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@JvmName("plus")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@ -192,24 +271,24 @@ interface Message {
|
||||
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)
|
||||
@JvmSynthetic
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
internal fun Message.followedByInternalForBinaryCompatibility(tail: Message): CombinedMessage {
|
||||
TODO()
|
||||
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)
|
||||
}
|
||||
return CombinedMessage(EmptyMessageChain, this.followedBy(tail))
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
@ -218,7 +297,8 @@ suspend inline fun <C : Contact> Message.sendTo(contact: C): 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<*>) {
|
||||
// fast-path
|
||||
return SingleMessageChainImpl(this)
|
||||
@ -231,7 +311,21 @@ fun Message.repeat(count: Int): MessageChain {
|
||||
@JvmSynthetic
|
||||
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 {
|
||||
*/
|
||||
@SinceMirai("0.34.0")
|
||||
@MiraiExperimentalAPI
|
||||
interface ConstrainSingle<M : Message> : SingleMessage {
|
||||
interface ConstrainSingle<M : Message> : MessageMetadata {
|
||||
val key: Message.Key<M>
|
||||
}
|
||||
|
||||
@ -263,6 +357,7 @@ interface MessageContent : SingleMessage
|
||||
/**
|
||||
* 将 [this] 发送给指定联系人
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
suspend inline fun <C : Contact> MessageChain.sendTo(contact: C): MessageReceipt<C> =
|
||||
contact.sendMessage(this) as MessageReceipt<C>
|
@ -40,10 +40,16 @@ import kotlin.reflect.KProperty
|
||||
* @see flatten 扁平化
|
||||
*/
|
||||
interface MessageChain : Message, Iterable<SingleMessage> {
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated(
|
||||
"有歧义, 自行使用 contentToString() 比较",
|
||||
ReplaceWith("this.contentToString() == other"),
|
||||
DeprecationLevel.HIDDEN
|
||||
)
|
||||
override operator fun contains(sub: String): Boolean
|
||||
|
||||
/**
|
||||
* 元素数量
|
||||
* 元素数量. [EmptyMessageChain] 不参加计数.
|
||||
*/
|
||||
@SinceMirai("0.31.1")
|
||||
val size: Int
|
||||
@ -104,20 +110,20 @@ inline fun MessageChain.foreachContent(block: (Message) -> Unit) {
|
||||
* 获取第一个 [M] 类型的 [Message] 实例
|
||||
*/
|
||||
@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] 实例
|
||||
* @throws [NoSuchElementException] 如果找不到该类型的实例
|
||||
*/
|
||||
@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] 实例
|
||||
*/
|
||||
@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
|
||||
@JvmSynthetic
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) {
|
||||
At -> firstOrNull<At>()
|
||||
AtAll -> firstOrNull<AtAll>()
|
||||
PlainText -> firstOrNull<PlainText>()
|
||||
Image -> firstOrNull<Image>()
|
||||
OnlineImage -> firstOrNull<OnlineImage>()
|
||||
OfflineImage -> firstOrNull<OfflineImage>()
|
||||
GroupImage -> firstOrNull<GroupImage>()
|
||||
FriendImage -> firstOrNull<FriendImage>()
|
||||
Face -> firstOrNull<Face>()
|
||||
QuoteReply -> firstOrNull<QuoteReply>()
|
||||
MessageSource -> firstOrNull<MessageSource>()
|
||||
OnlineMessageSource -> firstOrNull<OnlineMessageSource>()
|
||||
OfflineMessageSource -> firstOrNull<OfflineMessageSource>()
|
||||
OnlineMessageSource.Outgoing -> firstOrNull<OnlineMessageSource.Outgoing>()
|
||||
OnlineMessageSource.Outgoing.ToGroup -> firstOrNull<OnlineMessageSource.Outgoing.ToGroup>()
|
||||
OnlineMessageSource.Outgoing.ToFriend -> firstOrNull<OnlineMessageSource.Outgoing.ToFriend>()
|
||||
OnlineMessageSource.Incoming -> firstOrNull<OnlineMessageSource.Incoming>()
|
||||
OnlineMessageSource.Incoming.FromGroup -> firstOrNull<OnlineMessageSource.Incoming.FromGroup>()
|
||||
OnlineMessageSource.Incoming.FromFriend -> firstOrNull<OnlineMessageSource.Incoming.FromFriend>()
|
||||
OnlineMessageSource -> firstOrNull<OnlineMessageSource>()
|
||||
XmlMessage -> firstOrNull<XmlMessage>()
|
||||
JsonMessage -> firstOrNull<JsonMessage>()
|
||||
RichMessage -> firstOrNull<RichMessage>()
|
||||
LightApp -> firstOrNull<LightApp>()
|
||||
PokeMessage -> firstOrNull<PokeMessage>()
|
||||
HummerMessage -> firstOrNull<HummerMessage>()
|
||||
FlashImage -> firstOrNull<FlashImage>()
|
||||
GroupFlashImage -> firstOrNull<GroupFlashImage>()
|
||||
FriendFlashImage -> firstOrNull<FriendFlashImage>()
|
||||
At -> firstIsInstanceOrNull<At>()
|
||||
AtAll -> firstIsInstanceOrNull<AtAll>()
|
||||
PlainText -> firstIsInstanceOrNull<PlainText>()
|
||||
Image -> firstIsInstanceOrNull<Image>()
|
||||
OnlineImage -> firstIsInstanceOrNull<OnlineImage>()
|
||||
OfflineImage -> firstIsInstanceOrNull<OfflineImage>()
|
||||
GroupImage -> firstIsInstanceOrNull<GroupImage>()
|
||||
FriendImage -> firstIsInstanceOrNull<FriendImage>()
|
||||
Face -> firstIsInstanceOrNull<Face>()
|
||||
QuoteReply -> firstIsInstanceOrNull<QuoteReply>()
|
||||
MessageSource -> firstIsInstanceOrNull<MessageSource>()
|
||||
OnlineMessageSource -> firstIsInstanceOrNull<OnlineMessageSource>()
|
||||
OfflineMessageSource -> firstIsInstanceOrNull<OfflineMessageSource>()
|
||||
OnlineMessageSource.Outgoing -> firstIsInstanceOrNull<OnlineMessageSource.Outgoing>()
|
||||
OnlineMessageSource.Outgoing.ToGroup -> firstIsInstanceOrNull<OnlineMessageSource.Outgoing.ToGroup>()
|
||||
OnlineMessageSource.Outgoing.ToFriend -> firstIsInstanceOrNull<OnlineMessageSource.Outgoing.ToFriend>()
|
||||
OnlineMessageSource.Incoming -> firstIsInstanceOrNull<OnlineMessageSource.Incoming>()
|
||||
OnlineMessageSource.Incoming.FromGroup -> firstIsInstanceOrNull<OnlineMessageSource.Incoming.FromGroup>()
|
||||
OnlineMessageSource.Incoming.FromFriend -> firstIsInstanceOrNull<OnlineMessageSource.Incoming.FromFriend>()
|
||||
OnlineMessageSource -> firstIsInstanceOrNull<OnlineMessageSource>()
|
||||
XmlMessage -> firstIsInstanceOrNull<XmlMessage>()
|
||||
JsonMessage -> firstIsInstanceOrNull<JsonMessage>()
|
||||
RichMessage -> firstIsInstanceOrNull<RichMessage>()
|
||||
LightApp -> firstIsInstanceOrNull<LightApp>()
|
||||
PokeMessage -> firstIsInstanceOrNull<PokeMessage>()
|
||||
HummerMessage -> firstIsInstanceOrNull<HummerMessage>()
|
||||
FlashImage -> firstIsInstanceOrNull<FlashImage>()
|
||||
GroupFlashImage -> firstIsInstanceOrNull<GroupFlashImage>()
|
||||
FriendFlashImage -> firstIsInstanceOrNull<FriendFlashImage>()
|
||||
else -> null
|
||||
} as M?
|
||||
|
||||
@ -191,7 +197,8 @@ inline fun <M : Message> MessageChain.any(key: Message.Key<M>): Boolean = firstO
|
||||
* val image: Image by message
|
||||
*/
|
||||
@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?) {
|
||||
* @see orElse 提供一个不存在则使用默认值的委托
|
||||
*/
|
||||
@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`
|
||||
@ -234,7 +242,7 @@ inline fun <reified T : Message> MessageChain.orNull(): OrNullDelegate<T?> = OrN
|
||||
inline fun <reified T : Message?> MessageChain.orElse(
|
||||
lazyDefault: () -> T
|
||||
): OrNullDelegate<T> =
|
||||
OrNullDelegate<T>(this.firstOrNull<T>() ?: lazyDefault())
|
||||
OrNullDelegate<T>(this.firstIsInstanceOrNull<T>() ?: lazyDefault())
|
||||
|
||||
// endregion delegate
|
||||
|
||||
@ -250,6 +258,7 @@ inline fun <reified T : Message?> MessageChain.orElse(
|
||||
@JvmName("newChain")
|
||||
@JsName("newChain")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
fun Message.asMessageChain(): MessageChain = when (this) {
|
||||
is MessageChain -> this
|
||||
is CombinedMessage -> (this as Iterable<Message>).asMessageChain()
|
||||
@ -281,6 +290,7 @@ fun Iterable<SingleMessage>.asMessageChain(): MessageChain =
|
||||
inline fun MessageChain.asMessageChain(): MessageChain = this // 避免套娃
|
||||
|
||||
@JvmSynthetic
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
fun CombinedMessage.asMessageChain(): MessageChain {
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
if (left is SingleMessage && this.tail is SingleMessage) {
|
||||
@ -377,6 +387,7 @@ inline fun Sequence<SingleMessage>.flatten(): Sequence<SingleMessage> = this //
|
||||
* - 其他: 返回 `sequenceOf(this)`
|
||||
*/
|
||||
fun Message.flatten(): Sequence<SingleMessage> {
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
return when (this) {
|
||||
is MessageChain -> this.asSequence()
|
||||
is CombinedMessage -> this.flatten() // already constrained single.
|
||||
@ -385,6 +396,7 @@ fun Message.flatten(): Sequence<SingleMessage> {
|
||||
}
|
||||
|
||||
@JvmSynthetic // make Java user happier with less methods
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
fun CombinedMessage.flatten(): Sequence<SingleMessage> {
|
||||
// already constrained single.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -400,17 +412,12 @@ inline fun MessageChain.flatten(): Sequence<SingleMessage> = this.asSequence() /
|
||||
/**
|
||||
* 不含任何元素的 [MessageChain]
|
||||
*/
|
||||
object EmptyMessageChain : MessageChain, Iterator<SingleMessage>, @MiraiExperimentalAPI SingleMessage {
|
||||
object EmptyMessageChain : MessageChain, Iterator<SingleMessage> {
|
||||
override fun contains(sub: String): Boolean = sub.isEmpty()
|
||||
override val size: Int get() = 0
|
||||
override fun toString(): String = ""
|
||||
override fun contentToString(): String = ""
|
||||
|
||||
override 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 hasNext(): Boolean = false
|
||||
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
|
||||
*/
|
||||
@PublishedApi
|
||||
internal class MessageChainImplByCollection constructor(
|
||||
private val delegate: Collection<SingleMessage> // 必须 constrainSingleMessages, 且为 immutable
|
||||
internal val delegate: Collection<SingleMessage> // 必须 constrainSingleMessages, 且为 immutable
|
||||
) : Message, Iterable<SingleMessage>, MessageChain {
|
||||
override val size: Int get() = delegate.size
|
||||
override fun iterator(): Iterator<SingleMessage> = delegate.iterator()
|
||||
|
||||
private var toStringTemp: String? = null
|
||||
override fun toString(): String =
|
||||
toStringTemp ?: this.delegate.joinToString("") { it.toString() }.also { toStringTemp = it }
|
||||
get() = field ?: this.delegate.joinToString("") { it.toString() }.also { field = it }
|
||||
|
||||
override fun toString(): String = toStringTemp!!
|
||||
|
||||
private var contentToStringTemp: String? = null
|
||||
override fun contentToString(): String =
|
||||
contentToStringTemp ?: this.delegate.joinToString("") { it.contentToString() }.also { contentToStringTemp = it }
|
||||
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(
|
||||
*/
|
||||
private val collected: List<SingleMessage> by lazy { delegate.constrainSingleMessages() }
|
||||
override fun iterator(): Iterator<SingleMessage> = collected.iterator()
|
||||
|
||||
private var toStringTemp: String? = null
|
||||
override fun toString(): String =
|
||||
toStringTemp ?: this.collected.joinToString("") { it.toString() }.also { toStringTemp = it }
|
||||
get() = field ?: this.joinToString("") { it.toString() }.also { field = it }
|
||||
|
||||
override fun toString(): String = toStringTemp!!
|
||||
|
||||
private var contentToStringTemp: String? = null
|
||||
override fun contentToString(): String =
|
||||
contentToStringTemp ?: this.collected.joinToString("") { it.contentToString() }
|
||||
.also { contentToStringTemp = it }
|
||||
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(
|
||||
*/
|
||||
@PublishedApi
|
||||
internal class SingleMessageChainImpl constructor(
|
||||
private val delegate: SingleMessage
|
||||
internal val delegate: SingleMessage
|
||||
) : Message, Iterable<SingleMessage>, MessageChain {
|
||||
override val size: Int get() = 1
|
||||
override fun toString(): String = this.delegate.toString()
|
||||
|
@ -30,7 +30,6 @@ class PlainText(val stringValue: String) :
|
||||
@Suppress("unused")
|
||||
constructor(charSequence: CharSequence) : this(charSequence.toString())
|
||||
|
||||
override operator fun contains(sub: String): Boolean = sub in stringValue
|
||||
override fun toString(): String = stringValue
|
||||
override fun contentToString(): String = stringValue
|
||||
|
||||
|
@ -97,30 +97,6 @@ open class BotConfiguration {
|
||||
fun fileBasedDeviceInfo(filename: String = "device.json") {
|
||||
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)
|
||||
|
@ -1,12 +1,13 @@
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.measureTime
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
internal class CombinedMessageTest {
|
||||
|
||||
|
||||
@Test
|
||||
fun testAsSequence() {
|
||||
var message: Message = "Hello ".toMessage()
|
||||
@ -32,84 +33,6 @@ internal class CombinedMessageTest {
|
||||
(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(
|
||||
@ -140,7 +63,7 @@ fun <T, A : Appendable> Iterator<T>.joinTo(
|
||||
buffer.appendElement(element, transform)
|
||||
} else break
|
||||
}
|
||||
if (limit >= 0 && count > limit) buffer.append(truncated)
|
||||
if (limit in 0 until count) buffer.append(truncated)
|
||||
buffer.append(postfix)
|
||||
return buffer
|
||||
}
|
||||
|
@ -10,9 +10,11 @@
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertSame
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
@ -25,7 +27,7 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM
|
||||
override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>"
|
||||
|
||||
override fun contentToString(): String {
|
||||
TODO("Not yet implemented")
|
||||
return ""
|
||||
}
|
||||
|
||||
override val key: Message.Key<TestConstrainSingleMessage>
|
||||
@ -46,16 +48,75 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
internal class ConstrainSingleTest {
|
||||
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@Test
|
||||
fun testConstrainSingleInPlus() {
|
||||
val new = TestConstrainSingleMessage()
|
||||
val combined = (TestConstrainSingleMessage() + new) as CombinedMessage
|
||||
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())
|
||||
}
|
||||
|
||||
assertEquals(combined.left, EmptyMessageChain)
|
||||
assertSame(combined.tail, new)
|
||||
@Test
|
||||
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 result = buildMessageChain {
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user