From ab3280f6b76c2df98e1f8dafb1808cb763a3adb9 Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 23 May 2022 18:08:32 +0100 Subject: [PATCH] Add more MessageProtocolTest: Add fragmented test Fix MusicShareProtocol and implement sending tests for MusicShareProtocolTest OutgoingMessagePipelineImpl: set stopWhenConsumed Fix `currentMessageChain` not updated in `processAlso`, add strong message packets checks, add tests for converting messages failed to send at FIRST step to LongMessageInternal Add notes for receiving ForwardMessage Add facade parameters to `download ForwardMessage*` Add ForwardMessageProtocolTest Add QuoteReplyProtocolTest Add CONTAINING_MSG for MessageDecoderContext.attributes, for information-use only Fix MessageReceiptTest Fix QuoteReplyProtocolTest --- .../kotlin/contact/GroupSendMessageImpl.kt | 10 +- .../kotlin/message/ReceiveMessageHandler.kt | 29 +- .../message/flags/InternalFlagOnlyMessage.kt | 29 +- .../message/protocol/MessageProtocol.kt | 7 + .../message/protocol/MessageProtocolFacade.kt | 42 +- .../protocol/decode/MessageDecoderPipeline.kt | 20 +- .../protocol/impl/ForwardMessageProtocol.kt | 2 +- .../impl/GeneralMessageSenderProtocol.kt | 59 ++- .../protocol/impl/MusicShareProtocol.kt | 33 +- .../outgoing/MessageProtocolStrategy.kt | 8 + .../outgoing/OutgoingMessagePipeline.kt | 25 +- .../message/visitor/MessageVisitorEx.kt | 4 + .../components/NoticeProcessorPipeline.kt | 12 - .../kotlin/network/protocol/data/proto/Msg.kt | 8 + .../kotlin/pipeline/ProcessorPipeline.kt | 11 +- .../impl/AbstractMessageProtocolTest.kt | 117 +++-- .../impl/ForwardMessageProtocolTest.kt | 146 ++++++ .../impl/GeneralMessageSenderProtocolTest.kt | 114 +++++ .../protocol/impl/LongMessageProtocolTest.kt | 48 ++ .../protocol/impl/MusicShareProtocolTest.kt | 46 +- .../protocol/impl/QuoteReplyProtocolTest.kt | 449 +++++++++++++++--- .../AbstractMockNetworkHandlerTest.kt | 13 +- .../kotlin/message/data/MessageReceiptTest.kt | 7 +- .../kotlin/message/data/MessageRefineTest.kt | 4 +- 24 files changed, 1020 insertions(+), 223 deletions(-) create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/ForwardMessageProtocolTest.kt create mode 100644 mirai-core/src/commonTest/kotlin/message/protocol/impl/GeneralMessageSenderProtocolTest.kt diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt index b0c037bde..7142dff17 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt @@ -44,11 +44,13 @@ internal suspend fun C.broadcastMessagePreSendEvent( } -internal enum class SendMessageStep { +internal enum class SendMessageStep( + val allowMultiplePackets: Boolean +) { /** * 尝试单包直接发送全部消息 */ - FIRST { + FIRST(false) { override fun nextStepOrNull(): SendMessageStep { return LONG_MESSAGE } @@ -57,7 +59,7 @@ internal enum class SendMessageStep { /** * 尝试通过长消息通道上传长消息取得 resId 后再通过普通消息通道发送长消息标识 */ - LONG_MESSAGE { + LONG_MESSAGE(false) { override fun nextStepOrNull(): SendMessageStep { return FRAGMENTED } @@ -66,7 +68,7 @@ internal enum class SendMessageStep { /** * 发送分片多包发送 */ - FRAGMENTED { + FRAGMENTED(true) { override fun nextStepOrNull(): SendMessageStep? { return null } diff --git a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt index 669d15c43..2070d5d7b 100644 --- a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt +++ b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt @@ -13,7 +13,6 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements -import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.joinToMessageChain import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toAudio import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.data.OnlineAudioImpl @@ -34,10 +33,11 @@ internal fun ImMsgBody.SourceMsg.toMessageChainNoSource( messageSourceKind: MessageSourceKind, groupIdOrZero: Long, refineContext: RefineContext = EmptyRefineContext, + facade: MessageProtocolFacade = MessageProtocolFacade ): MessageChain { val elements = this.elems return buildMessageChain(elements.size + 1) { - joinToMessageChain(elements, groupIdOrZero, messageSourceKind, bot, this) + facade.decode(elements, groupIdOrZero, messageSourceKind, bot, this, null) }.cleanupRubbishMessageElements().refineLight(bot, refineContext) } @@ -47,13 +47,15 @@ internal suspend fun List.toMessageChainOnline( groupIdOrZero: Long, messageSourceKind: MessageSourceKind, refineContext: RefineContext = EmptyRefineContext, + facade: MessageProtocolFacade = MessageProtocolFacade ): MessageChain { - return toMessageChain(bot, groupIdOrZero, true, messageSourceKind).refineDeep(bot, refineContext) + return toMessageChain(bot, groupIdOrZero, true, messageSourceKind, facade).refineDeep(bot, refineContext) } internal suspend fun MsgComm.Msg.toMessageChainOnline( bot: Bot, refineContext: RefineContext = EmptyRefineContext, + facade: MessageProtocolFacade = MessageProtocolFacade, ): MessageChain { fun getSourceKind(c2cCmd: Int): MessageSourceKind { return when (c2cCmd) { @@ -69,7 +71,7 @@ internal suspend fun MsgComm.Msg.toMessageChainOnline( MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0 else -> 0 } - return listOf(this).toMessageChainOnline(bot, groupId, kind, refineContext) + return listOf(this).toMessageChainOnline(bot, groupId, kind, refineContext, facade) } //internal fun List.toMessageChainOffline( @@ -95,20 +97,21 @@ private fun List.toMessageChain( groupIdOrZero: Long, onlineSource: Boolean?, messageSourceKind: MessageSourceKind, + facade: MessageProtocolFacade = MessageProtocolFacade, ): MessageChain { val messageList = this - val elements = messageList.flatMap { it.msgBody.richText.elems } - - val builder = MessageChainBuilder(elements.size) + val builder = MessageChainBuilder(messageList.sumOf { it.msgBody.richText.elems.size }) if (onlineSource != null) { builder.add(ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList)) } - joinToMessageChain(elements, groupIdOrZero, messageSourceKind, bot, builder) + messageList.forEach { msg -> + facade.decode(msg.msgBody.richText.elems, groupIdOrZero, messageSourceKind, bot, builder, msg) + } for (msg in messageList) { msg.msgBody.richText.ptt?.toAudio()?.let { builder.add(it) } @@ -143,16 +146,6 @@ internal object ReceiveMessageTransformer { } } - fun joinToMessageChain( - elements: List, - groupIdOrZero: Long, - messageSourceKind: MessageSourceKind, - bot: Bot, - builder: MessageChainBuilder, - ) { - return MessageProtocolFacade.decode(elements, groupIdOrZero, messageSourceKind, bot, builder) - } - fun MessageChainBuilder.compressContinuousPlainText() { var index = 0 val builder = StringBuilder() diff --git a/mirai-core/src/commonMain/kotlin/message/flags/InternalFlagOnlyMessage.kt b/mirai-core/src/commonMain/kotlin/message/flags/InternalFlagOnlyMessage.kt index 9f540e85c..bc78ff6e4 100644 --- a/mirai-core/src/commonMain/kotlin/message/flags/InternalFlagOnlyMessage.kt +++ b/mirai-core/src/commonMain/kotlin/message/flags/InternalFlagOnlyMessage.kt @@ -12,10 +12,7 @@ package net.mamoe.mirai.internal.message.flags import net.mamoe.mirai.internal.message.visitor.ex -import net.mamoe.mirai.message.data.AbstractMessageKey -import net.mamoe.mirai.message.data.ConstrainSingle -import net.mamoe.mirai.message.data.MessageKey -import net.mamoe.mirai.message.data.MessageMetadata +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.safeCast @@ -24,17 +21,35 @@ import net.mamoe.mirai.utils.safeCast */ internal sealed interface InternalFlagOnlyMessage : MessageMetadata +internal sealed interface ForceAs : InternalFlagOnlyMessage, ConstrainSingle { + companion object Key : AbstractMessageKey({ it.safeCast() }) +} + /** * 内部 flag, 放入 chain 强制作为 long 发送 */ -internal object ForceAsLongMessage : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage, - AbstractMessageKey({ it.safeCast() }) { +internal object ForceAsLongMessage : ForceAs, + AbstractPolymorphicMessageKey(ForceAs, { it.safeCast() }) { override val key: MessageKey get() = this override fun toString(): String = "" override fun accept(visitor: MessageVisitor, data: D): R { - return visitor.ex()?.visitForceAsLongMessage(this, data) ?: super.accept(visitor, data) + return visitor.ex()?.visitForceAsLongMessage(this, data) ?: super.accept(visitor, data) + } +} + +/** + * 内部 flag, 放入 chain 强制作为 fragmented 发送 + */ +internal object ForceAsFragmentedMessage : ForceAs, + AbstractPolymorphicMessageKey(ForceAs, { it.safeCast() }) { + override val key: MessageKey get() = this + + override fun toString(): String = "" + + override fun accept(visitor: MessageVisitor, data: D): R { + return visitor.ex()?.visitForceAsFragmentedMessage(this, data) ?: super.accept(visitor, data) } } diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocol.kt index 0bc072a3c..26c5a5408 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocol.kt @@ -16,12 +16,19 @@ import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePreproc import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer import net.mamoe.mirai.message.data.SingleMessage +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.systemProp +import net.mamoe.mirai.utils.withSwitch import kotlin.reflect.KClass // Loaded by ServiceLoader internal abstract class MessageProtocol( val priority: UInt = PRIORITY_CONTENT // the higher, the prior it being called ) { + val logger: MiraiLogger by lazy { + MiraiLogger.Factory.create(this::class).withSwitch(systemProp("mirai.message.protocol.log.full", false)) + } + fun collectProcessors(processorCollector: ProcessorCollector) { processorCollector.collectProcessorsImpl() } diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt index b0bca26ad..040a68880 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt @@ -22,11 +22,18 @@ import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.message.protocol.decode.* import net.mamoe.mirai.internal.message.protocol.encode.* import net.mamoe.mirai.internal.message.protocol.outgoing.* +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.COMPONENTS +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.MESSAGE_TO_RETRY +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.STEP import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.buildComponentStorage import net.mamoe.mirai.internal.network.component.withFallback 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.pipeline.ProcessResult import net.mamoe.mirai.internal.utils.runCoroutineInPlace import net.mamoe.mirai.internal.utils.structureToString @@ -34,10 +41,7 @@ import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor import net.mamoe.mirai.message.data.visitor.accept -import net.mamoe.mirai.utils.MutableTypeSafeMap -import net.mamoe.mirai.utils.TestOnly -import net.mamoe.mirai.utils.buildTypeSafeMap -import net.mamoe.mirai.utils.castUp +import net.mamoe.mirai.utils.* import java.util.* import kotlin.reflect.KClass @@ -72,6 +76,7 @@ internal interface MessageProtocolFacade { messageSourceKind: MessageSourceKind, bot: Bot, builder: MessageChainBuilder, + containingMsg: MsgComm.Msg? = null, ) @@ -127,7 +132,7 @@ internal interface MessageProtocolFacade { groupIdOrZero: Long, messageSourceKind: MessageSourceKind, bot: Bot, - ): MessageChain = buildMessageChain { decode(elements, groupIdOrZero, messageSourceKind, bot, this) } + ): MessageChain = buildMessageChain { decode(elements, groupIdOrZero, messageSourceKind, bot, this, null) } fun copy(): MessageProtocolFacade @@ -235,7 +240,8 @@ internal class MessageProtocolFacadeImpl( groupIdOrZero: Long, messageSourceKind: MessageSourceKind, bot: Bot, - builder: MessageChainBuilder + builder: MessageChainBuilder, + containingMsg: MsgComm.Msg? ) { val pipeline = decoderPipeline @@ -243,6 +249,7 @@ internal class MessageProtocolFacadeImpl( set(MessageDecoderContext.BOT, bot) set(MessageDecoderContext.MESSAGE_SOURCE_KIND, messageSourceKind) set(MessageDecoderContext.GROUP_ID, groupIdOrZero) + set(MessageDecoderContext.CONTAINING_MSG, containingMsg) } runCoroutineInPlace { @@ -297,8 +304,15 @@ internal class MessageProtocolFacadeImpl( ): ProcessResult> { val attributes = createAttributesForOutgoingMessage(target, message, components) - val (context, _) = preprocessorPipeline.process(message.toMessageChain(), attributes) - return outgoingPipeline.process(message.toMessageChain(), context, attributes) + val data = message.toMessageChain() + val (context, _) = preprocessorPipeline.process(data, attributes) + val preprocessed = context.currentMessageChain + + return outgoingPipeline.process( + data, + outgoingPipeline.createContext(preprocessed, context.attributes.plus(MESSAGE_TO_RETRY to preprocessed)), + attributes + ) } override fun copy(): MessageProtocolFacade { @@ -327,12 +341,14 @@ internal class MessageProtocolFacadeImpl( message: Message, context: ComponentStorage ): MutableTypeSafeMap { + val chain = message.toMessageChain() val attributes = buildTypeSafeMap { - set(OutgoingMessagePipelineContext.CONTACT, target.impl()) - set(OutgoingMessagePipelineContext.ORIGINAL_MESSAGE, message) - set(OutgoingMessagePipelineContext.ORIGINAL_MESSAGE_AS_CHAIN, message.toMessageChain()) - set(OutgoingMessagePipelineContext.STEP, SendMessageStep.FIRST) - set(OutgoingMessagePipelineContext.COMPONENTS, thisComponentStorage.withFallback(context)) + set(CONTACT, target.impl()) + set(ORIGINAL_MESSAGE, message) + set(ORIGINAL_MESSAGE_AS_CHAIN, chain) + set(STEP, SendMessageStep.FIRST) + set(COMPONENTS, thisComponentStorage.withFallback(context)) + set(MESSAGE_TO_RETRY, chain) } return attributes } diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/decode/MessageDecoderPipeline.kt b/mirai-core/src/commonMain/kotlin/message/protocol/decode/MessageDecoderPipeline.kt index 6acec0407..969b7ea97 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/decode/MessageDecoderPipeline.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/decode/MessageDecoderPipeline.kt @@ -10,11 +10,11 @@ package net.mamoe.mirai.internal.message.protocol.decode import net.mamoe.mirai.Bot +import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext.Companion.CONTAINING_MSG 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.internal.pipeline.ProcessorPipeline -import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.pipeline.* +import net.mamoe.mirai.internal.utils.structureToStringAndDesensitizeIfAvailable import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.utils.* @@ -29,6 +29,7 @@ internal interface MessageDecoderContext : ProcessorPipelineContext("bot") val MESSAGE_SOURCE_KIND = TypeKey("messageSourceKind") val GROUP_ID = TypeKey("groupId") // zero if not group + val CONTAINING_MSG = TypeKey("containingMsg") } } @@ -45,6 +46,17 @@ internal open class MessageDecoderPipelineImpl : override fun createContext(data: ImMsgBody.Elem, attributes: TypeSafeMap): MessageDecoderContext = MessageDecoderContextImpl(attributes) + override suspend fun process( + data: ImMsgBody.Elem, + context: MessageDecoderContext, + attributes: TypeSafeMap + ): ProcessResult { + context.attributes[CONTAINING_MSG]?.let { msg -> + traceLogging.info { "Processing MsgCommon.Msg: ${msg.structureToStringAndDesensitizeIfAvailable()}" } + } + return super.process(data, context, attributes) + } + companion object { @TestOnly val defaultTraceLogging: MiraiLoggerWithSwitch by lazy { diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/ForwardMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/ForwardMessageProtocol.kt index 502e2f279..07a3acdfb 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/ForwardMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/ForwardMessageProtocol.kt @@ -53,7 +53,7 @@ internal class ForwardMessageProtocol : MessageProtocol() { false ) - currentMessageChain = RichMessage.forwardMessage( + currentMessageChain += RichMessage.forwardMessage( resId = resId, fileName = components[ClockHolder].local.currentTimeSeconds().toString(), forwardMessage = forward, diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralMessageSenderProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralMessageSenderProtocol.kt index 7b9121629..b19fdf8e2 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralMessageSenderProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralMessageSenderProtocol.kt @@ -14,11 +14,13 @@ import net.mamoe.mirai.contact.* import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.SendMessageStep +import net.mamoe.mirai.internal.message.flags.ForceAsFragmentedMessage import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.MESSAGE_TO_RETRY import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.STEP @@ -30,17 +32,20 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcP import net.mamoe.mirai.message.data.AtAll import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.content -import net.mamoe.mirai.message.data.toMessageChain +import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.buildTypeSafeMap +import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.truncated internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_SENDER) { override fun ProcessorCollector.collectProcessorsImpl() { - add(GeneralMessageSender()) + add(GeneralMessageSender(logger)) } - class GeneralMessageSender : OutgoingMessageSender { + class GeneralMessageSender( + private val logger: MiraiLogger, + ) : OutgoingMessageSender { override suspend fun OutgoingMessagePipelineContext.process() { markAsConsumed() @@ -57,28 +62,49 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S contact = contact, message = currentMessageChain, originalMessage = attributes[ORIGINAL_MESSAGE_AS_CHAIN], - fragmented = step == SendMessageStep.FRAGMENTED + fragmented = step == SendMessageStep.FRAGMENTED || currentMessageChain.contains(ForceAsFragmentedMessage) ) { source = it } - sendAllPackets(bot, step, contact, packets) - - val sourceAwait = source?.await() ?: error("Internal error: source is not initialized") - sourceAwait.tryEnsureSequenceIdAvailable() - collect(sourceAwait.createMessageReceipt(contact, true)) + if (sendAllPackets(bot, step, contact, packets)) { + val sourceAwait = source?.await() ?: error("Internal error: source is not initialized") + sourceAwait.tryEnsureSequenceIdAvailable() + collect(sourceAwait.createMessageReceipt(contact, true)) + } } + /** + * @return `true`, if source needs to be added + */ private suspend fun OutgoingMessagePipelineContext.sendAllPackets( bot: AbstractBot, step: SendMessageStep, contact: Contact, packets: List - ) = packets.forEach { packet -> + ): Boolean { + if (!step.allowMultiplePackets && packets.size != 1) { + throw IllegalStateException("Internal error: step $step doesn't allow multiple packets while found ${packets.size} ones.") + } + + packets.forEach { packet -> + if (!sendSinglePacket(bot, packet, step, contact)) return@sendAllPackets false + } + + return true + } + + private suspend fun OutgoingMessagePipelineContext.sendSinglePacket( + bot: AbstractBot, + packet: OutgoingPacket, + step: SendMessageStep, + contact: Contact, + ): Boolean { val originalMessage = attributes[ORIGINAL_MESSAGE] val protocolStrategy = components[MessageProtocolStrategy] val finalMessage = currentMessageChain val resp = protocolStrategy.sendPacket(bot, packet) as MessageSvcPbSendMsg.Response if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) { + logger.info { "STEP $step: message too large." } val next = step.nextStepOrNull() ?: throw MessageTooLargeException( contact, @@ -88,13 +114,16 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S ) // retry with next step - processAlso( - originalMessage.toMessageChain(), + logger.info { "Retrying with STEP $next" } + val (_, receipts) = processAlso( + attributes[MESSAGE_TO_RETRY], extraAttributes = buildTypeSafeMap { set(STEP, next) }, - ) // We expect to get a Receipt from processAlso - return@forEach + ) + check(receipts.size == 1) { "Internal error: expected exactly one receipt collected from sub-process, but found ${receipts.size}." } + // We expect to get a Receipt from processAlso + return false } if (resp is MessageSvcPbSendMsg.Response.ServiceUnavailable) { throw IllegalStateException("Send message to $contact failed, server service is unavailable.") @@ -117,7 +146,7 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S check(resp is MessageSvcPbSendMsg.Response.SUCCESS) { "Send message failed: $resp" } - + return true } } 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 8d392ab37..cd6e16180 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt @@ -10,6 +10,8 @@ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder @@ -45,24 +47,35 @@ internal class MusicShareProtocol : MessageProtocol() { } } - private class Sender : OutgoingMessageSender { + open class Sender : OutgoingMessageSender { override suspend fun OutgoingMessagePipelineContext.process() { - val contact = attributes[CONTACT] - val bot = contact.bot val musicShare = currentMessageChain[MusicShare] ?: return + markAsConsumed() - val packet = MusicSharePacket( - bot.client, musicShare, contact.id, - targetKind = if (contact is Group) MessageSourceKind.GROUP else MessageSourceKind.FRIEND // always FRIEND - ) - val result = bot.network.sendAndExpect(packet) - result.pkg.checkSuccess("send music share") - + val contact = attributes[CONTACT] val strategy = components[MessageProtocolStrategy] + val bot = contact.bot + + sendMusicSharePacket(bot, musicShare, contact, strategy) + val source = strategy.constructSourceForSpecialMessage(attributes[ORIGINAL_MESSAGE_AS_CHAIN], 3116) source.tryEnsureSequenceIdAvailable() collect(source.createMessageReceipt(contact, true)) } + + protected open suspend fun sendMusicSharePacket( + bot: QQAndroidBot, + musicShare: MusicShare, + contact: AbstractContact, + strategy: MessageProtocolStrategy<*> + ) { + val packet = MusicSharePacket( + bot.client, musicShare, contact.id, + targetKind = if (contact is Group) MessageSourceKind.GROUP else MessageSourceKind.FRIEND // always FRIEND + ) + val result = strategy.sendPacket(bot, packet) + result.pkg.checkSuccess("send music share") + } } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/MessageProtocolStrategy.kt b/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/MessageProtocolStrategy.kt index e6f44a3f6..e05259e80 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/MessageProtocolStrategy.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/MessageProtocolStrategy.kt @@ -25,6 +25,7 @@ import net.mamoe.mirai.internal.network.components.ClockHolder.Companion.clock import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.* import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.OnlineMessageSource @@ -34,6 +35,13 @@ internal interface MessageProtocolStrategy { return bot.network.sendAndExpect(packet) } + suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacketWithRespType): R { + return bot.network.sendAndExpect(packet) + } + + /** + * If [fragmented] is false, returned list must contain at most one element. + */ suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: C, diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/OutgoingMessagePipeline.kt b/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/OutgoingMessagePipeline.kt index 32b992735..094961cce 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/OutgoingMessagePipeline.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/OutgoingMessagePipeline.kt @@ -16,10 +16,7 @@ import net.mamoe.mirai.internal.contact.SendMessageStep import net.mamoe.mirai.internal.message.source.ensureSequenceIdAvailable import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.handler.logger -import net.mamoe.mirai.internal.pipeline.AbstractProcessorPipeline -import net.mamoe.mirai.internal.pipeline.PipelineConfiguration -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.estimateLength import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* @@ -38,12 +35,21 @@ internal interface OutgoingMessagePipeline : internal open class OutgoingMessagePipelineImpl : AbstractProcessorPipeline>( - PipelineConfiguration(stopWhenConsumed = false), @OptIn(TestOnly::class) defaultTraceLogging + PipelineConfiguration(stopWhenConsumed = true), @OptIn(TestOnly::class) defaultTraceLogging ), OutgoingMessagePipeline { inner class OutgoingMessagePipelineContextImpl( attributes: TypeSafeMap, override var currentMessageChain: MessageChain - ) : OutgoingMessagePipelineContext, BaseContextImpl(attributes) + ) : OutgoingMessagePipelineContext, BaseContextImpl(attributes) { + override suspend fun processAlso( + data: OutgoingMessagePipelineInput, + extraAttributes: TypeSafeMap + ): ProcessResult>, MessageReceipt<*>> { + return super.processAlso(data, extraAttributes).also { (context, _) -> + this.currentMessageChain = (context as OutgoingMessagePipelineContext).currentMessageChain + } + } + } override fun createContext( data: OutgoingMessagePipelineInput, attributes: TypeSafeMap @@ -61,6 +67,9 @@ internal open class OutgoingMessagePipelineImpl : internal interface OutgoingMessagePipelineContext : ProcessorPipelineContext> { + /** + * Current message chain updated throughout the process. Will be updated from the [sub-processes][processAlso]. + */ var currentMessageChain: MessageChain suspend fun MessageSource.tryEnsureSequenceIdAvailable() { @@ -110,6 +119,10 @@ internal interface OutgoingMessagePipelineContext : */ val ORIGINAL_MESSAGE_AS_CHAIN = TypeKey("originalMessageAsChain") + /** + * Message chain used when retrying with next [step][SendMessageStep]s. + */ + val MESSAGE_TO_RETRY = TypeKey("messageToRetry") /** * Message target diff --git a/mirai-core/src/commonMain/kotlin/message/visitor/MessageVisitorEx.kt b/mirai-core/src/commonMain/kotlin/message/visitor/MessageVisitorEx.kt index 25203f976..463718ec5 100644 --- a/mirai-core/src/commonMain/kotlin/message/visitor/MessageVisitorEx.kt +++ b/mirai-core/src/commonMain/kotlin/message/visitor/MessageVisitorEx.kt @@ -49,6 +49,10 @@ internal interface MessageVisitorEx : MessageVisitor { return visitInternalFlagOnlyMessage(message, data) } + fun visitForceAsFragmentedMessage(message: ForceAsFragmentedMessage, data: D): R { + return visitInternalFlagOnlyMessage(message, data) + } + fun visitDontAsLongMessage(message: DontAsLongMessage, data: D): R { return visitInternalFlagOnlyMessage(message, data) } diff --git a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt index 59b2ead50..4bc9ec14e 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt @@ -30,7 +30,6 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushP import net.mamoe.mirai.internal.network.toPacket import net.mamoe.mirai.internal.pipeline.* import net.mamoe.mirai.internal.utils.io.ProtocolStruct -import net.mamoe.mirai.internal.utils.structureToStringAndDesensitizeIfAvailable import net.mamoe.mirai.utils.* import kotlin.reflect.KClass @@ -90,17 +89,6 @@ internal open class NoticeProcessorPipelineImpl protected constructor( ) : BaseContextImpl(attributes), NoticePipelineContext { override val bot: QQAndroidBot get() = this@NoticeProcessorPipelineImpl.bot - - override suspend fun processAlso( - data: ProtocolStruct, - attributes: TypeSafeMap - ): ProcessResult, Packet> { - traceLogging.info { "processAlso: data=${data.structureToStringAndDesensitizeIfAvailable()}" } - return process(data, this.attributes + attributes).also { packets -> - this.collected.data += packets.collected - traceLogging.info { "processAlso: result=$packets" } - } - } } override fun handleExceptionInProcess( 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 f75d75fc9..9b328f5fe 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 @@ -16,6 +16,7 @@ import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.internal.utils.io.NestedStructure import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer import net.mamoe.mirai.internal.utils.io.ProtoBuf +import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.structureToStringIfAvailable import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.unzip @@ -1005,11 +1006,18 @@ internal class ImMsgBody : ProtoBuf { @ProtoNumber(6) @JvmField val type: Int = 0, @ProtoNumber(7) @JvmField val richMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, + @NestedStructure(SrcMsgDesensitizer::class) @ProtoNumber(9) @JvmField val srcMsg: ByteArray? = null, @ProtoNumber(10) @JvmField val toUin: Long = 0L, @ProtoNumber(11) @JvmField val troopName: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf + internal object SrcMsgDesensitizer : NestedStructureDesensitizer { + override fun deserialize(context: SourceMsg, byteArray: ByteArray): MsgComm.Msg { + return byteArray.loadAs(MsgComm.Msg.serializer()) + } + } + @Serializable internal class Text( @ProtoNumber(1) @JvmField val str: String = "", diff --git a/mirai-core/src/commonMain/kotlin/pipeline/ProcessorPipeline.kt b/mirai-core/src/commonMain/kotlin/pipeline/ProcessorPipeline.kt index 48db5e7b7..f9ef20f1d 100644 --- a/mirai-core/src/commonMain/kotlin/pipeline/ProcessorPipeline.kt +++ b/mirai-core/src/commonMain/kotlin/pipeline/ProcessorPipeline.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.internal.pipeline import net.mamoe.mirai.internal.message.contextualBugReportException +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext import net.mamoe.mirai.internal.network.components.NoticeProcessor import net.mamoe.mirai.internal.utils.structureToStringAndDesensitizeIfAvailable import net.mamoe.mirai.utils.* @@ -39,6 +40,7 @@ internal interface ProcessorPipeline

, C : ProcessorPipelineC fun registerBefore(processor: P): DisposableRegistry + fun createContext(data: D, attributes: TypeSafeMap): C /** * Process using the [context]. @@ -139,7 +141,7 @@ internal interface ProcessorPipelineContext { annotation class ConsumptionMarker // to give an explicit color. /** - * Fire the [data] into the processor pipeline, and collect the results to current [collected]. + * Fire the [data] into the processor pipeline, and collect the results to current [collected], updating *some mutable properties* in contexts, e.g. [OutgoingMessagePipelineContext.currentMessageChain] * * @param extraAttributes extra attributes * @return result collected from processors. This would also have been collected to this context (where you call [processAlso]). @@ -216,8 +218,6 @@ protected constructor( } } - protected abstract fun createContext(data: D, attributes: TypeSafeMap): C - abstract inner class BaseContextImpl( attributes: TypeSafeMap, ) : AbstractProcessorPipelineContext(attributes, traceLogging) { @@ -226,7 +226,10 @@ protected constructor( extraAttributes: TypeSafeMap ): ProcessResult, R> { traceLogging.info { "processAlso: data=${data.structureToStringAndDesensitizeIfAvailable()}" } - return process(data, this.attributes + extraAttributes).also { + traceLogging.info { "extraAttributes = $extraAttributes" } + val newAttributes = this.attributes + extraAttributes + traceLogging.info { "newAttributes = $newAttributes" } + return process(data, newAttributes).also { this.collected.data += it.collected traceLogging.info { "processAlso: result=$it" } } 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 f1e9b4971..15edd15df 100644 --- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt @@ -16,7 +16,9 @@ import kotlinx.io.core.ByteReadPacket import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.AbstractBot +import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.data.inferMessageSourceKind import net.mamoe.mirai.internal.message.protocol.MessageProtocol @@ -41,6 +43,7 @@ import net.mamoe.mirai.internal.notice.processors.GroupExtensions import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.Clock +import net.mamoe.mirai.utils.lateinitMutableProperty import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.toUHexString import org.junit.jupiter.api.AfterEach @@ -52,9 +55,19 @@ import kotlin.test.assertEquals import kotlin.test.asserter internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest(), GroupExtensions { + init { + System.setProperty("mirai.message.protocol.log.full", "true") + System.setProperty("mirai.message.outgoing.pipeline.log.full", "true") + } + + override fun createAccount(): BotAccount = BotAccount(1230001L, "pwd") protected abstract val protocols: Array - protected var defaultTarget: ContactOrBot? = null + protected var defaultTarget: ContactOrBot by lateinitMutableProperty { + bot.addGroup(123, 1230003).apply { + addMember(1230003, "user3", MemberPermission.OWNER) + } + } private var decoderLoggerEnabled = false private var encoderLoggerEnabled = false @@ -144,8 +157,8 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler var messages: MessageChainBuilder = MessageChainBuilder() var groupIdOrZero: Long = 0 - var messageSourceKind: MessageSourceKind = MessageSourceKind.GROUP - var target: ContactOrBot? = defaultTarget + var target: ContactOrBot = defaultTarget + var messageSourceKind: MessageSourceKind by lateinitMutableProperty { target.inferMessageSourceKind() } var withGeneralFlags = true var isForward = false @@ -157,12 +170,10 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler messages.addAll(message) } - fun target(target: ContactOrBot?) { + fun target(target: ContactOrBot) { this.target = target - if (target != null) { - messageSourceKind = target.inferMessageSourceKind() - } + messageSourceKind = target.inferMessageSourceKind() if (target is Group) { groupIdOrZero = target.id @@ -232,51 +243,53 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler // sending /////////////////////////////////////////////////////////////////////////// - init { - components[MessageProtocolStrategy] = object : MessageProtocolStrategy { - override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet { - assertEquals(0x123, packet.sequenceId) - return MessageSvcPbSendMsg.Response.SUCCESS - } - - override suspend fun createPacketsForGeneralMessage( - client: QQAndroidClient, - contact: AbstractContact, - message: MessageChain, - originalMessage: MessageChain, - fragmented: Boolean, - sourceCallback: (Deferred) -> Unit - ): List { - sourceCallback(CompletableDeferred(constructSourceForSpecialMessage(originalMessage, 1000))) - return listOf(OutgoingPacket("Test", "test", 0x123, ByteReadPacket.Empty)) - } - - override suspend fun constructSourceForSpecialMessage( - originalMessage: MessageChain, - fromAppId: Int - ): OnlineMessageSource.Outgoing { - return when (val defaultTarget = defaultTarget) { - is Group -> OnlineMessageSourceToGroupImpl( - coroutineScope = defaultTarget, - internalIds = intArrayOf(1), - time = 1, - originalMessage = originalMessage, - sender = bot, - target = defaultTarget - ) - is Friend -> OnlineMessageSourceToFriendImpl( - sequenceIds = intArrayOf(1), - internalIds = intArrayOf(1), - time = 1, - originalMessage = originalMessage, - sender = bot, - target = defaultTarget - ) - else -> error("Unexpected target: $defaultTarget") - } - } - + open inner class TestMessageProtocolStrategy : MessageProtocolStrategy { + override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet { + assertEquals(0x123, packet.sequenceId) + return MessageSvcPbSendMsg.Response.SUCCESS } + + override suspend fun createPacketsForGeneralMessage( + client: QQAndroidClient, + contact: AbstractContact, + message: MessageChain, + originalMessage: MessageChain, + fragmented: Boolean, + sourceCallback: (Deferred) -> Unit + ): List { + sourceCallback(CompletableDeferred(constructSourceForSpecialMessage(originalMessage, 1000))) + return listOf(OutgoingPacket("Test", "test", 0x123, ByteReadPacket.Empty)) + } + + override suspend fun constructSourceForSpecialMessage( + originalMessage: MessageChain, + fromAppId: Int + ): OnlineMessageSource.Outgoing { + return when (val defaultTarget = defaultTarget) { + is Group -> OnlineMessageSourceToGroupImpl( + coroutineScope = defaultTarget, + internalIds = intArrayOf(1), + time = 1, + originalMessage = originalMessage, + sender = bot, + target = defaultTarget + ) + is Friend -> OnlineMessageSourceToFriendImpl( + sequenceIds = intArrayOf(1), + internalIds = intArrayOf(1), + time = 1, + originalMessage = originalMessage, + sender = bot, + target = defaultTarget + ) + else -> error("Unexpected target: $defaultTarget") + } + } + + } + + init { + components[MessageProtocolStrategy] = TestMessageProtocolStrategy() components[HighwayUploader] = object : HighwayUploader { override suspend fun uploadMessages( contact: AbstractContact, @@ -300,7 +313,7 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler fun runWithFacade(action: suspend MessageProtocolFacade.() -> Unit) { runBlockingUnit { facadeOf(*protocols).run { action() } - MessageProtocolFacade.INSTANCE.run { action() } + MessageProtocolFacade.INSTANCE.copy().run { action() } } } diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/ForwardMessageProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/ForwardMessageProtocolTest.kt new file mode 100644 index 000000000..729ea7cce --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/ForwardMessageProtocolTest.kt @@ -0,0 +1,146 @@ +/* + * 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.internal.message.LightMessageRefiner.dropMiraiInternalFlags +import net.mamoe.mirai.internal.message.data.ForwardMessageInternal +import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck +import net.mamoe.mirai.internal.message.protocol.MessageProtocol +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.cast +import net.mamoe.mirai.utils.castUp +import net.mamoe.mirai.utils.getRandomString +import kotlin.random.Random +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class ForwardMessageProtocolTest : AbstractMessageProtocolTest() { + override val protocols: Array = + arrayOf( + TextProtocol(), + ImageProtocol(), + ForwardMessageProtocol(), + GeneralMessageSenderProtocol(), + ) + + init { + defaultTarget = bot.addGroup(123, 1230003).apply { + addMember(1230003, "user3", MemberPermission.OWNER) + } + } + + @Test + fun precondition() { + assertEquals(getRandomString(5000, Random(1)), getRandomString(5000, Random(1))) + assertMessageEquals( + "test".toPlainText() + getRandomString(5000, Random(1)) + + Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200), + "test".toPlainText() + getRandomString(5000, Random(1)) + + Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200) + ) + } + + @Test + fun `can convert ForwardMessage to ForwardMessageInternal`() { + var message = buildForwardMessage(defaultTarget.cast()) { + currentTime = 16000000 + 1 named "Name1" says "Hello" + }.toMessageChain() + + message += IgnoreLengthCheck + + runWithFacade { + preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> + val receipt = receipts.single() + assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) + + assertMessageEquals( + ForwardMessageInternal( + """ 群聊的聊天记录 Name1: Hello

查看1条转发消息 """, + "(size=1)501389E3070B20D87A80A67961F4EA0E", + null, + origin = message[ForwardMessage] + ) + IgnoreLengthCheck, context.currentMessageChain + ) + } + } + } + + @Test + fun `can convert empty ForwardMessage`() { + val message = buildForwardMessage(defaultTarget.cast()) {}.toMessageChain() + + runWithFacade { + preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> + val receipt = receipts.single() + assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) + + assertMessageEquals( + ForwardMessageInternal( + """ 群聊的聊天记录 查看0条转发消息 """, + "(size=0)D41D8CD98F00B204E9800998ECF8427E", + null, + origin = message[ForwardMessage] + ), context.currentMessageChain + ) + } + } + } + + // // TODO: 2022/5/23 test for download ForwardMessage +// @Test +// fun `can receive and download ForwardMessage`() { +// val message = runBlocking { +// runWithFacade { +// net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( +// msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( +// fromUin = 1230001, +// toUin = 1230002, +// msgType = 166, +// c2cCmd = 11, +// msgSeq = 34437, +// msgTime = 1653334690, +// msgUid = 72057594524997436, +// wseqInC2cMsghead = 34437, +// ), +// msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( +// richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( +// attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( +// codePage = 0, +// time = 1653334690, +// random = 487069500, +// effect = 0, +// charSet = 134, +// pitchAndFamily = 2, +// fontName = "宋体", +// ), +// elems = mutableListOf( +// net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( +// richMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichMsg( +// template1 = "01 78 9C B5 92 4F 6F D3 30 18 C6 BF 8A 65 0E 39 4C 23 4D FA 2F A0 38 53 B7 29 EB D8 12 89 65 45 FC 11 9A DC C4 C9 2C D9 49 65 3B DD DA DB 24 0E 08 0E 88 03 17 04 42 48 70 40 42 C0 89 DB 3E 4E BB 7E 0C 1C 77 12 E3 80 C4 85 F8 10 47 AF F3 F3 F3 3C EF EB 6F 9D 73 06 A6 44 48 5A 95 C8 72 6E B7 2C 40 CA B4 CA 68 59 20 6B 74 1C 6E 7A 16 90 0A 97 19 66 55 49 90 35 23 D2 02 5B 81 CF 65 01 24 11 53 9A 92 FD 5D 04 DB 5D 08 14 E1 13 86 95 F9 76 20 C0 A9 6A 98 70 4A C9 59 54 33 45 23 59 40 30 16 94 E4 08 3E 59 5D BC 58 7C FA B2 FA F6 63 71 F9 E6 29 04 FC 44 10 49 33 04 C7 C7 E1 91 3C 4C F2 78 F4 28 89 C2 68 7B 63 E0 B9 03 B1 71 FF E1 78 87 1E A4 C5 38 8E 6B 6F 6F BB FF 60 CF 3E 9A 77 0E 1E C7 C3 D1 4E 31 BC 37 8F C2 DD 3C 12 83 44 34 A8 9C 32 12 63 4E 10 EC 3B 2D BD BC 8E 73 C7 E9 BA 1D A7 D7 ED 35 3A 93 9A 23 E8 42 20 AB 5A A4 44 CB DA D7 17 B7 20 A8 05 43 10 82 9C E1 42 3B D2 0E 32 1D 4C 42 8B D2 54 F9 B5 87 D0 94 5B 30 F0 A9 76 0C 18 9E 55 B5 5A 3B 6E CE 2B AA 63 39 A1 6B 22 5E BF 03 5F 51 C5 08 90 74 AE 45 B5 3B 1A 86 CF 0F 69 49 A4 D1 C1 F4 2E 99 E0 54 D7 1C 17 06 43 CA 1D CF E3 15 B9 7A FB EC 66 4C BE 6D 28 7F C2 DC 1E 04 69 C5 2A 81 E0 AD BE 79 FE 11 7E 17 80 FC 2C A3 ED FF 4C 3D 15 E0 94 66 19 D1 19 E6 98 49 A2 53 57 33 46 4C 3A 76 E0 CB 9A 73 2C 66 7F BF 36 58 7E F8 7C F5 EE A5 BB 7C FF 71 75 F9 75 F1 EA F5 F2 E7 F3 E5 C5 77 DF BE FE 33 F0 ED A6 0D 9A 64 9A 09 4A D3 F7 9B B1 41 40 D3 66 0E 7F 4F A4 DE 4D 26 4D 67 36 1D 23 C2 D6 C3 1C FC 02 21 C3 0A 7E".hexToBytes(), +// serviceId = 35, +// ), +// ), +// net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( +// generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( +// pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 CA 04 00 D2 05 02 08 6A".hexToBytes(), +// ), +// ), +// net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( +// ), +// ), +// ), +// ), +// ).toMessageChainOnline(bot, facade = this) +// } +// } +// } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/GeneralMessageSenderProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/GeneralMessageSenderProtocolTest.kt new file mode 100644 index 000000000..50ada7e88 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/GeneralMessageSenderProtocolTest.kt @@ -0,0 +1,114 @@ +/* + * 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 kotlinx.coroutines.Deferred +import net.mamoe.mirai.internal.AbstractBot +import net.mamoe.mirai.internal.contact.AbstractContact +import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags +import net.mamoe.mirai.internal.message.flags.ForceAsFragmentedMessage +import net.mamoe.mirai.internal.message.protocol.MessageProtocol +import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy +import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.QQAndroidClient +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OnlineMessageSource +import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.message.data.messageChainOf +import net.mamoe.mirai.utils.castUp +import org.junit.jupiter.api.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +internal class GeneralMessageSenderProtocolTest : AbstractMessageProtocolTest() { + override val protocols: Array = arrayOf(TextProtocol(), GeneralMessageSenderProtocol()) + + @Test + fun `can convert messages failed to send to fragmented`() { + val message = messageChainOf(PlainText("test"), PlainText("test")) + + runWithFacade { + components[MessageProtocolStrategy] = object : TestMessageProtocolStrategy() { + var count = 0 + override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet { + println("MessageProtocolStrategy.sendPacket called: $count") + if (count++ <= 1) { // fail the first and second attempt + return MessageSvcPbSendMsg.Response.MessageTooLarge + } + return super.sendPacket(bot, packet) + } + + override suspend fun createPacketsForGeneralMessage( + client: QQAndroidClient, + contact: AbstractContact, + message: MessageChain, + originalMessage: MessageChain, + fragmented: Boolean, + sourceCallback: (Deferred) -> Unit + ): List { + if (count == 2) { + assertTrue { fragmented } + } else { + assertFalse { fragmented } + } + return super.createPacketsForGeneralMessage( + client, + contact, + message, + originalMessage, + fragmented, + sourceCallback + ) + } + } + preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> + val receipt = receipts.single() + assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) + assertMessageEquals(message.dropMiraiInternalFlags(), context.currentMessageChain) + } + } + } + + @Test + fun `can convert messages to fragmented`() { + val message = messageChainOf(PlainText("test"), PlainText("test"), ForceAsFragmentedMessage) + + runWithFacade { + components[MessageProtocolStrategy] = object : TestMessageProtocolStrategy() { + override suspend fun createPacketsForGeneralMessage( + client: QQAndroidClient, + contact: AbstractContact, + message: MessageChain, + originalMessage: MessageChain, + fragmented: Boolean, + sourceCallback: (Deferred) -> Unit + ): List { + assertTrue { fragmented } + return super.createPacketsForGeneralMessage( + client, + contact, + message, + originalMessage, + fragmented, + sourceCallback + ) + } + } + preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> + val receipt = receipts.single() + assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) + assertMessageEquals(message, context.currentMessageChain) + } + } + } + +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/LongMessageProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/LongMessageProtocolTest.kt index 702e6c1b4..a5848fb2c 100644 --- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/LongMessageProtocolTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/LongMessageProtocolTest.kt @@ -10,13 +10,19 @@ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.flags.ForceAsLongMessage import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck import net.mamoe.mirai.internal.message.protocol.MessageProtocol +import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy +import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.repeat +import net.mamoe.mirai.message.data.toMessageChain import net.mamoe.mirai.message.data.toPlainText import net.mamoe.mirai.utils.castUp import net.mamoe.mirai.utils.getRandomString @@ -86,4 +92,46 @@ internal class LongMessageProtocolTest : AbstractMessageProtocolTest() { } } } + + @Test + fun `can convert messages failed to send at FIRST step to LongMessageInternal`() { + val message = "test".toPlainText().toMessageChain() + + runWithFacade { + components[MessageProtocolStrategy] = object : TestMessageProtocolStrategy() { + var count = 0 + override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet { + println("MessageProtocolStrategy.sendPacket called: $count") + if (count++ == 0) { // fail the first attempt + return MessageSvcPbSendMsg.Response.MessageTooLarge + } + return super.sendPacket(bot, packet) + } + } + preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> + val receipt = receipts.single() + assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) + + assertMessageEquals( + LongMessageInternal( + """ + + + + test + + 点击查看完整消息 + + + + """.trimIndent(), "(size=1)8698F15C27DA63648CEF9A93EA76B084" + ), context.currentMessageChain + ) + } + } + } } \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/MusicShareProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/MusicShareProtocolTest.kt index 5f785770c..a08176f62 100644 --- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/MusicShareProtocolTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/MusicShareProtocolTest.kt @@ -10,19 +10,26 @@ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.protocol.MessageProtocol +import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageProcessorAdapter +import net.mamoe.mirai.internal.pipeline.replaceProcessor import net.mamoe.mirai.message.data.LightApp import net.mamoe.mirai.message.data.MessageOrigin import net.mamoe.mirai.message.data.MessageOriginKind import net.mamoe.mirai.message.data.MusicKind.NeteaseCloudMusic import net.mamoe.mirai.message.data.MusicShare +import net.mamoe.mirai.utils.castUp import net.mamoe.mirai.utils.hexToBytes import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import kotlin.test.assertTrue internal class MusicShareProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array = - arrayOf(TextProtocol(), MusicShareProtocol(), RichMessageProtocol()) + arrayOf(TextProtocol(), MusicShareProtocol(), RichMessageProtocol(), GeneralMessageSenderProtocol()) @BeforeEach fun `init group`() { @@ -73,5 +80,40 @@ internal class MusicShareProtocolTest : AbstractMessageProtocolTest() { }.doDecoderChecks() } - // no encoder. specially handled, no test for now. + @Test + fun `can send MusicShare to group`() { + val message = MusicShare( + kind = NeteaseCloudMusic, + title = "ジェリーフィッシュ", + summary = "Yunomi/ローラーガール", + jumpUrl = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46", + pictureUrl = "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg", + musicUrl = "http://music.163.com/song/media/outer/url?id=562591636&userid=324076307&sc=wmv&tn=", + brief = "[分享]ジェリーフィッシュ", + ) + + runWithFacade { + assertTrue { + outgoingPipeline.replaceProcessor( + { it is MusicShareProtocol.Sender }, + OutgoingMessageProcessorAdapter(object : MusicShareProtocol.Sender() { + override suspend fun sendMusicSharePacket( + bot: QQAndroidBot, + musicShare: MusicShare, + contact: AbstractContact, + strategy: MessageProtocolStrategy<*> + ) { + // nop + } + }) + ) + } + preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> + val receipt = receipts.single() + assertMessageEquals(message, receipt.source.originalMessage) + assertMessageEquals(message, context.currentMessageChain) + } + } + } + } \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/impl/QuoteReplyProtocolTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/impl/QuoteReplyProtocolTest.kt index 6ac218a02..091ec78c8 100644 --- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/QuoteReplyProtocolTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/QuoteReplyProtocolTest.kt @@ -7,48 +7,16 @@ * 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 - */ - -/* - * 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.protocol.MessageProtocol -import net.mamoe.mirai.internal.message.protocol.decodeAndRefineLight -import net.mamoe.mirai.message.data.Face -import net.mamoe.mirai.message.data.MessageSourceKind +import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData +import net.mamoe.mirai.internal.message.toMessageChainOnline +import net.mamoe.mirai.internal.utils.runCoroutineInPlace +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource.Key.quote +import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.message.data.QuoteReply import net.mamoe.mirai.message.data.messageChainOf import net.mamoe.mirai.utils.hexToBytes import org.junit.jupiter.api.Test @@ -57,8 +25,9 @@ internal class QuoteReplyProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array = arrayOf(QuoteReplyProtocol(), TextProtocol()) @Test - fun `decode group reference group`() { + fun `decode referencing online group message in group`() { buildCodingChecks { + targetGroup() elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( @@ -82,38 +51,388 @@ internal class QuoteReplyProtocolTest : AbstractMessageProtocolTest() { ), ), ) -// message( -// QuoteReply( -//// OfflineMessageSourceImplData( -//// -//// ) -// ) -// ) - targetGroup() - useOrdinaryEquality() + message( + QuoteReply( + OfflineMessageSourceImplData( + ids = intArrayOf(1803), + internalIds = intArrayOf(539443883), + time = 1653147259, + originalMessage = messageChainOf(PlainText("a")), + kind = messageSourceKind, + fromId = 1230001, + targetId = 1, + botId = bot.id, + ) + ), PlainText("s") + ) }.doDecoderChecks() } @Test - fun `can decode`() { - doDecoderChecks( - messageChainOf(Face(Face.YIN_XIAN)), - ) { - decodeAndRefineLight( - listOf( - net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( - face = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Face( - index = 108, - old = "14 AD".hexToBytes(), + fun `encode referencing offline group message in group`() { + buildCodingChecks { + targetGroup() + elem( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( + origSeqs = intArrayOf(-31257), + senderUin = 1230001, + time = 1653326514, + flag = 1, + elems = mutableListOf( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "a", + ), + ), ), - ) + // mirai's OfflineMessageSource has no enough information to create 'srcMsg' + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "s", + ), ), - groupIdOrZero = 0, - MessageSourceKind.GROUP, - bot, ) - } + message( + QuoteReply( + OfflineMessageSourceImplData( + ids = intArrayOf(-31257), + internalIds = intArrayOf(1860746670), + time = 1653326514, + originalMessage = messageChainOf(PlainText("a")), + kind = messageSourceKind, + fromId = 1230001, + targetId = 1994701021, + botId = bot.id, + ) + ), PlainText("s") + ) + }.doEncoderChecks() + } + + private val onlineIncomingGroupMessage = runCoroutineInPlace { + net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( + msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( + fromUin = 1230001, + toUin = 1230002, + msgType = 166, + c2cCmd = 11, + msgSeq = 31245, + msgTime = 1653330864, + msgUid = 72057594652150074, + wseqInC2cMsghead = 31245, + ), + msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( + richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( + attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( + codePage = 0, + time = 1653330864, + random = 614222138, + size = 9, + effect = 0, + charSet = 134, + pitchAndFamily = 0, + fontName = "Helvetica", + ), + elems = mutableListOf( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "a", + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( + pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 61".hexToBytes(), + ), + ), + ), + ), + ), + ).toMessageChainOnline(bot) + } + + @Test + fun `encode referencing online incoming group message in group`() { + buildCodingChecks { + targetGroup() + elem( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( + origSeqs = intArrayOf(31245), + senderUin = 1230001, + time = 1653330864, + flag = 1, + elems = mutableListOf( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "a", + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( + pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 61".hexToBytes(), + ), + ), + ), + pbReserve = "18 BA 92 F1 A4 02".hexToBytes(), + srcMsg = "0A 20 08 B1 89 4B 10 B2 89 4B 18 A6 01 20 0B 28 8D F4 01 30 B0 A7 AF 94 06 38 BA 92 F1 A4 02 E0 01 01 1A 2D 0A 2B 12 05 0A 03 0A 01 61 12 00 12 1C AA 02 19 9A 01 16 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 61 12 02 4A 00".hexToBytes(), + toUin = 1230002, + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "s", + ), + ) + ) + + message(onlineIncomingGroupMessage.quote(), PlainText("s")) + }.doEncoderChecks() + } + + // stranger and group temp are almost the same for friend. + @Test + fun `decode referencing online incoming private message in friend`() { + buildCodingChecks { + targetFriend() + elem( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( + origSeqs = intArrayOf(34279), + senderUin = 1230001, + time = 1653326514, + flag = 1, + elems = mutableListOf( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "a", + ), + ), + ), + pbReserve = "18 AE FB A2 F7 86 80 80 80 01".hexToBytes(), + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "s", + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( + pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 51".hexToBytes(), + ), + ), + ) + message( + QuoteReply( + OfflineMessageSourceImplData( + ids = intArrayOf(34279), + internalIds = intArrayOf(1860746670), + time = 1653326514, + originalMessage = messageChainOf(PlainText("a")), + kind = messageSourceKind, + fromId = 1230001, + targetId = 0, // the referenced message was actually sending from friend 1230001 to bot. + botId = bot.id, + ) + ), PlainText("s") + ) + }.doDecoderChecks() + } + + @Test + fun `decode referencing online outgoing private message in friend`() { + buildCodingChecks { + targetFriend() + elem( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( + origSeqs = intArrayOf(49858), + senderUin = 1230002, + time = 1653329998, + flag = 1, + elems = mutableListOf( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "b", + ), + ), + ), + pbReserve = "18 C3 94 C4 B3 84 80 80 80 01".hexToBytes(), + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "s", + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( + pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 5E".hexToBytes(), + ), + ), + ) + message( + QuoteReply( + OfflineMessageSourceImplData( + ids = intArrayOf(49858), + internalIds = intArrayOf(1181813315), + time = 1653329998, + originalMessage = messageChainOf(PlainText("b")), + kind = messageSourceKind, + fromId = 1230002, // bot id + targetId = 0, // the referenced message was actually sending from bot to the friend 1230001. + botId = bot.id, + ) + ), PlainText("s") + ) + }.doDecoderChecks() + } + + @Test + fun `encode referencing offline private message in friend`() { + buildCodingChecks { + targetFriend() + + elem( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( + origSeqs = intArrayOf(-31257), + senderUin = 1230001, + time = 1653326514, + flag = 1, + elems = mutableListOf( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "a", + ), + ), + ), + // mirai's OfflineMessageSource has no enough information to create 'srcMsg' + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "s", + ), + ), + ) + message( + QuoteReply( + OfflineMessageSourceImplData( + ids = intArrayOf(-31257), + internalIds = intArrayOf(1860746670), + time = 1653326514, + originalMessage = messageChainOf(PlainText("a")), + kind = messageSourceKind, + fromId = 1230001, + targetId = 1994701021, + botId = bot.id, + ) + ), PlainText("s") + ) + }.doEncoderChecks() + } + + init { + bot.addFriend(1230001) + } + + private val onlineIncomingFriendMessage: MessageChain = runCoroutineInPlace { + net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( + msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( + fromUin = 1230001, + toUin = 1230002, + msgType = 166, + c2cCmd = 11, + msgSeq = 31222, + msgTime = 1653328003, + msgUid = 72057595832827069, + wseqInC2cMsghead = 31222, + ), + msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( + richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( + attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( + codePage = 0, + time = 1653328002, + random = 1794899133, + size = 9, + effect = 0, + charSet = 134, + pitchAndFamily = 0, + fontName = "Helvetica", + ), + elems = mutableListOf( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "a", + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( + pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 4F".hexToBytes(), + ), + ), + ), + ), + ), + ).toMessageChainOnline(bot) + } + + + @Test + fun `encode referencing online incoming private message in friend`() { + buildCodingChecks { + targetFriend() + elem( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( + origSeqs = intArrayOf(31222), + senderUin = 1230001, + time = 1653328003, + flag = 1, + elems = mutableListOf( + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "a", + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + ), // Don't worry about this empty Elem, it's from official + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( + pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 4F".hexToBytes(), + ), + ), + ), + pbReserve = "18 BD F9 EF D7 06".hexToBytes(), + // srcMsg is available for online source + srcMsg = "0A 20 08 B1 89 4B 10 B2 89 4B 18 A6 01 20 0B 28 F6 F3 01 30 83 91 AF 94 06 38 BD F9 EF D7 06 E0 01 01 1A 2D 0A 2B 12 05 0A 03 0A 01 61 12 00 12 1C AA 02 19 9A 01 16 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 4F 12 02 4A 00".hexToBytes(), + toUin = 1230002, + ), + ), + net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( + text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( + str = "s", + ), + ) + ) + + message(onlineIncomingFriendMessage.quote(), PlainText("s")) + }.doEncoderChecks() } private fun CodingChecksBuilder.targetGroup() { @@ -121,7 +440,7 @@ internal class QuoteReplyProtocolTest : AbstractMessageProtocolTest() { } private fun CodingChecksBuilder.targetFriend() { - target(bot.addFriend(1)) + target(bot.getFriendOrFail(1230001)) } diff --git a/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt b/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt index 093c3577e..62844d39f 100644 --- a/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt +++ b/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt @@ -13,6 +13,7 @@ package net.mamoe.mirai.internal.network.framework import kotlinx.coroutines.SupervisorJob import net.mamoe.mirai.Bot +import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage @@ -31,6 +32,8 @@ import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.utils.MiraiLogger import network.framework.components.TestEventDispatcherImpl import org.junit.jupiter.api.TestInstance +import kotlin.math.absoluteValue +import kotlin.random.Random import kotlin.test.assertEquals /** @@ -41,9 +44,13 @@ internal abstract class AbstractMockNetworkHandlerTest : AbstractNetworkHandlerT protected open fun createNetworkHandlerContext() = TestNetworkHandlerContext(bot, logger, components) protected open fun createNetworkHandler() = TestNetworkHandler(bot, createNetworkHandlerContext()) - protected val bot: QQAndroidBot = MockBot { - nhProvider = { createNetworkHandler() } - additionalComponentsProvider = { this@AbstractMockNetworkHandlerTest.components } + protected open fun createAccount() = BotAccount(Random.nextLong().absoluteValue.mod(1000L), "pwd") + + protected val bot: QQAndroidBot by lazy { + MockBot(createAccount()) { + nhProvider = { createNetworkHandler() } + additionalComponentsProvider = { this@AbstractMockNetworkHandlerTest.components } + } } protected val logger = MiraiLogger.Factory.create(Bot::class, "test") protected val components = ConcurrentComponentStorage().apply { diff --git a/mirai-core/src/jvmTest/kotlin/message/data/MessageReceiptTest.kt b/mirai-core/src/jvmTest/kotlin/message/data/MessageReceiptTest.kt index 8ee1434f8..5860639f0 100644 --- a/mirai-core/src/jvmTest/kotlin/message/data/MessageReceiptTest.kt +++ b/mirai-core/src/jvmTest/kotlin/message/data/MessageReceiptTest.kt @@ -37,11 +37,8 @@ import kotlin.test.assertTrue internal class MessageReceiptTest : AbstractTest(), GroupExtensions { private val bot = MockBot() - /** - * This test is very ugly, but we cannot do anything else. - */ // We need #1304 @Test - fun `refine ForwardMessageInternal for MessageReceipt`() = runBlockingUnit { + fun `refine ForwardMessageInternal for MessageReceipt's original MessageChain`() = runBlockingUnit { val group = bot.addGroup(123, 2) val forward = buildForwardMessage(group) { @@ -52,7 +49,7 @@ internal class MessageReceiptTest : AbstractTest(), GroupExtensions { val facade = MessageProtocolFacade.INSTANCE.copy() assertTrue { - facade.preprocessorPipeline.replaceProcessor( + facade.outgoingPipeline.replaceProcessor( { it is GeneralMessageSenderProtocol.GeneralMessageSender }, OutgoingMessageProcessorAdapter(object : OutgoingMessageSender { override suspend fun OutgoingMessagePipelineContext.process() { diff --git a/mirai-core/src/jvmTest/kotlin/message/data/MessageRefineTest.kt b/mirai-core/src/jvmTest/kotlin/message/data/MessageRefineTest.kt index 3d19c2020..e92e8eb1a 100644 --- a/mirai-core/src/jvmTest/kotlin/message/data/MessageRefineTest.kt +++ b/mirai-core/src/jvmTest/kotlin/message/data/MessageRefineTest.kt @@ -18,9 +18,9 @@ import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.getMiraiImpl import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight -import net.mamoe.mirai.internal.message.ReceiveMessageTransformer import net.mamoe.mirai.internal.message.RefinableMessage import net.mamoe.mirai.internal.message.RefineContext +import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm @@ -326,7 +326,7 @@ private fun sourceStub( private suspend fun testRecursiveRefine(list: List, expected: MessageChain, isLight: Boolean) { val actual = buildMessageChain { - ReceiveMessageTransformer.joinToMessageChain(list, 0, MessageSourceKind.GROUP, bot, this) + MessageProtocolFacade.decode(list, 0, MessageSourceKind.GROUP, bot, this, null) }.let { c -> if (isLight) { c.refineLight(bot)