From 1a8172437ba894773f9ca12dd366739608bf4bf7 Mon Sep 17 00:00:00 2001 From: Karlatemp <karlatemp@vip.qq.com> Date: Sat, 16 Oct 2021 16:00:31 +0800 Subject: [PATCH 1/5] Redesign `MessageChain.cleanupRubbishMessageElements()` --- .../kotlin/message/ReceiveMessageHandler.kt | 120 +++++++++--------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt index 661a98dae44..20d3eac3cb6 100644 --- a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt +++ b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt @@ -218,76 +218,82 @@ internal object ReceiveMessageTransformer { } index++ } + + // delete empty plain text + removeAll { it is PlainText && it.content.isEmpty() } } fun MessageChain.cleanupRubbishMessageElements(): MessageChain { - var previousLast: SingleMessage? = null - var last: SingleMessage? = null - return buildMessageChain(initialSize = this.count()) { - this@cleanupRubbishMessageElements.forEach { element -> - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - if (last is LongMessageInternal && element is PlainText) { - if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) { - previousLast = last - last = element - return@forEach - } - } - if (last is PokeMessage && element is PlainText) { - if (element == UNSUPPORTED_POKE_MESSAGE_PLAIN) { - previousLast = last - last = element - return@forEach - } - } - if (last is VipFace && element is PlainText) { - val l = last as VipFace - if (element.content.length == 4 + (l.count / 10) + l.kind.name.length) { - previousLast = last - last = element - return@forEach - } - } - // 解决tim发送的语音无法正常识别 - if (element is PlainText) { - if (element == UNSUPPORTED_VOICE_MESSAGE_PLAIN) { - previousLast = last - last = element - return@forEach - } - } + val builder = MessageChainBuilder(initialSize = count()).also { + it.addAll(this) + } - if (element is PlainText && last is At && previousLast is QuoteReply - && element.content.startsWith(' ') - ) { - // Android QQ 发送, 是 Quote+At+PlainText(" xxx") // 首空格 - removeLastOrNull() // At - val new = PlainText(element.content.substring(1)) - add(new) - previousLast = null - last = new - return@forEach - } + kotlin.run moveQuoteReply@{ // Move QuoteReply after MessageSource + val exceptedQuoteReplyIndex = builder.indexOfFirst { it is MessageSource } + 1 + val quoteReplyIndex = builder.indexOfFirst { it is QuoteReply } + if (quoteReplyIndex < 1) return@moveQuoteReply + if (quoteReplyIndex != exceptedQuoteReplyIndex) { + val qr = builder[quoteReplyIndex] + builder.removeAt(quoteReplyIndex) + builder.add(exceptedQuoteReplyIndex, qr) + } + } - if (element is QuoteReply) { - // 客户端为兼容早期不支持 QuoteReply 的客户端而添加的 At - removeLastOrNull()?.let { rm -> - if ((rm as? PlainText)?.content != " ") add(rm) - else removeLastOrNull()?.let { rm2 -> - if (rm2 !is At) add(rm2) + kotlin.run quote@{ + val quoteReplyIndex = builder.indexOfFirst { it is QuoteReply } + if (quoteReplyIndex > 0) { + // QuoteReply + At + PlainText(space 1) + if (quoteReplyIndex < builder.size - 1) { + if (builder[quoteReplyIndex + 1] is At) { + builder.removeAt(quoteReplyIndex + 1) + } + if (quoteReplyIndex < builder.size - 1) { + val elm = builder[quoteReplyIndex + 1] + if (elm is PlainText && elm.content.startsWith(' ')) { + if (elm.content.length == 1) { + builder.removeAt(quoteReplyIndex + 1) + } else { + builder[quoteReplyIndex + 1] = PlainText(elm.content.substring(1)) + } } } + return@quote } + } + } - append(element) + // TIM audios + if (builder.any { it is Audio }) { + builder.remove(UNSUPPORTED_VOICE_MESSAGE_PLAIN) + } - previousLast = last - last = element + kotlin.run { // VipFace + val vipFaceIndex = builder.indexOfFirst { it is VipFace } + if (vipFaceIndex > 0 && vipFaceIndex < builder.size - 1) { + val l = builder[vipFaceIndex] as VipFace + val text = builder[vipFaceIndex + 1] + if (text is PlainText) { + if (text.content.length == 4 + (l.count / 10) + l.kind.name.length) { + builder.removeAt(vipFaceIndex + 1) + } + } } + } - // 处理分片信息 - compressContinuousPlainText() + fun removeSuffixText(index: Int, text: PlainText) { + if (index > 0 && index < builder.size - 1) { + if (builder[index + 1] == text) { + builder.removeAt(index + 1) + } + } } + + removeSuffixText(builder.indexOfFirst { it is LongMessageInternal }, UNSUPPORTED_MERGED_MESSAGE_PLAIN) + removeSuffixText(builder.indexOfFirst { it is PokeMessage }, UNSUPPORTED_POKE_MESSAGE_PLAIN) + + builder.compressContinuousPlainText() + + return builder.asMessageChain() } private fun decodeText(text: ImMsgBody.Text, list: MessageChainBuilder) { From 90b5e3156cd16d0257a3debfca72f1e81eed014a Mon Sep 17 00:00:00 2001 From: Karlatemp <karlatemp@vip.qq.com> Date: Sat, 16 Oct 2021 18:53:23 +0800 Subject: [PATCH 2/5] Fix logic --- .../src/commonMain/kotlin/message/ReceiveMessageHandler.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt index 20d3eac3cb6..7578b254c6c 100644 --- a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt +++ b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt @@ -241,7 +241,7 @@ internal object ReceiveMessageTransformer { kotlin.run quote@{ val quoteReplyIndex = builder.indexOfFirst { it is QuoteReply } - if (quoteReplyIndex > 0) { + if (quoteReplyIndex >= 0) { // QuoteReply + At + PlainText(space 1) if (quoteReplyIndex < builder.size - 1) { if (builder[quoteReplyIndex + 1] is At) { @@ -269,7 +269,7 @@ internal object ReceiveMessageTransformer { kotlin.run { // VipFace val vipFaceIndex = builder.indexOfFirst { it is VipFace } - if (vipFaceIndex > 0 && vipFaceIndex < builder.size - 1) { + if (vipFaceIndex >= 0 && vipFaceIndex < builder.size - 1) { val l = builder[vipFaceIndex] as VipFace val text = builder[vipFaceIndex + 1] if (text is PlainText) { @@ -281,7 +281,7 @@ internal object ReceiveMessageTransformer { } fun removeSuffixText(index: Int, text: PlainText) { - if (index > 0 && index < builder.size - 1) { + if (index >= 0 && index < builder.size - 1) { if (builder[index + 1] == text) { builder.removeAt(index + 1) } From fdac8a638cdecf49ba574011429cf9e969c45c17 Mon Sep 17 00:00:00 2001 From: Karlatemp <karlatemp@vip.qq.com> Date: Sat, 16 Oct 2021 18:54:07 +0800 Subject: [PATCH 3/5] `CleanupRubbishMessageElementsTest` --- .../CleanupRubbishMessageElementsTest.kt | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt diff --git a/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt b/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt new file mode 100644 index 00000000000..abfa0fb7087 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.message + +import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements +import net.mamoe.mirai.message.data.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import kotlin.test.assertEquals + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class CleanupRubbishMessageElementsTest { + //region + private val replySource = OfflineMessageSourceImplData( + kind = MessageSourceKind.GROUP, + ids = intArrayOf(1), + botId = 1, + time = 1, + fromId = 87, + targetId = 7454, + originalMessage = messageChainOf(), + internalIds = intArrayOf(8711) + ) + + private val source = OfflineMessageSourceImplData( + kind = MessageSourceKind.GROUP, + ids = intArrayOf(1), + botId = 1, + time = 1, + fromId = 2, + targetId = 3, + originalMessage = messageChainOf(), + internalIds = intArrayOf(9) + ) + //endregion + + private fun noMessageSource(c: MessageChain): MessageChain { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + return createMessageChainImplOptimized(c.filterNot { it is MessageSource }) + } + + private fun assertCleanup(excepted: MessageChain, source: MessageChain) { + assertEquals( + excepted, + source.cleanupRubbishMessageElements() + ) + assertEquals( + noMessageSource(excepted), + noMessageSource(source).cleanupRubbishMessageElements() + ) + } + + @Test + fun testCleanupNoSource() { + // Windows PC QQ + assertCleanup( + messageChainOf(QuoteReply(replySource), PlainText("Hello!")), + messageChainOf(At(123), PlainText(" "), QuoteReply(replySource), PlainText("Hello!")), + ) + + // QQ Android + assertCleanup( + messageChainOf(QuoteReply(replySource), PlainText("Hello!")), + messageChainOf(QuoteReply(replySource), At(1234567890), PlainText(" Hello!")), + ) + } + + @Test + fun testTIMAudio() { + val audio = OnlineAudioImpl("0", byteArrayOf(), 0, AudioCodec.SILK, "", 0, null) + assertCleanup( + messageChainOf(source, audio), + messageChainOf(source, audio, UNSUPPORTED_VOICE_MESSAGE_PLAIN), + ) + } + + @Test + fun testPokeMessageCleanup() { + val poke = PokeMessage("", 1, 1) + assertCleanup( + messageChainOf(source, poke), + messageChainOf(source, poke, UNSUPPORTED_POKE_MESSAGE_PLAIN), + ) + } + + @Test + fun testVipFaceCleanup() { + val vf = VipFace(VipFace.Kind(1, "Test!"), 50) + assertCleanup( + messageChainOf(source, vf), + messageChainOf(source, vf, PlainText("----CCCCCTest!")), + ) + } + + @Test + fun testLongMessageInternalCleanup() { + val li = LongMessageInternal("", "") + assertCleanup( + messageChainOf(source, li), + messageChainOf(source, li, UNSUPPORTED_MERGED_MESSAGE_PLAIN), + ) + } + + @Test + fun testCompressContinuousPlainText() { + assertCleanup( + messageChainOf(PlainText("1234567890")), + "12 3 45 6 789 0".split(" ").map(::PlainText).toMessageChain(), + ) + } + + @Test + fun testEmptyPlainTextRemoved() { + assertCleanup( + messageChainOf(), + " ".split(" ").map(::PlainText).toMessageChain(), + ) + } +} \ No newline at end of file From 14309dec74dedfe7044915ed8783f6907b98c5a2 Mon Sep 17 00:00:00 2001 From: Karlatemp <karlatemp@vip.qq.com> Date: Sat, 16 Oct 2021 18:58:11 +0800 Subject: [PATCH 4/5] Fix testing unit --- .../message/CleanupRubbishMessageElementsTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt b/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt index abfa0fb7087..cf0c2ffb606 100644 --- a/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt @@ -58,17 +58,17 @@ internal class CleanupRubbishMessageElementsTest { } @Test - fun testCleanupNoSource() { + fun testQuoteAtSpace() { // Windows PC QQ assertCleanup( - messageChainOf(QuoteReply(replySource), PlainText("Hello!")), - messageChainOf(At(123), PlainText(" "), QuoteReply(replySource), PlainText("Hello!")), + messageChainOf(source, QuoteReply(replySource), PlainText("Hello!")), + messageChainOf(source, At(123), PlainText(" "), QuoteReply(replySource), PlainText("Hello!")), ) // QQ Android assertCleanup( - messageChainOf(QuoteReply(replySource), PlainText("Hello!")), - messageChainOf(QuoteReply(replySource), At(1234567890), PlainText(" Hello!")), + messageChainOf(source, QuoteReply(replySource), PlainText("Hello!")), + messageChainOf(source, QuoteReply(replySource), At(1234567890), PlainText(" Hello!")), ) } From 7c24d022c83d596488374cac48595b29eca544fa Mon Sep 17 00:00:00 2001 From: Karlatemp <karlatemp@vip.qq.com> Date: Sun, 17 Oct 2021 12:49:54 +0800 Subject: [PATCH 5/5] more testing --- .../CleanupRubbishMessageElementsTest.kt | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt b/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt index cf0c2ffb606..14a358075f7 100644 --- a/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt @@ -41,11 +41,6 @@ internal class CleanupRubbishMessageElementsTest { ) //endregion - private fun noMessageSource(c: MessageChain): MessageChain { - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - return createMessageChainImplOptimized(c.filterNot { it is MessageSource }) - } - private fun assertCleanup(excepted: MessageChain, source: MessageChain) { assertEquals( excepted, @@ -114,6 +109,10 @@ internal class CleanupRubbishMessageElementsTest { messageChainOf(PlainText("1234567890")), "12 3 45 6 789 0".split(" ").map(::PlainText).toMessageChain(), ) + assertCleanup( + msg(source, At(123456), "Hello! How are you?"), + msg(source, At(123456), "Hello", "!", " ", "How", " ", "are ", "you?"), + ) } @Test @@ -122,5 +121,36 @@ internal class CleanupRubbishMessageElementsTest { messageChainOf(), " ".split(" ").map(::PlainText).toMessageChain(), ) + assertCleanup( + msg(AtAll), + msg("", AtAll, "", "", ""), + ) } + + @Test + fun testBlankPlainTextLiving() { + assertCleanup( + msg(" "), + msg("", " ", " ", " "), + ) + } + + //region + + private fun msg(vararg msgs: Any?): MessageChain { + return msgs.map { elm -> + when (elm) { + is Message -> elm + is String -> PlainText(elm) + else -> PlainText(elm.toString()) + } + }.toMessageChain() + } + + private fun noMessageSource(c: MessageChain): MessageChain { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + return createMessageChainImplOptimized(c.filterNot { it is MessageSource }) + } + + //endregion } \ No newline at end of file