Redesign MessageChain.cleanupRubbishMessageElements() (#1607)

* Redesign `MessageChain.cleanupRubbishMessageElements()`

* Fix logic

* `CleanupRubbishMessageElementsTest`

* Fix testing unit

* more testing
This commit is contained in:
Karlatemp 2021-10-28 20:43:41 +08:00 committed by GitHub
parent 3d502a496e
commit 527fe08446
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 222 additions and 60 deletions

View File

@ -218,77 +218,83 @@ 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
val builder = MessageChainBuilder(initialSize = count()).also {
it.addAll(this)
}
}
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
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 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 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 (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)
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)
previousLast = last
last = element
// TIM audios
if (builder.any { it is Audio }) {
builder.remove(UNSUPPORTED_VOICE_MESSAGE_PLAIN)
}
// 处理分片信息
compressContinuousPlainText()
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)
}
}
}
}
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) {
if (text.attr6Buf.isEmpty()) {

View File

@ -0,0 +1,156 @@
/*
* 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 assertCleanup(excepted: MessageChain, source: MessageChain) {
assertEquals(
excepted,
source.cleanupRubbishMessageElements()
)
assertEquals(
noMessageSource(excepted),
noMessageSource(source).cleanupRubbishMessageElements()
)
}
@Test
fun testQuoteAtSpace() {
// Windows PC QQ
assertCleanup(
messageChainOf(source, QuoteReply(replySource), PlainText("Hello!")),
messageChainOf(source, At(123), PlainText(" "), QuoteReply(replySource), PlainText("Hello!")),
)
// QQ Android
assertCleanup(
messageChainOf(source, QuoteReply(replySource), PlainText("Hello!")),
messageChainOf(source, 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(),
)
assertCleanup(
msg(source, At(123456), "Hello! How are you?"),
msg(source, At(123456), "Hello", "!", " ", "How", " ", "are ", "you?"),
)
}
@Test
fun testEmptyPlainTextRemoved() {
assertCleanup(
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
}