From a89f6aeaef574229f243ece48834ba0edc8978f2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 28 Apr 2022 16:00:07 +0100 Subject: [PATCH] Integrate new MessageProtocol with existing code --- .run/RunMessageDecodingRecorderKt.run.xml | 23 +++ .../src/commonMain/kotlin/CoroutineUtils.kt | 8 +- .../kotlin/message/ReceiveMessageHandler.kt | 97 ++--------- .../kotlin/message/messageToElems.kt | 164 ++---------------- .../protocol/MessageDecoderPipeline.kt | 54 ++++++ .../protocol/MessageDecoderPipelineImpl.kt | 2 + .../protocol/MessageEncoderPipeline.kt | 79 +++++++++ .../protocol/MessageEncoderPipelineImpl.kt | 7 +- .../message/protocol/MessageProtocol.kt | 149 +++------------- .../message/protocol/MessageProtocolFacade.kt | 49 ++++++ .../protocol/impl/CustomMessageProtocol.kt | 9 +- .../message/protocol/impl/FaceProtocol.kt | 2 + .../protocol/impl/FileMessageProtocol.kt | 5 + .../protocol/impl/FlashImageProtocol.kt | 7 +- .../protocol/impl/GeneralFlagsProtocol.kt | 29 ---- .../protocol/impl/IgnoredMessagesProtocol.kt | 12 +- .../message/protocol/impl/ImageProtocol.kt | 23 ++- .../protocol/impl/MarketFaceProtocol.kt | 19 +- .../protocol/impl/MusicShareProtocol.kt | 3 + .../protocol/impl/PokeMessageProtocol.kt | 9 +- .../protocol/impl/PttMessageProtocol.kt | 1 + .../protocol/impl/QuoteReplyProtocol.kt | 16 +- .../protocol/impl/RichMessageProtocol.kt | 9 +- .../message/protocol/impl/TextProtocol.kt | 6 +- .../impl/UnsupportedMessageProtocol.kt | 7 +- .../message/protocol/impl/VipFaceProtocol.kt | 2 + .../kotlin/network/component/ComponentKey.kt | 11 +- .../components/NoticeProcessorPipeline.kt | 9 +- .../kotlin/network/protocol/data/proto/Msg.kt | 15 +- .../kotlin/pipeline/ProcessorPipeline.kt | 17 +- .../kotlin/utils/runCoroutineInPlace.kt | 58 +++++++ ....internal.message.protocol.MessageProtocol | 24 +++ .../CleanupRubbishMessageElementsTest.kt | 2 + .../protocol/MessageProtocolFacadeTest.kt | 42 +++++ .../impl/AbstractMessageProtocolTest.kt | 147 +++++++++++++--- .../message/protocol/impl/EqualityAsserter.kt | 95 ++++++++++ .../message/protocol/impl/FaceProtocolTest.kt | 5 +- .../message/protocol/impl/TextProtocolTest.kt | 118 +++++++++++++ .../protocol/MessageDecodingRecorder.kt | 28 +++ .../bootstrap/RunMessageDecodingRecorder.kt | 46 +++++ .../jvmTest/kotlin/bootstrap/RunRecorder.kt | 2 +- 41 files changed, 934 insertions(+), 476 deletions(-) create mode 100644 .run/RunMessageDecodingRecorderKt.run.xml create mode 100644 mirai-core/src/commonMain/kotlin/message/protocol/MessageDecoderPipeline.kt create mode 100644 mirai-core/src/commonMain/kotlin/message/protocol/MessageEncoderPipeline.kt create mode 100644 mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt delete mode 100644 mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralFlagsProtocol.kt create mode 100644 mirai-core/src/commonMain/kotlin/utils/runCoroutineInPlace.kt create mode 100644 mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.internal.message.protocol.MessageProtocol create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/EqualityAsserter.kt create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/TextProtocolTest.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/message/protocol/MessageDecodingRecorder.kt create mode 100644 mirai-core/src/jvmTest/kotlin/bootstrap/RunMessageDecodingRecorder.kt diff --git a/.run/RunMessageDecodingRecorderKt.run.xml b/.run/RunMessageDecodingRecorderKt.run.xml new file mode 100644 index 000000000..8ff153729 --- /dev/null +++ b/.run/RunMessageDecodingRecorderKt.run.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt b/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt index 77afab183..13e5d9e68 100644 --- a/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt +++ b/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt @@ -1,10 +1,10 @@ /* - * 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. + * 此源代码的使用受 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ @file:JvmMultifileClass diff --git a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt index ae4ba7e26..5c4ac4064 100644 --- a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt +++ b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt @@ -17,10 +17,18 @@ import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.joinToMessageC import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toAudio import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.data.OnlineAudioImpl +import net.mamoe.mirai.internal.message.protocol.MessageDecoderContext.Companion.BOT +import net.mamoe.mirai.internal.message.protocol.MessageDecoderContext.Companion.GROUP_ID +import net.mamoe.mirai.internal.message.protocol.MessageDecoderContext.Companion.MESSAGE_SOURCE_KIND +import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade +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.* import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.utils.runCoroutineInPlace import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.buildTypeSafeMap import net.mamoe.mirai.utils.toLongUnsigned /** @@ -147,68 +155,16 @@ internal object ReceiveMessageTransformer { bot: Bot, builder: MessageChainBuilder, ) { - // ProtoBuf.encodeToHexString(elements).soutv("join") - // (this._miraiContentToString().soutv()) - for (element in elements) { - transformElement(element, groupIdOrZero, messageSourceKind, bot, builder) - when { - element.richMsg != null -> { - // removed - } - } + val pipeline = MessageProtocolFacade.decoderPipeline + + val attributes = buildTypeSafeMap { + set(BOT, bot) + set(MESSAGE_SOURCE_KIND, messageSourceKind) + set(GROUP_ID, groupIdOrZero) } - } - private fun transformElement( - element: ImMsgBody.Elem, - groupIdOrZero: Long, - messageSourceKind: MessageSourceKind, - bot: Bot, - builder: MessageChainBuilder, - ) { - when { - element.srcMsg != null -> { - // removed - } - element.notOnlineImage != null -> { - // removed - } - element.customFace != null -> { - // removed - } - element.face != null -> { - // removed - } - element.text != null -> { - // removed - } - element.marketFace != null -> { - // removed - } - element.lightApp != null -> { - // removed - } - element.customElem != null -> { - // removed - } - element.commonElem != null -> { - // removed - } - element.transElemInfo != null -> { - // removed - } - - element.elemFlags2 != null - || element.extraInfo != null - || element.generalFlags != null - || element.anonGroupMsg != null - -> { - // ignore - } - else -> { - // removed - // println(it._miraiContentToString()) - } + runCoroutineInPlace { + elements.forEach { builder.addAll(pipeline.process(it, attributes)) } } } @@ -315,27 +271,6 @@ internal object ReceiveMessageTransformer { return builder.asMessageChain() } - private fun decodeText(text: ImMsgBody.Text, list: MessageChainBuilder) { - // removed - } - - private fun decodeSrcMsg( - srcMsg: ImMsgBody.SourceMsg, - list: MessageChainBuilder, - bot: Bot, - messageSourceKind: MessageSourceKind, - groupIdOrZero: Long, - ) { - // removed - } - - private fun decodeLightApp( - lightApp: ImMsgBody.LightAppElem, - list: MessageChainBuilder, - ) { - // removed - } - fun ImMsgBody.Ptt.toAudio() = OnlineAudioImpl( filename = fileName.decodeToString(), fileMd5 = fileMd5, diff --git a/mirai-core/src/commonMain/kotlin/message/messageToElems.kt b/mirai-core/src/commonMain/kotlin/message/messageToElems.kt index 910ecd236..3b8392d3b 100644 --- a/mirai-core/src/commonMain/kotlin/message/messageToElems.kt +++ b/mirai-core/src/commonMain/kotlin/message/messageToElems.kt @@ -9,27 +9,16 @@ package net.mamoe.mirai.internal.message -import net.mamoe.mirai.contact.AnonymousMember import net.mamoe.mirai.contact.ContactOrBot -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.internal.message.data.MarketFaceImpl -import net.mamoe.mirai.internal.message.data.UnsupportedMessageImpl -import net.mamoe.mirai.internal.message.flags.InternalFlagOnlyMessage -import net.mamoe.mirai.internal.message.image.OfflineFriendImage -import net.mamoe.mirai.internal.message.image.OfflineGroupImage -import net.mamoe.mirai.internal.message.image.OnlineFriendImageImpl -import net.mamoe.mirai.internal.message.image.OnlineGroupImageImpl -import net.mamoe.mirai.internal.message.source.MessageSourceInternal +import net.mamoe.mirai.internal.message.protocol.MessageEncoderContext +import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody -import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.utils.hexToBytes - -internal val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510 +import net.mamoe.mirai.internal.utils.runCoroutineInPlace +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.utils.buildTypeSafeMap -internal val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。") -internal val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。") -internal val UNSUPPORTED_FLASH_MESSAGE_PLAIN = PlainText("[闪照]请使用新版手机QQ查看闪照。") internal val UNSUPPORTED_VOICE_MESSAGE_PLAIN = PlainText("收到语音消息,你需要升级到最新版QQ才能接收,升级地址https://im.qq.com") @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @@ -38,138 +27,21 @@ internal fun MessageChain.toRichTextElems( withGeneralFlags: Boolean, isForward: Boolean = false, ): MutableList { - val forGroup = messageTarget is Group - val elements = ArrayList(this.size) + val originalMessage = this + val pipeline = MessageProtocolFacade.encoderPipeline - var longTextResId: String? = null - - fun transformOneMessage(currentMessage: Message) { - if (currentMessage is RichMessage) { - // removed - } - - when (currentMessage) { - is PlainText -> { - // removed - } - is CustomMessage -> { - // removed - } - is At -> { - // removed - } - is PokeMessage -> { - // removed - } - - - is OfflineGroupImage -> { - // removed - } - is OnlineGroupImageImpl -> { - // removed - } - is OnlineFriendImageImpl -> { - // removed - } - is OfflineFriendImage -> { - // removed - } - - - is FlashImage -> { - // removed - } - - is AtAll -> { - // removed - } - is Face -> { - // removed - } - is QuoteReply -> { // transformed - } - is Dice -> { - // removed - } - is MarketFace -> { - // removed - } - is VipFace -> { - // removed - } - is PttMessage -> { - // removed - } - is MusicShare -> { - // removed - } - - is ForwardMessage, - is MessageSource, // mirai metadata only - is RichMessage, // already transformed above - -> { - - } - is InternalFlagOnlyMessage, is ShowImageFlag -> { - // ignore - } - is UnsupportedMessageImpl -> { - // removed - } - else -> { - // unrecognized types are ignored - // error("unsupported message type: ${currentMessage::class.simpleName}") - } - } + val attributes = buildTypeSafeMap { + set(MessageEncoderContext.CONTACT, messageTarget) + set(MessageEncoderContext.ORIGINAL_MESSAGE, originalMessage) + set(MessageEncoderContext.ADD_GENERAL_FLAGS, withGeneralFlags) + set(MessageEncoderContext.IS_FORWARD, isForward) } - if (this.anyIsInstance()) { - when (val source = this[QuoteReply]!!.source) { - is MessageSourceInternal -> { - elements.add(ImMsgBody.Elem(srcMsg = source.toJceData())) - if (forGroup) { - if (source is OnlineMessageSource.Incoming.FromGroup) { - val sender0 = source.sender - if (sender0 !is AnonymousMember) - transformOneMessage(At(sender0)) - // transformOneMessage(PlainText(" ")) - // removed by https://github.com/mamoe/mirai/issues/524 - // 发送 QuoteReply 消息时无可避免的产生多余空格 #524 - } - } - } - else -> error("unsupported MessageSource implementation: ${source::class.simpleName}. Don't implement your own MessageSource.") - } + val builder = ArrayList(originalMessage.size) + + runCoroutineInPlace { + originalMessage.forEach { builder.addAll(pipeline.process(it, attributes)) } } - this.forEach(::transformOneMessage) - - if (withGeneralFlags) { - when { - longTextResId != null -> { - // removed - } - this.anyIsInstance() -> { - // removed - } - this.anyIsInstance() -> { - // removed - } - this.anyIsInstance() -> { - // removed - } - this.anyIsInstance() -> { - // removed - } - else -> { - // removed - } - } - } - - return elements + return builder } - -@Suppress("SpellCheckingInspection") -internal val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes() diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageDecoderPipeline.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageDecoderPipeline.kt new file mode 100644 index 000000000..0f92730c7 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageDecoderPipeline.kt @@ -0,0 +1,54 @@ +/* + * 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.Bot +import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.internal.pipeline.PipelineConsumptionMarker +import net.mamoe.mirai.internal.pipeline.Processor +import net.mamoe.mirai.internal.pipeline.ProcessorPipeline +import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.MessageSourceKind +import net.mamoe.mirai.utils.TypeKey +import kotlin.coroutines.RestrictsSuspension + +internal interface MessageDecoderPipeline : ProcessorPipeline + + +@RestrictsSuspension // Implementor can only call `MessageDecoderContext.process` and `processAlso` so there will be no suspension point +internal interface MessageDecoderContext : ProcessorPipelineContext { + companion object { + val BOT = TypeKey("bot") + val MESSAGE_SOURCE_KIND = TypeKey("messageSourceKind") + val GROUP_ID = TypeKey("groupId") // zero if not group + } +} + +internal interface MessageDecoder : PipelineConsumptionMarker { + suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) +} + +/** + * Adapter for [MessageDecoder] to be used as [Processor]. + */ +internal class MessageDecoderProcessor( + private val decoder: MessageDecoder, +) : Processor { + override suspend fun process(context: MessageDecoderContext, data: ImMsgBody.Elem) { + @Suppress("ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL") + decoder.run { context.process(data) } + // TODO: 2022/4/27 handle exceptions + } + + override fun toString(): String { + return "MessageDecoderProcessor(decoder=$decoder)" + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageDecoderPipelineImpl.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageDecoderPipelineImpl.kt index c74b956e6..f096a06e9 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/MessageDecoderPipelineImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageDecoderPipelineImpl.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.internal.message.protocol import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.pipeline.AbstractProcessorPipeline +import net.mamoe.mirai.internal.pipeline.PipelineConfiguration import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.TypeSafeMap @@ -25,6 +26,7 @@ private val defaultTraceLogging: MiraiLogger by lazy { internal open class MessageDecoderPipelineImpl : AbstractProcessorPipeline( + PipelineConfiguration(stopWhenConsumed = true), defaultTraceLogging ), MessageDecoderPipeline { diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageEncoderPipeline.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageEncoderPipeline.kt new file mode 100644 index 000000000..cef36070e --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageEncoderPipeline.kt @@ -0,0 +1,79 @@ +/* + * 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.contact.ContactOrBot +import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.internal.pipeline.PipelineConsumptionMarker +import net.mamoe.mirai.internal.pipeline.Processor +import net.mamoe.mirai.internal.pipeline.ProcessorPipeline +import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.SingleMessage +import net.mamoe.mirai.utils.TypeKey +import net.mamoe.mirai.utils.uncheckedCast +import kotlin.reflect.KClass + +internal interface MessageEncoderPipeline : + ProcessorPipeline, SingleMessage, ImMsgBody.Elem> { +} + +internal interface MessageEncoderContext : ProcessorPipelineContext { + + /** + * General flags that should be appended to the end of the result. + * + * Do not update this property directly, but call [collectGeneralFlags]. + */ + var generalFlags: ImMsgBody.Elem + + companion object { + val ADD_GENERAL_FLAGS = TypeKey("addGeneralFlags") + val MessageEncoderContext.addGeneralFlags get() = attributes[ADD_GENERAL_FLAGS] + + /** + * Override default generalFlags if needed + */ + inline fun MessageEncoderContext.collectGeneralFlags(block: () -> ImMsgBody.Elem) { + if (addGeneralFlags) { + generalFlags = block() + } + } + + val CONTACT = TypeKey("contactOrBot") + val MessageEncoderContext.contact get() = attributes[CONTACT] + + val ORIGINAL_MESSAGE = TypeKey("originalMessage") + val MessageEncoderContext.originalMessage get() = attributes[ORIGINAL_MESSAGE] + + val IS_FORWARD = TypeKey("isForward") + val MessageEncoderContext.isForward get() = attributes[IS_FORWARD] + } +} + + +internal fun interface MessageEncoder : PipelineConsumptionMarker { + suspend fun MessageEncoderContext.process(data: T) +} + +/** + * Adapter for [MessageEncoder] to be used as [Processor]. + */ +internal class MessageEncoderProcessor( + private val encoder: MessageEncoder, + private val elementType: KClass, +) : Processor { + override suspend fun process(context: MessageEncoderContext, data: SingleMessage) { + if (elementType.isInstance(data)) { + encoder.run { context.process(data.uncheckedCast()) } + // TODO: 2022/4/27 handle exceptions + } + } +} diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageEncoderPipelineImpl.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageEncoderPipelineImpl.kt index 46cf94bd2..19f97a3f4 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/MessageEncoderPipelineImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageEncoderPipelineImpl.kt @@ -9,9 +9,9 @@ package net.mamoe.mirai.internal.message.protocol -import net.mamoe.mirai.internal.message.PB_RESERVE_FOR_ELSE import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.pipeline.AbstractProcessorPipeline +import net.mamoe.mirai.internal.pipeline.PipelineConfiguration import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.utils.* @@ -22,6 +22,7 @@ private val defaultTraceLogging: MiraiLogger by lazy { internal open class MessageEncoderPipelineImpl : AbstractProcessorPipeline, MessageEncoderContext, SingleMessage, ImMsgBody.Elem>( + PipelineConfiguration(stopWhenConsumed = true), defaultTraceLogging ), MessageEncoderPipeline { @@ -34,4 +35,8 @@ internal open class MessageEncoderPipelineImpl : } override fun createContext(attributes: TypeSafeMap): MessageEncoderContext = MessageEncoderContextImpl(attributes) + + private companion object { + private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes() + } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocol.kt index 43a3021ee..d05022b38 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocol.kt @@ -9,33 +9,12 @@ package net.mamoe.mirai.internal.message.protocol -import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody -import net.mamoe.mirai.internal.pipeline.PipelineConsumptionMarker -import net.mamoe.mirai.internal.pipeline.Processor -import net.mamoe.mirai.internal.pipeline.ProcessorPipeline -import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext -import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.SingleMessage -import net.mamoe.mirai.utils.TypeKey -import net.mamoe.mirai.utils.uncheckedCast -import java.util.* import kotlin.reflect.KClass -internal abstract class ProcessorCollector { - inline fun add(encoder: MessageEncoder) = add(encoder, T::class) - - - abstract fun add(encoder: MessageEncoder, elementType: KClass) - - abstract fun add(decoder: MessageDecoder) -} - +// Loaded by ServiceLoader internal abstract class MessageProtocol( - private val priority: UInt = 1000u // the higher, the prior it being called + val priority: UInt = PRIORITY_CONTENT // the higher, the prior it being called ) { fun collectProcessors(processorCollector: ProcessorCollector) { processorCollector.collectProcessorsImpl() @@ -46,124 +25,36 @@ internal abstract class MessageProtocol( companion object { const val PRIORITY_METADATA: UInt = 10000u const val PRIORITY_CONTENT: UInt = 1000u + const val PRIORITY_IGNORE: UInt = 500u const val PRIORITY_UNSUPPORTED: UInt = 100u } -} -internal object MessageProtocols { - val instances: List = initialize() + object PriorityComparator : Comparator { + override fun compare(o1: MessageProtocol, o2: MessageProtocol): Int { - private fun initialize(): List { - val encoderPipeline = MessageEncoderPipelineImpl() - val decoderPipeline = MessageDecoderPipelineImpl() + // Do not use o1.compareTo + // > Task :mirai-core:checkAndroidApiLevel + // > /Users/runner/work/mirai/mirai/mirai-core/build/classes/kotlin/android/main/net/mamoe/mirai/internal/message/protocol/MessageProtocol$PriorityComparator.class + // > Method compare(Lnet/mamoe/mirai/internal/message/protocol/MessageProtocol;Lnet/mamoe/mirai/internal/message/protocol/MessageProtocol;)I + // > Invoke method java/lang/Integer.compareUnsigned(II)I + // Couldn't access java/lang/Integer.compareUnsigned(II)I: java/lang/Integer.compareUnsigned(II)I since api level 26 - val instances = ServiceLoader.load(MessageProtocol::class.java).iterator().asSequence().toList() - for (instance in instances) { - instance.collectProcessors(object : ProcessorCollector() { - override fun add(encoder: MessageEncoder, elementType: KClass) { - encoderPipeline.registerProcessor(MessageEncoderProcessor(encoder, elementType)) - } - - override fun add(decoder: MessageDecoder) { - decoderPipeline.registerProcessor(MessageDecoderProcessor(decoder)) - } - - }) + return uintCompare(o1.priority.toInt(), o2.priority.toInt()) } - return instances - } - -} - -/////////////////////////////////////////////////////////////////////////// -// decoders -/////////////////////////////////////////////////////////////////////////// - -internal interface MessageDecoderContext : ProcessorPipelineContext { - companion object { - val BOT = TypeKey("bot") - val MESSAGE_SOURCE_KIND = TypeKey("messageSourceKind") - val GROUP_ID = TypeKey("groupId") // zero if not group + private fun uintCompare(v1: Int, v2: Int): Int = (v1 xor Int.MIN_VALUE).compareTo(v2 xor Int.MIN_VALUE) } } -internal interface MessageDecoder : PipelineConsumptionMarker { - suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) +internal abstract class ProcessorCollector { + inline fun add(encoder: MessageEncoder) = add(encoder, T::class) + + + abstract fun add(encoder: MessageEncoder, elementType: KClass) + + abstract fun add(decoder: MessageDecoder) } -/** - * Adapter for [MessageDecoder] to be used as [Processor]. - */ -internal class MessageDecoderProcessor( - private val decoder: MessageDecoder -) : Processor { - override suspend fun process(context: MessageDecoderContext, data: ImMsgBody.Elem) { - decoder.run { context.process(data) } - // TODO: 2022/4/27 handle exceptions - } -} - -internal interface MessageDecoderPipeline : ProcessorPipeline - -/////////////////////////////////////////////////////////////////////////// -// encoders -/////////////////////////////////////////////////////////////////////////// - -internal interface MessageEncoderContext : ProcessorPipelineContext { - - /** - * General flags that should be appended to the end of the result. - * - * Do not update this property directly, but call [collectGeneralFlags]. - */ - var generalFlags: ImMsgBody.Elem - - companion object { - val ADD_GENERAL_FLAGS = TypeKey("addGeneralFlags") - val MessageEncoderContext.addGeneralFlags get() = attributes[ADD_GENERAL_FLAGS] - - /** - * Override default generalFlags if needed - */ - inline fun MessageEncoderContext.collectGeneralFlags(block: () -> ImMsgBody.Elem) { - if (addGeneralFlags) { - generalFlags = block() - } - } - - val CONTACT = TypeKey("contact") - val MessageEncoderContext.contact get() = attributes[CONTACT] - - val ORIGINAL_MESSAGE = TypeKey("originalMessage") - val MessageEncoderContext.originalMessage get() = attributes[ORIGINAL_MESSAGE] - - val IS_FORWARD = TypeKey("isForward") - val MessageEncoderContext.isForward get() = attributes[IS_FORWARD] - } -} - - -internal fun interface MessageEncoder : PipelineConsumptionMarker { - suspend fun MessageEncoderContext.process(data: T) -} - -/** - * Adapter for [MessageEncoder] to be used as [Processor]. - */ -internal class MessageEncoderProcessor( - private val encoder: MessageEncoder, - private val elementType: KClass, -) : Processor { - override suspend fun process(context: MessageEncoderContext, data: SingleMessage) { - if (elementType.isInstance(data)) { - encoder.run { context.process(data.uncheckedCast()) } - // TODO: 2022/4/27 handle exceptions - } - } -} - -internal interface MessageEncoderPipeline : ProcessorPipeline, SingleMessage, ImMsgBody.Elem> /////////////////////////////////////////////////////////////////////////// // refiners diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt new file mode 100644 index 000000000..678f7cf2c --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt @@ -0,0 +1,49 @@ +/* + * 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.message.data.SingleMessage +import java.util.* +import kotlin.reflect.KClass + +internal interface MessageProtocolFacade { + val encoderPipeline: MessageEncoderPipeline + val decoderPipeline: MessageDecoderPipeline + val loaded: List + + companion object INSTANCE : MessageProtocolFacade by MessageProtocolFacadeImpl() +} + +internal class MessageProtocolFacadeImpl : MessageProtocolFacade { + override val encoderPipeline: MessageEncoderPipeline = MessageEncoderPipelineImpl() + override val decoderPipeline: MessageDecoderPipeline = MessageDecoderPipelineImpl() + + override val loaded: List = initialize() + + private fun initialize(): List { + val instances = ServiceLoader.load(MessageProtocol::class.java).iterator().asSequence() + .toCollection(PriorityQueue(MessageProtocol.PriorityComparator.reversed())) + + for (instance in instances) { + instance.collectProcessors(object : ProcessorCollector() { + override fun add(encoder: MessageEncoder, elementType: KClass) { + encoderPipeline.registerProcessor(MessageEncoderProcessor(encoder, elementType)) + } + + override fun add(decoder: MessageDecoder) { + decoderPipeline.registerProcessor(MessageDecoderProcessor(decoder)) + } + + }) + } + + return instances.toList() + } +} diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/CustomMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/CustomMessageProtocol.kt index 2e41ab5cf..6b40b296e 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/CustomMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/CustomMessageProtocol.kt @@ -9,7 +9,6 @@ package net.mamoe.mirai.internal.message.protocol.impl -import net.mamoe.mirai.internal.message.MIRAI_CUSTOM_ELEM_TYPE import net.mamoe.mirai.internal.message.protocol.* import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.CustomMessage @@ -24,6 +23,8 @@ internal class CustomMessageProtocol : MessageProtocol() { private class Encoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: CustomMessage) { + markAsConsumed() + @Suppress("UNCHECKED_CAST") collect( ImMsgBody.Elem( @@ -37,12 +38,16 @@ internal class CustomMessageProtocol : MessageProtocol() { ) ) } + + private companion object { + private val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510 + } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.customElem == null) return - + markAsConsumed() kotlin.runCatching { data.customElem.data.read { CustomMessage.load(this) diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/FaceProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/FaceProtocol.kt index 7c9cf5a13..8d7f4f4d6 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/FaceProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/FaceProtocol.kt @@ -28,6 +28,7 @@ internal class FaceProtocol : MessageProtocol() { private class Encoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: Face) { + markAsConsumed() collect( if (data.id >= 260) { ImMsgBody.Elem(commonElem = data.toCommData()) @@ -67,6 +68,7 @@ internal class FaceProtocol : MessageProtocol() { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { val commonElem = data.commonElem ?: return if (commonElem.serviceType != 33) return + markAsConsumed() val proto = commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype33.serializer()) diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt index a857abedd..f0b857c7b 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt @@ -31,6 +31,11 @@ internal class FileMessageProtocol : MessageProtocol() { if (data.transElemInfo == null) return if (data.transElemInfo.elemType != 24) return + markAsConsumed() + + processAlso(data) + process(data) + data.transElemInfo.elemValue.read { // group file feed // 01 00 77 08 06 12 0A 61 61 61 61 61 61 2E 74 78 74 1A 06 31 35 42 79 74 65 3A 5F 12 5D 08 66 12 25 2F 64 37 34 62 62 66 33 61 2D 37 62 32 35 2D 31 31 65 62 2D 38 34 66 38 2D 35 34 35 32 30 30 37 62 35 64 39 66 18 0F 22 0A 61 61 61 61 61 61 2E 74 78 74 28 00 3A 00 42 20 61 33 32 35 66 36 33 34 33 30 65 37 61 30 31 31 66 37 64 30 38 37 66 63 33 32 34 37 35 34 39 63 diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/FlashImageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/FlashImageProtocol.kt index 88a77f6a6..d1cb2381c 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/FlashImageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/FlashImageProtocol.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.User -import net.mamoe.mirai.internal.message.UNSUPPORTED_FLASH_MESSAGE_PLAIN import net.mamoe.mirai.internal.message.image.OnlineFriendImageImpl import net.mamoe.mirai.internal.message.image.OnlineGroupImageImpl import net.mamoe.mirai.internal.message.image.friendImageId @@ -23,6 +22,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.data.FlashImage +import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.utils.hexToBytes internal class FlashImageProtocol : MessageProtocol() { @@ -36,6 +36,8 @@ internal class FlashImageProtocol : MessageProtocol() { if (data.commonElem == null) return if (data.commonElem.serviceType != 3) return + markAsConsumed() + val proto = data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype3.serializer()) if (proto.flashTroopPic != null) { @@ -51,6 +53,8 @@ internal class FlashImageProtocol : MessageProtocol() { private class Encoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: FlashImage) { + markAsConsumed() + collect(data.toJceData(contact)) processAlso(UNSUPPORTED_FLASH_MESSAGE_PLAIN) collectGeneralFlags { @@ -61,6 +65,7 @@ internal class FlashImageProtocol : MessageProtocol() { private companion object { @Suppress("SpellCheckingInspection") private val PB_RESERVE_FOR_DOUTU = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes() + private val UNSUPPORTED_FLASH_MESSAGE_PLAIN = PlainText("[闪照]请使用新版手机QQ查看闪照。") private fun FlashImage.toJceData(messageTarget: ContactOrBot?): ImMsgBody.Elem { return if (messageTarget is User) { diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralFlagsProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralFlagsProtocol.kt deleted file mode 100644 index 8b85c01ce..000000000 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralFlagsProtocol.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.message.protocol.MessageEncoder -import net.mamoe.mirai.internal.message.protocol.MessageEncoderContext -import net.mamoe.mirai.internal.message.protocol.MessageProtocol -import net.mamoe.mirai.internal.message.protocol.ProcessorCollector -import net.mamoe.mirai.message.data.SingleMessage - -internal class GeneralFlagsProtocol : MessageProtocol() { - override fun ProcessorCollector.collectProcessorsImpl() { - add(Encoder()) - } - - private class Encoder : MessageEncoder { - override suspend fun MessageEncoderContext.process(data: SingleMessage) { - - } - - } -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/IgnoredMessagesProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/IgnoredMessagesProtocol.kt index 5ff734cb8..9c33de01c 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/IgnoredMessagesProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/IgnoredMessagesProtocol.kt @@ -17,18 +17,24 @@ import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.ShowImageFlag import net.mamoe.mirai.message.data.SingleMessage -internal class IgnoredMessagesProtocol : MessageProtocol() { +internal class IgnoredMessagesProtocol : MessageProtocol(PRIORITY_IGNORE) { override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) add(Decoder()) + + // 所有未处理的 Elem 都会变成 UnsupportedMessage 所有不用在这里处理 } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { - when (data) { + when { + data.elemFlags2 != null + || data.extraInfo != null + || data.generalFlags != null + || data.anonGroupMsg != null + -> markAsConsumed() } } - } private class Encoder : MessageEncoder { diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/ImageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/ImageProtocol.kt index e7ddf5c4b..d70cf9494 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/ImageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/ImageProtocol.kt @@ -29,15 +29,22 @@ internal class ImageProtocol : MessageProtocol() { private class ImageDecoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { - if (data.notOnlineImage != null) collect(OnlineFriendImageImpl(data.notOnlineImage)) - if (data.customFace != null) { - collect(OnlineGroupImageImpl(data.customFace)) - data.customFace.pbReserve.let { - if (it.isNotEmpty() && it.loadAs(CustomFace.ResvAttr.serializer()).msgImageShow != null) { - collect(ShowImageFlag) + markAsConsumed() + when { + data.notOnlineImage != null -> { + collect(OnlineFriendImageImpl(data.notOnlineImage)) + } + data.customFace != null -> { + collect(OnlineGroupImageImpl(data.customFace)) + data.customFace.pbReserve.let { + if (it.isNotEmpty() && it.loadAs(CustomFace.ResvAttr.serializer()).msgImageShow != null) { + collect(ShowImageFlag) + } } } - + else -> { + markNotConsumed() + } } } @@ -45,6 +52,8 @@ internal class ImageProtocol : MessageProtocol() { private class ImageEncoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: AbstractImage) { + markAsConsumed() + when (data) { is OfflineGroupImage -> { if (contact is User) { diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/MarketFaceProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/MarketFaceProtocol.kt index 26a6699bf..a41cc042c 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/MarketFaceProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/MarketFaceProtocol.kt @@ -7,24 +7,6 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -/* - * 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 - */ - -/* - * 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.message.data.MarketFaceImpl @@ -62,6 +44,7 @@ internal class MarketFaceProtocol : MessageProtocol() { private class DiceEncoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: Dice) { + markAsConsumed() processAlso(MarketFaceImpl(data.toJceStruct())) } } diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt index d5b17ce83..fd2fd2b3a 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt @@ -17,10 +17,13 @@ import net.mamoe.mirai.message.data.content internal class MusicShareProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { + add(Encoder()) +// add(Decoder()) } private class Encoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: MusicShare) { + markAsConsumed() // 只有在 QuoteReply 的 source 里才会进行 MusicShare 转换, 因此可以转 PT. // 发送消息时会被特殊处理 processAlso(PlainText(data.content)) diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/PokeMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/PokeMessageProtocol.kt index 98a5cb7de..040a08991 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/PokeMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/PokeMessageProtocol.kt @@ -9,15 +9,19 @@ package net.mamoe.mirai.internal.message.protocol.impl -import net.mamoe.mirai.internal.message.UNSUPPORTED_POKE_MESSAGE_PLAIN import net.mamoe.mirai.internal.message.protocol.* import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray +import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.PokeMessage internal class PokeMessageProtocol : MessageProtocol() { + companion object { + val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。") + } + override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) add(Decoder()) @@ -25,6 +29,7 @@ internal class PokeMessageProtocol : MessageProtocol() { private class Encoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: PokeMessage) { + markAsConsumed() collect( ImMsgBody.Elem( commonElem = ImMsgBody.CommonElem( @@ -41,13 +46,13 @@ internal class PokeMessageProtocol : MessageProtocol() { ) processAlso(UNSUPPORTED_POKE_MESSAGE_PLAIN) } - } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.commonElem == null) return if (data.commonElem.serviceType != 2) return + markAsConsumed() val proto = data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer()) diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/PttMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/PttMessageProtocol.kt index 36c115ec0..867b88d45 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/PttMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/PttMessageProtocol.kt @@ -26,6 +26,7 @@ internal class PttMessageProtocol : MessageProtocol() { private class Encoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: PttMessage) { + markAsConsumed() collect( ImMsgBody.Elem( extraInfo = ImMsgBody.ExtraInfo(flags = 16, groupMask = 1) diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/QuoteReplyProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/QuoteReplyProtocol.kt index 7827d8315..6396cebb9 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/QuoteReplyProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/QuoteReplyProtocol.kt @@ -32,11 +32,16 @@ internal class QuoteReplyProtocol : MessageProtocol(PRIORITY_METADATA) { private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.srcMsg == null) return - OfflineMessageSourceImplData( - data.srcMsg, - attributes[BOT], - attributes[MESSAGE_SOURCE_KIND], - attributes[GROUP_ID] + markAsConsumed() + collect( + QuoteReply( + OfflineMessageSourceImplData( + data.srcMsg, + attributes[BOT], + attributes[MESSAGE_SOURCE_KIND], + attributes[GROUP_ID] + ) + ) ) } @@ -45,6 +50,7 @@ internal class QuoteReplyProtocol : MessageProtocol(PRIORITY_METADATA) { private class Encoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: QuoteReply) { val source = data.source as? MessageSourceInternal ?: return + markAsConsumed() collect(ImMsgBody.Elem(srcMsg = source.toJceData())) if (contact is Group) { if (source is OnlineMessageSource.Incoming.FromGroup) { diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/RichMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/RichMessageProtocol.kt index 5b4845b04..1a2bba391 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/RichMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/RichMessageProtocol.kt @@ -10,7 +10,6 @@ package net.mamoe.mirai.internal.message.protocol.impl import kotlinx.io.core.toByteArray -import net.mamoe.mirai.internal.message.UNSUPPORTED_MERGED_MESSAGE_PLAIN import net.mamoe.mirai.internal.message.data.ForwardMessageInternal import net.mamoe.mirai.internal.message.data.LightAppInternal import net.mamoe.mirai.internal.message.data.LongMessageInternal @@ -32,6 +31,10 @@ import net.mamoe.mirai.utils.zip * - [ForwardMessage] */ internal class RichMessageProtocol : MessageProtocol() { + companion object { + val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。") + } + override fun ProcessorCollector.collectProcessorsImpl() { add(RichMsgDecoder()) add(LightAppDecoder()) @@ -41,6 +44,7 @@ internal class RichMessageProtocol : MessageProtocol() { private class Encoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: RichMessage) { + markAsConsumed() val content = data.content.toByteArray().zip() var longTextResId: String? = null when (data) { @@ -92,7 +96,7 @@ internal class RichMessageProtocol : MessageProtocol() { ImMsgBody.Elem( generalFlags = ImMsgBody.GeneralFlags( longTextFlag = 1, - longTextResid = longTextResId!!, + longTextResid = longTextResId, pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes() ) ) @@ -112,6 +116,7 @@ internal class RichMessageProtocol : MessageProtocol() { private class LightAppDecoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { val lightApp = data.lightApp ?: return + markAsConsumed() val content = runWithBugReport("解析 lightApp", { "resId=" + lightApp.msgResid + "data=" + lightApp.data.toUHexString() }) { diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/TextProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/TextProtocol.kt index f729d0a7e..3e0cff8fd 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/TextProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/TextProtocol.kt @@ -40,6 +40,7 @@ internal class TextProtocol : MessageProtocol() { private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { val text = data.text ?: return + markAsConsumed() if (text.attr6Buf.isEmpty()) { collect(PlainText(text.str)) } else { @@ -59,16 +60,18 @@ internal class TextProtocol : MessageProtocol() { private class PlainTextEncoder : MessageEncoder { override suspend fun MessageEncoderContext.process(data: PlainText) { + markAsConsumed() collect(ImMsgBody.Elem(text = ImMsgBody.Text(str = data.content))) } } private class AtEncoder : MessageEncoder<At> { override suspend fun MessageEncoderContext.process(data: At) { + markAsConsumed() collected += ImMsgBody.Elem( text = data.toJceData( attributes[CONTACT].safeCast(), - originalMessage[MessageSource], + originalMessage.sourceOrNull, isForward, ) ) @@ -148,6 +151,7 @@ internal class TextProtocol : MessageProtocol() { private class AtAllEncoder : MessageEncoder<AtAll> { override suspend fun MessageEncoderContext.process(data: AtAll) { + markAsConsumed() collect(jceData) } diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/UnsupportedMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/UnsupportedMessageProtocol.kt index 3ac695348..31769e044 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/UnsupportedMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/UnsupportedMessageProtocol.kt @@ -13,7 +13,7 @@ import net.mamoe.mirai.internal.message.data.UnsupportedMessageImpl import net.mamoe.mirai.internal.message.protocol.* import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody -internal class UnsupportedMessageProtocol : MessageProtocol(priority = 100u) { +internal class UnsupportedMessageProtocol : MessageProtocol(priority = PRIORITY_UNSUPPORTED) { override fun ProcessorCollector.collectProcessorsImpl() { add(Decoder()) add(Encoder()) @@ -21,13 +21,16 @@ internal class UnsupportedMessageProtocol : MessageProtocol(priority = 100u) { private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { - val struct = UnsupportedMessageImpl(data).takeIf { it.struct.isNotEmpty() } ?: return + markAsConsumed() + val struct = UnsupportedMessageImpl(data) + if (struct.struct.isEmpty()) return collect(struct) } } private class Encoder : MessageEncoder<UnsupportedMessageImpl> { override suspend fun MessageEncoderContext.process(data: UnsupportedMessageImpl) { + markAsConsumed() collect(data.structElem) } } diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/VipFaceProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/VipFaceProtocol.kt index 1600cce34..e86a79d45 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/VipFaceProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/VipFaceProtocol.kt @@ -24,6 +24,7 @@ internal class VipFaceProtocol : MessageProtocol() { private class Encoder : MessageEncoder<VipFace> { override suspend fun MessageEncoderContext.process(data: VipFace) { + markAsConsumed() processAlso(PlainText(data.contentToString())) } } @@ -32,6 +33,7 @@ internal class VipFaceProtocol : MessageProtocol() { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.commonElem == null) return if (data.commonElem.serviceType != 23) return + markAsConsumed() val proto = data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype23.serializer()) diff --git a/mirai-core/src/commonMain/kotlin/network/component/ComponentKey.kt b/mirai-core/src/commonMain/kotlin/network/component/ComponentKey.kt index d29edc9d7..92ebcc076 100644 --- a/mirai-core/src/commonMain/kotlin/network/component/ComponentKey.kt +++ b/mirai-core/src/commonMain/kotlin/network/component/ComponentKey.kt @@ -1,20 +1,23 @@ /* - * 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. + * 此源代码的使用受 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ package net.mamoe.mirai.internal.network.component +import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import kotlin.reflect.* import kotlin.reflect.full.allSupertypes /** * A key for specific component [T]. Components are not polymorphic. * + * Most components locate in `net.mamoe.mirai.internal.network.components` while some like [MessageProtocolFacade] don't. + * * @param T is a type hint. */ internal interface ComponentKey<T : Any> { diff --git a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt index 8bf421af0..b9faceb5c 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt @@ -28,10 +28,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushTransMsg import net.mamoe.mirai.internal.network.toPacket -import net.mamoe.mirai.internal.pipeline.AbstractProcessorPipeline -import net.mamoe.mirai.internal.pipeline.Processor -import net.mamoe.mirai.internal.pipeline.ProcessorPipeline -import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext +import net.mamoe.mirai.internal.pipeline.* import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.utils.* import kotlin.reflect.KClass @@ -82,7 +79,9 @@ internal open class NoticeProcessorPipelineImpl protected constructor( private val bot: QQAndroidBot, traceLogging: MiraiLogger = defaultTraceLogging, ) : NoticeProcessorPipeline, - AbstractProcessorPipeline<NoticeProcessor, NoticePipelineContext, ProtocolStruct, Packet>(traceLogging) { + AbstractProcessorPipeline<NoticeProcessor, NoticePipelineContext, ProtocolStruct, Packet>( + PipelineConfiguration(stopWhenConsumed = false), traceLogging + ) { open inner class ContextImpl( attributes: TypeSafeMap, diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt index a7eabc879..c76a7d8ca 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt @@ -1,10 +1,10 @@ /* - * 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. + * 此源代码的使用受 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto @@ -14,6 +14,7 @@ import kotlinx.serialization.protobuf.ProtoIntegerType import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.internal.utils.io.ProtoBuf +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY @Serializable @@ -380,7 +381,11 @@ internal class ImMsgBody : ProtoBuf { @ProtoNumber(51) @JvmField val lightApp: LightAppElem? = null, @ProtoNumber(52) @JvmField val eimInfo: EIMInfo? = null, @ProtoNumber(53) @JvmField val commonElem: CommonElem? = null, - ) : ProtoBuf + ) : ProtoBuf { + override fun toString(): String { + return this.structureToString() + } + } @Serializable internal class ElemFlags( diff --git a/mirai-core/src/commonMain/kotlin/pipeline/ProcessorPipeline.kt b/mirai-core/src/commonMain/kotlin/pipeline/ProcessorPipeline.kt index 9bc0f9052..b58eee9de 100644 --- a/mirai-core/src/commonMain/kotlin/pipeline/ProcessorPipeline.kt +++ b/mirai-core/src/commonMain/kotlin/pipeline/ProcessorPipeline.kt @@ -49,6 +49,10 @@ internal value class MutableProcessResult<R>( internal interface PipelineConsumptionMarker internal interface ProcessorPipelineContext<D, R> { + + /** + * Child processes ([processAlso]) will inherit [attributes] from its parent, while any other properties from the context will not. + */ val attributes: TypeSafeMap val collected: MutableProcessResult<R> @@ -135,11 +139,16 @@ internal abstract class AbstractProcessorPipelineContext<D, R>( } } +internal class PipelineConfiguration( + var stopWhenConsumed: Boolean +) + internal abstract class AbstractProcessorPipeline<P : Processor<C, D>, C : ProcessorPipelineContext<D, R>, D, R> protected constructor( + val configuration: PipelineConfiguration, val traceLogging: MiraiLogger, ) : ProcessorPipeline<P, D, R> { - 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 <R> runCoroutineInPlace(coroutine: suspend () -> R): R { + var lateResult: CompletableDeferred<R>? = null + + val result = coroutine.startCoroutineUninterceptedOrReturn(Continuation(EmptyCoroutineContext) { r -> + val deferred: CompletableDeferred<R>? = 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<ImMsgBody.Elem> ) { - 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<T>, - actual: List<T>, - 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<ImMsgBody.Elem> - ): Unit = doEncoderChecks(mutableListOf(expectedStruct), protocol, encode) + ): Unit = doEncoderChecks(expectedStruct.toList(), protocol, encode) + + + inner class ChecksBuilder { + var elems: MutableList<ImMsgBody.Elem> = 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<ImMsgBody.Elem>, + 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<ChecksConfiguration> { // 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<ChecksConfiguration>.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<ChecksConfiguration>.doDecoderChecks() { + val config = this.getCompleted() + doDecoderChecks(config.messageChain, protocol) { + decode(config.elems, config.groupIdOrZero, config.messageSourceKind, bot) + } + } + + protected open fun Deferred<ChecksConfiguration>.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<T>, + actual: List<T>, + message: String? = null + ) + + object Ordinary : EqualityAsserter { + override fun <T> assertEquals(expected: List<T>, actual: List<T>, 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 <T> assertEquals(expected: List<T>, actual: List<T>, 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 <T> structureToStringOrOrdinaryString(it: T): String = + it.structureToString().ifBlank { + it.structureToStringIfAvailable() ?: error("structureToString is not available") + } + } + + object OrdinaryThenStructural : EqualityAsserter { + override fun <T> assertEquals(expected: List<T>, actual: List<T>, 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.