(
}
}
+internal class PipelineConfiguration(
+ var stopWhenConsumed: Boolean
+)
+
internal abstract class AbstractProcessorPipeline, C : ProcessorPipelineContext, D, R>
protected constructor(
+ val configuration: PipelineConfiguration,
val traceLogging: MiraiLogger,
) : ProcessorPipeline {
- constructor() : this(SilentLogger)
+ constructor(configuration: PipelineConfiguration) : this(configuration, SilentLogger)
/**
* Must be ordered
@@ -199,6 +208,12 @@ protected constructor(
}, success=${result.isSuccess}, consumed=${context.isConsumed}, diff=$diffPackets"
}
}
+
+ if (context.isConsumed && configuration.stopWhenConsumed) {
+ traceLogging.info { "stopWhenConsumed=true, stopped." }
+
+ break
+ }
}
return context.collected.data
}
diff --git a/mirai-core/src/commonMain/kotlin/utils/runCoroutineInPlace.kt b/mirai-core/src/commonMain/kotlin/utils/runCoroutineInPlace.kt
new file mode 100644
index 000000000..f1d29be52
--- /dev/null
+++ b/mirai-core/src/commonMain/kotlin/utils/runCoroutineInPlace.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.utils
+
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.runBlocking
+import net.mamoe.mirai.utils.MiraiLogger
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
+import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn
+
+/**
+ * Runs the [coroutine] directly in current thread, **expecting no suspension**.
+ */
+internal fun runCoroutineInPlace(coroutine: suspend () -> R): R {
+ var lateResult: CompletableDeferred? = null
+
+ val result = coroutine.startCoroutineUninterceptedOrReturn(Continuation(EmptyCoroutineContext) { r ->
+ val deferred: CompletableDeferred? = lateResult
+ @Suppress("KotlinConstantConditions")
+ if (deferred != null) {
+ r.fold(onSuccess = { deferred.complete(it) }, onFailure = { deferred.completeExceptionally(it) })
+ } else {
+ if (logger.isErrorEnabled) {
+ logger.error(IllegalStateException("runCoroutineInPlace reached an unexpected state: coroutine did not finish. ()"))
+ }
+ }
+ })
+
+ if (result != COROUTINE_SUSPENDED) {
+ @Suppress("UNCHECKED_CAST")
+ return result as R
+ }
+
+ lateResult = CompletableDeferred()
+
+ if (logger.isErrorEnabled) {
+ logger.error(IllegalStateException("runCoroutineInPlace reached an unexpected state: coroutine did not finish."))
+ }
+
+ return runBlocking { lateResult.await() }
+}
+
+private val myStubFailure = Exception()
+
+private class RunCoroutineInPlace
+
+private val logger by lazy {
+ MiraiLogger.Factory.create(RunCoroutineInPlace::class)
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.internal.message.protocol.MessageProtocol b/mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.internal.message.protocol.MessageProtocol
new file mode 100644
index 000000000..485655d1f
--- /dev/null
+++ b/mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.internal.message.protocol.MessageProtocol
@@ -0,0 +1,24 @@
+#
+# Copyright 2019-2022 Mamoe Technologies and contributors.
+#
+# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+#
+# https://github.com/mamoe/mirai/blob/dev/LICENSE
+#
+
+net.mamoe.mirai.internal.message.protocol.impl.CustomMessageProtocol
+net.mamoe.mirai.internal.message.protocol.impl.FaceProtocol
+net.mamoe.mirai.internal.message.protocol.impl.FileMessageProtocol
+net.mamoe.mirai.internal.message.protocol.impl.FlashImageProtocol
+net.mamoe.mirai.internal.message.protocol.impl.IgnoredMessagesProtocol
+net.mamoe.mirai.internal.message.protocol.impl.ImageProtocol
+net.mamoe.mirai.internal.message.protocol.impl.MarketFaceProtocol
+net.mamoe.mirai.internal.message.protocol.impl.MusicShareProtocol
+net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol
+net.mamoe.mirai.internal.message.protocol.impl.PttMessageProtocol
+net.mamoe.mirai.internal.message.protocol.impl.QuoteReplyProtocol
+net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol
+net.mamoe.mirai.internal.message.protocol.impl.TextProtocol
+net.mamoe.mirai.internal.message.protocol.impl.UnsupportedMessageProtocol
+net.mamoe.mirai.internal.message.protocol.impl.VipFaceProtocol
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt b/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt
index 794fd9c29..83e6947d8 100644
--- a/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt
+++ b/mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt
@@ -12,6 +12,8 @@ package net.mamoe.mirai.internal.message
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements
import net.mamoe.mirai.internal.message.data.LongMessageInternal
import net.mamoe.mirai.internal.message.data.OnlineAudioImpl
+import net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol.Companion.UNSUPPORTED_POKE_MESSAGE_PLAIN
+import net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol.Companion.UNSUPPORTED_MERGED_MESSAGE_PLAIN
import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData
import net.mamoe.mirai.message.data.*
import org.junit.jupiter.api.Test
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt
new file mode 100644
index 000000000..86ad0324b
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.message.protocol
+
+import net.mamoe.mirai.internal.test.AbstractTest
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+internal class MessageProtocolFacadeTest : AbstractTest() {
+
+
+ @Test
+ fun `can load`() {
+ assertEquals(
+ """
+ QuoteReplyProtocol
+ CustomMessageProtocol
+ FileMessageProtocol
+ FlashImageProtocol
+ FaceProtocol
+ ImageProtocol
+ MarketFaceProtocol
+ MusicShareProtocol
+ PokeMessageProtocol
+ IgnoredMessagesProtocol
+ PttMessageProtocol
+ RichMessageProtocol
+ TextProtocol
+ UnsupportedMessageProtocol
+ VipFaceProtocol
+ """.trimIndent(),
+ MessageProtocolFacadeImpl().loaded.joinToString("\n") { it::class.simpleName.toString() }
+ )
+ }
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt
index a650be54b..c3332d283 100644
--- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt
@@ -9,16 +9,29 @@
package net.mamoe.mirai.internal.message.protocol.impl
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import net.mamoe.mirai.contact.ContactOrBot
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.internal.contact.inferMessageSourceKind
import net.mamoe.mirai.internal.message.protocol.*
import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
-import net.mamoe.mirai.internal.utils.structureToString
+import net.mamoe.mirai.internal.notice.processors.GroupExtensions
import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.message.data.MessageChainBuilder
+import net.mamoe.mirai.message.data.MessageSourceKind
+import net.mamoe.mirai.message.data.SingleMessage
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
-import kotlin.test.asserter
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
-internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest() {
+internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest(), GroupExtensions {
+
+ protected abstract val protocol: MessageProtocol
+ protected var defaultTarget: ContactOrBot? = null
private var decoderLoggerEnabled = false
private var encoderLoggerEnabled = false
@@ -50,45 +63,39 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
protocol: MessageProtocol,
encode: MessageProtocolFacade.() -> List
) {
- assertEquals(
+ asserter.assertEquals(
expectedStruct,
facadeOf(protocol).encode(),
message = "Failed to check single Protocol"
)
- assertEquals(
+ asserter.assertEquals(
expectedStruct,
MessageProtocolFacade.INSTANCE.encode(),
message = "Failed to check with all protocols"
)
}
- @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
- private fun <@kotlin.internal.OnlyInputTypes T> assertEquals(
- expected: List,
- actual: List,
- message: String? = null
- ) {
- if (expected.size == 1 && actual.size == 1) {
- asserter.assertEquals(message, expected.single().structureToString(), actual.single().structureToString())
- } else {
- asserter.assertEquals(
- message,
- expected.joinToString { it.structureToString() },
- actual.joinToString { it.structureToString() })
- }
+ var asserter: EqualityAsserter = EqualityAsserter.OrdinaryThenStructural
+
+ fun useOrdinaryEquality() {
+ asserter = EqualityAsserter.Ordinary
+ }
+
+ fun useStructuralEquality() {
+ asserter = EqualityAsserter.Structural
}
protected fun doDecoderChecks(
expectedChain: MessageChain,
- protocol: MessageProtocol,
+ protocol: MessageProtocol = this.protocol,
decode: MessageProtocolFacade.() -> MessageChain
) {
- assertEquals(
+ asserter.assertEquals(
expectedChain.toList(),
facadeOf(protocol).decode().toList(),
message = "Failed to check single Protocol"
)
- assertEquals(
+ asserter.assertEquals(
expectedChain.toList(),
MessageProtocolFacade.INSTANCE.decode().toList(),
message = "Failed to check with all protocols"
@@ -96,8 +103,98 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
}
protected fun doEncoderChecks(
- expectedStruct: ImMsgBody.Elem,
- protocol: MessageProtocol,
+ vararg expectedStruct: ImMsgBody.Elem,
+ protocol: MessageProtocol = this.protocol,
encode: MessageProtocolFacade.() -> List
- ): Unit = doEncoderChecks(mutableListOf(expectedStruct), protocol, encode)
+ ): Unit = doEncoderChecks(expectedStruct.toList(), protocol, encode)
+
+
+ inner class ChecksBuilder {
+ var elems: MutableList = mutableListOf()
+ var messages: MessageChainBuilder = MessageChainBuilder()
+
+ var groupIdOrZero: Long = 0
+ var messageSourceKind: MessageSourceKind = MessageSourceKind.GROUP
+ var target: ContactOrBot? = defaultTarget
+ var withGeneralFlags = true
+ var isForward = false
+
+ fun elem(vararg elem: ImMsgBody.Elem) {
+ elems.addAll(elem)
+ }
+
+ fun message(vararg message: SingleMessage) {
+ messages.addAll(message)
+ }
+
+ fun target(target: ContactOrBot?) {
+ this.target = target
+
+ if (target != null) {
+ messageSourceKind = target.inferMessageSourceKind()
+ }
+
+ if (target is Group) {
+ groupIdOrZero = target.id
+ }
+ }
+
+ fun forward() {
+ this.isForward = true
+ }
+
+ fun build() = ChecksConfiguration(
+ elems.toList(),
+ messages.build(),
+ groupIdOrZero,
+ messageSourceKind,
+ target,
+ withGeneralFlags,
+ isForward
+ )
+ }
+
+ class ChecksConfiguration(
+ val elems: List,
+ val messageChain: MessageChain,
+ val groupIdOrZero: Long,
+ val messageSourceKind: MessageSourceKind,
+ val target: ContactOrBot?,
+ val withGeneralFlags: Boolean,
+ val isForward: Boolean,
+ )
+
+ @Suppress("DeferredIsResult")
+ protected fun buildChecks(
+ builderAction: ChecksBuilder.() -> Unit,
+ ): Deferred { // IDE will warn you if you forget to call .do
+ contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+ return CompletableDeferred(ChecksBuilder().apply(builderAction).build())
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ protected open fun Deferred.doEncoderChecks() {
+ val config = this.getCompleted()
+ doEncoderChecks(config.elems, protocol) {
+ encode(
+ config.messageChain,
+ config.target,
+ withGeneralFlags = config.withGeneralFlags,
+ isForward = config.isForward
+ )
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ protected open fun Deferred.doDecoderChecks() {
+ val config = this.getCompleted()
+ doDecoderChecks(config.messageChain, protocol) {
+ decode(config.elems, config.groupIdOrZero, config.messageSourceKind, bot)
+ }
+ }
+
+ protected open fun Deferred.doBothChecks() {
+ doEncoderChecks()
+ doDecoderChecks()
+ }
}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/EqualityAsserter.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/EqualityAsserter.kt
new file mode 100644
index 000000000..119af8934
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/EqualityAsserter.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.message.protocol.impl
+
+import net.mamoe.mirai.internal.utils.structureToString
+import net.mamoe.mirai.internal.utils.structureToStringIfAvailable
+import kotlin.test.assertNotNull
+import kotlin.test.asserter
+
+internal interface EqualityAsserter {
+ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+ fun <@kotlin.internal.OnlyInputTypes T> assertEquals(
+ expected: List,
+ actual: List,
+ message: String? = null
+ )
+
+ object Ordinary : EqualityAsserter {
+ override fun assertEquals(expected: List, actual: List, message: String?) {
+ if (expected.size == actual.size) {
+ if (expected.size == 1 && expected.singleOrNull() == actual.singleOrNull()) {
+ return asserter.assertEquals(message, expected.single(), actual.single())
+ }
+
+ if (expected.zip(actual).all { (e, a) -> e == a }) return
+
+ asserter.assertEquals(message, expected, actual)
+ } else {
+ asserter.assertEquals(message, expected, actual)
+ }
+ }
+ }
+
+ object Structural : EqualityAsserter {
+ override fun assertEquals(expected: List, actual: List, message: String?) {
+ if (expected.size == 1 && actual.size == 1) {
+ val e = expected.single()
+ val a = actual.single()
+ if (a == null || e == null) {
+ asserter.assertEquals(
+ "[Null] $message",
+ structureToStringOrOrdinaryString(e),
+ structureToStringOrOrdinaryString(a)
+ )
+ assertNotNull(a, message)
+ assertNotNull(e, message)
+ }
+ @Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
+ if (!e!!::class.isInstance(a) && !a!!::class.isInstance(e)) {
+ asserter.assertEquals(
+ "[Incompatible type] $message",
+ structureToStringOrOrdinaryString(e),
+ structureToStringOrOrdinaryString(a)
+ )
+ return
+ }
+ asserter.assertEquals(
+ message,
+ structureToStringOrOrdinaryString(e),
+ structureToStringOrOrdinaryString(a)
+ )
+ } else {
+ asserter.assertEquals(
+ message,
+ expected.joinToString { structureToStringOrOrdinaryString(it) },
+ actual.joinToString { structureToStringOrOrdinaryString(it) })
+ }
+ }
+
+ private fun structureToStringOrOrdinaryString(it: T): String =
+ it.structureToString().ifBlank {
+ it.structureToStringIfAvailable() ?: error("structureToString is not available")
+ }
+ }
+
+ object OrdinaryThenStructural : EqualityAsserter {
+ override fun assertEquals(expected: List, actual: List, message: String?) {
+ try {
+ Ordinary.assertEquals(expected, actual, message)
+ return
+ } catch (e: AssertionError) {
+ Structural.assertEquals(expected, actual, message)
+ return
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/FaceProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/FaceProtocolTest.kt
index a3be0664a..bbae19c54 100644
--- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/FaceProtocolTest.kt
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/FaceProtocolTest.kt
@@ -9,6 +9,7 @@
package net.mamoe.mirai.internal.message.protocol.impl
+import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.message.data.Face
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.messageChainOf
@@ -16,6 +17,7 @@ import net.mamoe.mirai.utils.hexToBytes
import org.junit.jupiter.api.Test
internal class FaceProtocolTest : AbstractMessageProtocolTest() {
+ override val protocol: MessageProtocol = FaceProtocol()
@Test
fun `can encode`() {
@@ -27,7 +29,6 @@ internal class FaceProtocolTest : AbstractMessageProtocolTest() {
buf = "00 01 00 04 52 CC F5 D0".hexToBytes(),
),
),
- FaceProtocol()
) {
encode(
messageChainOf(Face(Face.PIE_ZUI)),
@@ -40,7 +41,6 @@ internal class FaceProtocolTest : AbstractMessageProtocolTest() {
fun `can decode`() {
doDecoderChecks(
messageChainOf(Face(Face.YIN_XIAN)),
- FaceProtocol()
) {
decode(
listOf(
@@ -58,4 +58,5 @@ internal class FaceProtocolTest : AbstractMessageProtocolTest() {
}
}
+
}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/TextProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/TextProtocolTest.kt
new file mode 100644
index 000000000..2184b95d5
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/TextProtocolTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.message.protocol.impl
+
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.message.data.At
+import net.mamoe.mirai.message.data.AtAll
+import net.mamoe.mirai.message.data.PlainText
+import net.mamoe.mirai.utils.hexToBytes
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+internal class TextProtocolTest : AbstractMessageProtocolTest() {
+
+ override val protocol = TextProtocol()
+
+ @BeforeEach
+ fun `init group`() {
+ defaultTarget = bot.addGroup(123, 1230003).apply {
+ addMember(1230003, "user3", MemberPermission.OWNER)
+ }
+ }
+
+ @Test
+ fun `test PlainText`() {
+ buildChecks {
+ elem(
+ net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+ text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+ str = "hello",
+ ),
+ )
+ )
+ message(PlainText("hello"))
+ }.doBothChecks()
+ }
+
+ @Test
+ fun `test AtAll`() {
+ buildChecks {
+ elem(
+ net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+ text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+ str = "@全体成员",
+ attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes(),
+ ),
+ )
+ )
+ message(AtAll)
+ }.doBothChecks()
+ }
+
+ @Test
+ fun `AtAll auto append spaces`() {
+ buildChecks {
+ elem(
+ net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+ text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+ str = "@全体成员",
+ attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes(),
+ ),
+ ),
+ net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+ text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+ str = "Hi",
+ ),
+ ),
+ )
+ message(AtAll, PlainText("Hi"))
+ }.doEncoderChecks()
+ }
+
+ @Test
+ fun `test At`() {
+ buildChecks {
+ elem(
+ net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+ text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+ str = "@user3",
+ attr6Buf = "00 01 00 00 00 06 00 00 12 C4 B3 00 00".hexToBytes(),
+ ),
+ )
+ )
+ message(At(1230003))
+ }.doBothChecks()
+ }
+
+ @Test
+ fun `At auto append spaces`() {
+ buildChecks {
+ elem(
+ net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+ text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+ str = "@user3",
+ attr6Buf = "00 01 00 00 00 06 00 00 12 C4 B3 00 00".hexToBytes(),
+ ),
+ ),
+ net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
+ text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
+ str = " ",
+ ),
+ ),
+ )
+ message(At(1230003))
+ message(PlainText(" "))
+ target(bot.addGroup(123, 1230003).apply {
+ addMember(1230003, "user3", MemberPermission.OWNER)
+ })
+ }.doBothChecks()
+ }
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/testFramework/message/protocol/MessageDecodingRecorder.kt b/mirai-core/src/commonTest/kotlin/testFramework/message/protocol/MessageDecodingRecorder.kt
new file mode 100644
index 000000000..f620950de
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/testFramework/message/protocol/MessageDecodingRecorder.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.testFramework.message.protocol
+
+import net.mamoe.mirai.internal.message.protocol.MessageDecoder
+import net.mamoe.mirai.internal.message.protocol.MessageDecoderContext
+import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
+import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer
+import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer.Companion.generateAndDesensitize
+import net.mamoe.mirai.utils.MiraiLogger
+import net.mamoe.mirai.utils.debug
+
+internal class MessageDecodingRecorder(
+ private val logger: MiraiLogger = MiraiLogger.Factory.create(MessageDecodingRecorder::class)
+) : MessageDecoder {
+ override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
+ logger.debug {
+ "\n" + ValueDescAnalyzer.generateAndDesensitize(data)
+ }
+ }
+}
\ No newline at end of file
diff --git a/mirai-core/src/jvmTest/kotlin/bootstrap/RunMessageDecodingRecorder.kt b/mirai-core/src/jvmTest/kotlin/bootstrap/RunMessageDecodingRecorder.kt
new file mode 100644
index 000000000..6be0c6e54
--- /dev/null
+++ b/mirai-core/src/jvmTest/kotlin/bootstrap/RunMessageDecodingRecorder.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.bootstrap
+
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.BotFactory
+import net.mamoe.mirai.internal.asQQAndroidBot
+import net.mamoe.mirai.internal.message.protocol.MessageDecoderProcessor
+import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
+import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer
+import net.mamoe.mirai.internal.testFramework.message.protocol.MessageDecodingRecorder
+import net.mamoe.mirai.utils.BotConfiguration
+import net.mamoe.mirai.utils.readResource
+import net.mamoe.yamlkt.Yaml
+import kotlin.concurrent.thread
+
+suspend fun main() {
+ Runtime.getRuntime().addShutdownHook(thread(start = false) {
+ Bot.instances.forEach {
+ it.close()
+ }
+ })
+
+
+ Desensitizer.local.desensitize("") // verify rules
+
+ val account = Yaml.decodeFromString(LocalAccount.serializer(), readResource("local.account.yml"))
+ val bot = BotFactory.newBot(account.id, account.password) {
+ enableContactCache()
+ fileBasedDeviceInfo("local.device.json")
+ protocol = BotConfiguration.MiraiProtocol.ANDROID_PHONE
+ }.asQQAndroidBot()
+
+ MessageProtocolFacade.decoderPipeline.registerBefore(MessageDecoderProcessor(MessageDecodingRecorder()))
+
+ bot.login()
+
+ bot.join()
+}
\ No newline at end of file
diff --git a/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt b/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt
index ebffce213..fca467702 100644
--- a/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt
+++ b/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019-2021 Mamoe Technologies and contributors.
+ * Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.