diff --git a/mirai-core-utils/src/commonMain/kotlin/Clock.kt b/mirai-core-utils/src/commonMain/kotlin/Clock.kt index 3894d3380..6ad23b720 100644 --- a/mirai-core-utils/src/commonMain/kotlin/Clock.kt +++ b/mirai-core-utils/src/commonMain/kotlin/Clock.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. @@ -11,7 +11,7 @@ package net.mamoe.mirai.utils public interface Clock { public fun currentTimeMillis(): Long - public fun currentTimeSeconds(): Long + public fun currentTimeSeconds(): Long = currentTimeMillis() / 1000 public object SystemDefault : Clock { override fun currentTimeMillis(): Long = net.mamoe.mirai.utils.currentTimeMillis() diff --git a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt index f650a6ae4..164ec70ab 100644 --- a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt +++ b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt @@ -26,6 +26,7 @@ import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.network.component.buildComponentStorage import net.mamoe.mirai.internal.network.components.BdhSession +import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.internal.network.highway.ChannelKind import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.ResourceKind.PRIVATE_IMAGE @@ -261,6 +262,7 @@ internal suspend fun C.sendMessageImpl( MessageProtocolFacade.preprocessAndSendOutgoing(this, message, buildComponentStorage { set(MessageProtocolStrategy, messageProtocolStrategy) set(HighwayUploader, HighwayUploader.Default) + set(ClockHolder, bot.components[ClockHolder]) }) } diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt index 237e3b992..b0c037bde 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt @@ -45,16 +45,27 @@ internal suspend fun C.broadcastMessagePreSendEvent( internal enum class SendMessageStep { + /** + * 尝试单包直接发送全部消息 + */ FIRST { override fun nextStepOrNull(): SendMessageStep { return LONG_MESSAGE } }, + + /** + * 尝试通过长消息通道上传长消息取得 resId 后再通过普通消息通道发送长消息标识 + */ LONG_MESSAGE { override fun nextStepOrNull(): SendMessageStep { return FRAGMENTED } }, + + /** + * 发送分片多包发送 + */ FRAGMENTED { override fun nextStepOrNull(): SendMessageStep? { return null diff --git a/mirai-core/src/commonMain/kotlin/message/data/LongMessageInternal.kt b/mirai-core/src/commonMain/kotlin/message/data/LongMessageInternal.kt index b4bd561b9..a949240ae 100644 --- a/mirai-core/src/commonMain/kotlin/message/data/LongMessageInternal.kt +++ b/mirai-core/src/commonMain/kotlin/message/data/LongMessageInternal.kt @@ -152,34 +152,6 @@ internal data class ForwardMessageInternal( } } - -internal fun RichMessage.Key.longMessage(brief: String, resId: String, timeSeconds: Long): LongMessageInternal { - val limited: String = if (brief.length > 30) { - brief.take(30) + "…" - } else { - brief - } - - val template = """ - - - - $limited - - 点击查看完整消息 - - - - """.trimIndent().trim() - - return LongMessageInternal(template, resId) -} - - private fun String.xmlEnc(): String { return this.replace("&", "&") } diff --git a/mirai-core/src/commonMain/kotlin/message/data/MultiMsgUploader.kt b/mirai-core/src/commonMain/kotlin/message/data/MultiMsgUploader.kt index 74db79f19..ae94da4c4 100644 --- a/mirai-core/src/commonMain/kotlin/message/data/MultiMsgUploader.kt +++ b/mirai-core/src/commonMain/kotlin/message/data/MultiMsgUploader.kt @@ -17,11 +17,9 @@ import net.mamoe.mirai.internal.contact.impl import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.contact.userIdOrNull import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade -import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader -import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.message.source.MessageSourceInternal import net.mamoe.mirai.internal.network.QQAndroidClient -import net.mamoe.mirai.internal.network.component.buildComponentStorage +import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.ResourceKind import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody @@ -42,21 +40,19 @@ import kotlin.random.Random internal open class MultiMsgUploader( val client: QQAndroidClient, val isLong: Boolean, - val tmpRand: Random = Random.Default, - val facade: MessageProtocolFacade, + val random: Random, val contact: Contact, - val strategy: MessageProtocolStrategy<*>, + val components: ComponentStorage, val senderName: String ) { protected open fun newUploader(): MultiMsgUploader = MultiMsgUploader( - isLong = isLong, client = client, - tmpRand = tmpRand, - facade = facade, + isLong = isLong, + random = random, contact = contact, - senderName = senderName, - strategy = strategy + components = components, + senderName = senderName ) val mainMsg = mutableListOf() @@ -69,7 +65,7 @@ internal open class MultiMsgUploader( protected open fun newNid(): String { var nid: String do { - nid = "${tmpRand.nextInt().absoluteValue}" + nid = "${random.nextInt().absoluteValue}" } while (nestedMsgs.containsKey(nid)) return nid } @@ -140,10 +136,7 @@ internal open class MultiMsgUploader( msgChain = convertNestedForwardMessage(nestedForward, msgChain) } - msgChain = facade.preprocess(contact.impl(), msgChain, buildComponentStorage { - set(MessageProtocolStrategy, strategy) - set(HighwayUploader, HighwayUploader.Default) - }) + msgChain = components[MessageProtocolFacade].preprocess(contact.impl(), msgChain, components) var seq: Int = -1 var uid: Int = -1 @@ -157,8 +150,8 @@ internal open class MultiMsgUploader( if (seq != -1 && uid != -1) { if (existsIds.add(seq.concatAsLong(uid))) break } - seq = tmpRand.nextInt().absoluteValue - uid = tmpRand.nextInt().absoluteValue + seq = random.nextInt().absoluteValue + uid = random.nextInt().absoluteValue } val msg0 = MsgComm.Msg( @@ -176,18 +169,13 @@ internal open class MultiMsgUploader( ), msgType = 82, // troop, groupInfo = if (contact is Group) MsgComm.GroupInfo( - groupCode = contact.groupCode, - groupCard = senderName // Cinnamon + groupCode = contact.groupCode, groupCard = senderName // Cinnamon ) else null, isSrcMsg = false, - ), - msgBody = ImMsgBody.MsgBody( + ), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = MessageProtocolFacade.encode( - msgChain, - messageTarget = contact, - withGeneralFlags = false, - isForward = true + msgChain, messageTarget = contact, withGeneralFlags = false, isForward = true ) ) ) @@ -197,17 +185,13 @@ internal open class MultiMsgUploader( } open fun toMessageValidationData(): MessageValidationData { - val msgTransmit = MsgTransmit.PbMultiMsgTransmit( - msg = mainMsg, - pbItemList = nestedMsgs.asSequence() - .map { (name, msgList) -> - MsgTransmit.PbMultiMsgItem( - fileName = name, - buffer = MsgTransmit.PbMultiMsgNew(msgList).toByteArray(MsgTransmit.PbMultiMsgNew.serializer()) - ) - } - .toList() - ) + val msgTransmit = + MsgTransmit.PbMultiMsgTransmit(msg = mainMsg, pbItemList = nestedMsgs.asSequence().map { (name, msgList) -> + MsgTransmit.PbMultiMsgItem( + fileName = name, + buffer = MsgTransmit.PbMultiMsgNew(msgList).toByteArray(MsgTransmit.PbMultiMsgNew.serializer()) + ) + }.toList()) val bytes = msgTransmit.toByteArray(MsgTransmit.PbMultiMsgTransmit.serializer()) return MessageValidationData(bytes.gzip()) @@ -218,27 +202,20 @@ internal open class MultiMsgUploader( val response = client.bot.network.sendAndExpect( MultiMsg.ApplyUp.createForGroup( - buType = if (isLong) 1 else 2, - client = client, - messageData = data, - dstUin = contact.uin + buType = if (isLong) 1 else 2, client = client, messageData = data, dstUin = contact.uin ) ) lateinit var resId: String when (response) { - is MultiMsg.ApplyUp.Response.MessageTooLarge -> - error( - "Internal error: message is too large, but this should be handled before sending. " - ) + is MultiMsg.ApplyUp.Response.MessageTooLarge -> error( + "Internal error: message is too large, but this should be handled before sending. " + ) is MultiMsg.ApplyUp.Response.RequireUpload -> { resId = response.proto.msgResid val body = LongMsg.ReqBody( - subcmd = 1, - platformType = 9, - termType = 5, - msgUpReq = listOf( + subcmd = 1, platformType = 9, termType = 5, msgUpReq = listOf( LongMsg.MsgUpReq( msgType = 3, // group dstUin = contact.uin, @@ -253,14 +230,10 @@ internal open class MultiMsgUploader( body.toExternalResource().use { resource -> Highway.uploadResourceBdh( - bot = client.bot, - resource = resource, - kind = when (isLong) { + bot = client.bot, resource = resource, kind = when (isLong) { true -> ResourceKind.LONG_MESSAGE false -> ResourceKind.FORWARD_MESSAGE - }, - commandId = 27, - initialTicket = response.proto.msgSig + }, commandId = 27, initialTicket = response.proto.msgSig ) } } diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt index 84ac27007..b0bca26ad 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt @@ -22,7 +22,10 @@ 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.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.pipeline.ProcessResult import net.mamoe.mirai.internal.utils.runCoroutineInPlace @@ -39,6 +42,8 @@ import java.util.* import kotlin.reflect.KClass internal interface MessageProtocolFacade { + val remark: String get() = "MessageProtocolFacade" + val encoderPipeline: MessageEncoderPipeline val decoderPipeline: MessageDecoderPipeline val preprocessorPipeline: OutgoingMessagePipeline @@ -129,7 +134,8 @@ internal interface MessageProtocolFacade { /** * The default global instance. */ - companion object INSTANCE : MessageProtocolFacade by MessageProtocolFacadeImpl() + companion object INSTANCE : MessageProtocolFacade by MessageProtocolFacadeImpl(), + ComponentKey } internal fun MessageProtocolFacade.decodeAndRefineLight( @@ -150,7 +156,8 @@ internal suspend fun MessageProtocolFacade.decodeAndRefineDeep( internal class MessageProtocolFacadeImpl( - private val protocols: Iterable = ServiceLoader.load(MessageProtocol::class.java) + private val protocols: Iterable = ServiceLoader.load(MessageProtocol::class.java), + override val remark: String = "MessageProtocolFacade" ) : MessageProtocolFacade { override val encoderPipeline: MessageEncoderPipeline = MessageEncoderPipelineImpl() override val decoderPipeline: MessageDecoderPipeline = MessageDecoderPipelineImpl() @@ -243,6 +250,15 @@ internal class MessageProtocolFacadeImpl( } } + private val thisComponentStorage by lazy { + buildComponentStorage { + set( + MessageProtocolFacade, + this@MessageProtocolFacadeImpl + ) + } + } + override suspend fun preprocess( target: C, message: Message, @@ -314,9 +330,9 @@ internal class MessageProtocolFacadeImpl( 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.PROTOCOL_STRATEGY, context[MessageProtocolStrategy].castUp()) - set(OutgoingMessagePipelineContext.HIGHWAY_UPLOADER, context[HighwayUploader]) + set(OutgoingMessagePipelineContext.COMPONENTS, thisComponentStorage.withFallback(context)) } return attributes } 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 9eb790be2..7a0e867a2 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt @@ -19,9 +19,11 @@ import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext +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.PROTOCOL_STRATEGY +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer import net.mamoe.mirai.internal.message.source.createMessageReceipt @@ -81,11 +83,11 @@ internal class FileMessageProtocol : MessageProtocol() { val contact = attributes[CONTACT] val bot = contact.bot - val strategy = attributes[PROTOCOL_STRATEGY] + val strategy = components[MessageProtocolStrategy] val source = coroutineScope { val source = async { - strategy.constructSourceForSpecialMessage(currentMessageChain, 2021) + strategy.constructSourceForSpecialMessage(attributes[ORIGINAL_MESSAGE_AS_CHAIN], 2021) } bot.network.sendAndExpect(FileManagement.Feed(bot.client, contact.id, file.busId, file.id)) 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 f705ced46..502e2f279 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/ForwardMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/ForwardMessageProtocol.kt @@ -14,15 +14,15 @@ import net.mamoe.mirai.internal.message.data.forwardMessage import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector +import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader 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.HIGHWAY_UPLOADER -import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.PROTOCOL_STRATEGY +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePreprocessor +import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.message.data.ForwardMessage import net.mamoe.mirai.message.data.RichMessage import net.mamoe.mirai.message.data.toMessageChain -import net.mamoe.mirai.utils.currentTimeSeconds internal class ForwardMessageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { @@ -46,16 +46,16 @@ internal class ForwardMessageProtocol : MessageProtocol() { }.asIterable().verifyLength(forward, contact) } - val resId = attributes[HIGHWAY_UPLOADER].uploadMessages( + val resId = components[HighwayUploader].uploadMessages( contact, - attributes[PROTOCOL_STRATEGY], + components, forward.nodeList, false ) currentMessageChain = RichMessage.forwardMessage( resId = resId, - fileName = currentTimeSeconds().toString(), + fileName = components[ClockHolder].local.currentTimeSeconds().toString(), forwardMessage = forward, ).toMessageChain() } 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 628e8122c..7b9121629 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralMessageSenderProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralMessageSenderProtocol.kt @@ -12,14 +12,17 @@ package net.mamoe.mirai.internal.message.protocol.impl import kotlinx.coroutines.Deferred 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.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.ORIGINAL_MESSAGE -import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.PROTOCOL_STRATEGY +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.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender import net.mamoe.mirai.internal.message.source.createMessageReceipt import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket @@ -41,7 +44,8 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S override suspend fun OutgoingMessagePipelineContext.process() { markAsConsumed() - val strategy = attributes[PROTOCOL_STRATEGY] + @Suppress("UNCHECKED_CAST") + val strategy = components[MessageProtocolStrategy] as MessageProtocolStrategy val step = attributes[STEP] val contact = attributes[CONTACT] val bot = contact.bot @@ -52,9 +56,9 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S client = bot.client, contact = contact, message = currentMessageChain, - fragmented = step == SendMessageStep.FRAGMENTED, - sourceCallback = { source = it } - ) + originalMessage = attributes[ORIGINAL_MESSAGE_AS_CHAIN], + fragmented = step == SendMessageStep.FRAGMENTED + ) { source = it } sendAllPackets(bot, step, contact, packets) @@ -70,7 +74,7 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S packets: List ) = packets.forEach { packet -> val originalMessage = attributes[ORIGINAL_MESSAGE] - val protocolStrategy = attributes[PROTOCOL_STRATEGY] + val protocolStrategy = components[MessageProtocolStrategy] val finalMessage = currentMessageChain val resp = protocolStrategy.sendPacket(bot, packet) as MessageSvcPbSendMsg.Response diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/LongMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/LongMessageProtocol.kt index 7fff3f90f..73346a3d5 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/LongMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/LongMessageProtocol.kt @@ -12,22 +12,21 @@ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.SendMessageStep import net.mamoe.mirai.internal.contact.takeContent -import net.mamoe.mirai.internal.message.data.longMessage +import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.flags.DontAsLongMessage 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.ProcessorCollector -import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy +import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader 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.HIGHWAY_UPLOADER -import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.PROTOCOL_STRATEGY import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.STEP +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer +import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.RichMessage -import net.mamoe.mirai.utils.currentTimeSeconds +import net.mamoe.mirai.utils.truncated internal class LongMessageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { @@ -36,24 +35,25 @@ internal class LongMessageProtocol : MessageProtocol() { convertToLongMessageIfNeeded( currentMessageChain, attributes[STEP], - attributes[CONTACT], - attributes[PROTOCOL_STRATEGY] + attributes[CONTACT] ) }) } + /** + * Convert to [LongMessageInternal] iff [SendMessageStep.FIRST] has failed. + */ private suspend fun OutgoingMessagePipelineContext.convertToLongMessageIfNeeded( chain: MessageChain, step: SendMessageStep, contact: AbstractContact, - strategy: MessageProtocolStrategy<*> ): MessageChain { - val uploader = attributes[HIGHWAY_UPLOADER] + val uploader = components[HighwayUploader] suspend fun sendLongImpl(): MessageChain { - val time = currentTimeSeconds() - val resId = uploader.uploadLongMessage(contact, strategy, chain, time.toInt()) - return chain + RichMessage.longMessage( + val time = components[ClockHolder].local.currentTimeSeconds() + val resId = uploader.uploadLongMessage(contact, components, chain, time.toInt()) + return chain + createLongMessage( brief = chain.takeContent(27), resId = resId, timeSeconds = time @@ -81,4 +81,26 @@ internal class LongMessageProtocol : MessageProtocol() { SendMessageStep.FRAGMENTED -> chain } } + + private fun createLongMessage(brief: String, resId: String, timeSeconds: Long): LongMessageInternal { + val limited: String = brief.truncated(30) + val template = """ + + + + $limited + + 点击查看完整消息 + + + + """.trimIndent().trim() + + return LongMessageInternal(template, resId) + } + } \ No newline at end of file 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 e91c26ebf..8d392ab37 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt @@ -14,8 +14,11 @@ 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 import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext +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.ORIGINAL_MESSAGE_AS_CHAIN +import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender import net.mamoe.mirai.internal.message.source.createMessageReceipt import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket @@ -55,8 +58,8 @@ internal class MusicShareProtocol : MessageProtocol() { val result = bot.network.sendAndExpect(packet) result.pkg.checkSuccess("send music share") - val strategy = attributes[OutgoingMessagePipelineContext.PROTOCOL_STRATEGY] - val source = strategy.constructSourceForSpecialMessage(currentMessageChain, 3116) + val strategy = components[MessageProtocolStrategy] + val source = strategy.constructSourceForSpecialMessage(attributes[ORIGINAL_MESSAGE_AS_CHAIN], 3116) source.tryEnsureSequenceIdAvailable() collect(source.createMessageReceipt(contact, true)) diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/HighwayUploader.kt b/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/HighwayUploader.kt index 5b378935a..48137b4cb 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/HighwayUploader.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/HighwayUploader.kt @@ -12,19 +12,20 @@ package net.mamoe.mirai.internal.message.protocol.outgoing import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.nickIn import net.mamoe.mirai.internal.message.data.MultiMsgUploader -import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.source.ensureSequenceIdAvailable import net.mamoe.mirai.internal.network.component.ComponentKey +import net.mamoe.mirai.internal.network.component.ComponentStorage +import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.message.data.ForwardMessage import net.mamoe.mirai.message.data.MessageChain +import kotlin.random.Random internal interface HighwayUploader { suspend fun uploadMessages( contact: AbstractContact, - strategy: MessageProtocolStrategy<*>, + components: ComponentStorage, nodes: Collection, isLong: Boolean, - facade: MessageProtocolFacade = MessageProtocolFacade, senderName: String = contact.bot.nickIn(contact), ): String { nodes.forEach { it.messageChain.ensureSequenceIdAvailable() } @@ -32,10 +33,10 @@ internal interface HighwayUploader { val uploader = MultiMsgUploader( client = contact.bot.client, isLong = isLong, - facade = facade, contact = contact, + random = Random(components[ClockHolder].local.currentTimeSeconds()), senderName = senderName, - strategy = strategy + components = components ).also { it.emitMain(nodes) } return uploader.uploadAndReturnResId() @@ -43,7 +44,7 @@ internal interface HighwayUploader { suspend fun uploadLongMessage( contact: AbstractContact, - strategy: MessageProtocolStrategy<*>, + components: ComponentStorage, chain: MessageChain, timeSeconds: Int, senderName: String = contact.bot.nickIn(contact), @@ -51,7 +52,7 @@ internal interface HighwayUploader { val bot = contact.bot return uploadMessages( contact, - strategy, + components, listOf( ForwardMessage.Node( senderId = bot.id, @@ -61,7 +62,7 @@ internal interface HighwayUploader { ) ), true, - senderName = senderName + senderName = senderName, ) } 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 6025e06a8..e6f44a3f6 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/MessageProtocolStrategy.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/MessageProtocolStrategy.kt @@ -37,13 +37,14 @@ internal interface MessageProtocolStrategy { suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: C, - message: MessageChain, + message: MessageChain, // to send + originalMessage: MessageChain, // to create Receipt fragmented: Boolean, sourceCallback: (Deferred) -> Unit, ): List suspend fun constructSourceForSpecialMessage( - finalMessage: MessageChain, + originalMessage: MessageChain, fromAppId: Int, ): OnlineMessageSource.Outgoing @@ -52,7 +53,7 @@ internal interface MessageProtocolStrategy { internal sealed class UserMessageProtocolStrategy : MessageProtocolStrategy { override suspend fun constructSourceForSpecialMessage( - finalMessage: MessageChain, + originalMessage: MessageChain, fromAppId: Int ): OnlineMessageSource.Outgoing { throw UnsupportedOperationException("Sending MusicShare or FileMessage to User is not yet supported") @@ -66,14 +67,15 @@ internal class FriendMessageProtocolStrategy( client: QQAndroidClient, contact: FriendImpl, message: MessageChain, + originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred) -> Unit ): List { - return MessageSvcPbSendMsg.createToFriend(client, contact, message, fragmented, sourceCallback) + return MessageSvcPbSendMsg.createToFriend(client, contact, message, originalMessage, fragmented, sourceCallback) } override suspend fun constructSourceForSpecialMessage( - finalMessage: MessageChain, + originalMessage: MessageChain, fromAppId: Int ): OnlineMessageSource.Outgoing { val receipt: PrivateMessageProcessor.SendPrivateMessageReceipt = withTimeoutOrNull(3000) { @@ -88,7 +90,7 @@ internal class FriendMessageProtocolStrategy( sender = contact.bot, target = contact, time = contact.bot.clock.server.currentTimeSeconds().toInt(), - originalMessage = finalMessage + originalMessage = originalMessage ) } } @@ -98,10 +100,18 @@ internal object StrangerMessageProtocolStrategy : UserMessageProtocolStrategy) -> Unit ): List { - return MessageSvcPbSendMsg.createToStranger(client, contact, message, fragmented, sourceCallback) + return MessageSvcPbSendMsg.createToStranger( + client, + contact, + message, + originalMessage, + fragmented, + sourceCallback + ) } } @@ -110,10 +120,11 @@ internal object GroupTempMessageProtocolStrategy : UserMessageProtocolStrategy) -> Unit ): List { - return MessageSvcPbSendMsg.createToTemp(client, contact, message, fragmented, sourceCallback) + return MessageSvcPbSendMsg.createToTemp(client, contact, message, originalMessage, fragmented, sourceCallback) } } @@ -124,14 +135,15 @@ internal open class GroupMessageProtocolStrategy( client: QQAndroidClient, contact: GroupImpl, message: MessageChain, + originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred) -> Unit ): List { - return MessageSvcPbSendMsg.createToGroup(client, contact, message, fragmented, sourceCallback) + return MessageSvcPbSendMsg.createToGroup(client, contact, message, originalMessage, fragmented, sourceCallback) } override suspend fun constructSourceForSpecialMessage( - finalMessage: MessageChain, + originalMessage: MessageChain, fromAppId: Int ): OnlineMessageSource.Outgoing { val receipt: GroupMessageProcessor.SendGroupMessageReceipt = withTimeoutOrNull(3000) { @@ -147,7 +159,7 @@ internal open class GroupMessageProtocolStrategy( sender = contact.bot, target = contact, time = contact.bot.clock.server.currentTimeSeconds().toInt(), - originalMessage = finalMessage + originalMessage = originalMessage ) } 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 0850e8efd..32b992735 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/OutgoingMessagePipeline.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/outgoing/OutgoingMessagePipeline.kt @@ -14,6 +14,7 @@ import net.mamoe.mirai.contact.MessageTooLargeException import net.mamoe.mirai.internal.contact.AbstractContact 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 @@ -104,6 +105,11 @@ internal interface OutgoingMessagePipelineContext : */ val ORIGINAL_MESSAGE = TypeKey("originalMessage") + /** + * You should only use [ORIGINAL_MESSAGE_AS_CHAIN] if you can't use [ORIGINAL_MESSAGE] + */ + val ORIGINAL_MESSAGE_AS_CHAIN = TypeKey("originalMessageAsChain") + /** * Message target @@ -112,9 +118,8 @@ internal interface OutgoingMessagePipelineContext : val STEP = TypeKey("step") - val PROTOCOL_STRATEGY = TypeKey>("protocolStrategy") - - val HIGHWAY_UPLOADER = TypeKey("highwayUploader") + val COMPONENTS = TypeKey("components") + val OutgoingMessagePipelineContext.components: ComponentStorage get() = attributes[COMPONENTS] } } diff --git a/mirai-core/src/commonMain/kotlin/network/component/ComponentStorage.kt b/mirai-core/src/commonMain/kotlin/network/component/ComponentStorage.kt index fe90fd8ce..6440d81a8 100644 --- a/mirai-core/src/commonMain/kotlin/network/component/ComponentStorage.kt +++ b/mirai-core/src/commonMain/kotlin/network/component/ComponentStorage.kt @@ -10,6 +10,8 @@ package net.mamoe.mirai.internal.network.component import org.jetbrains.annotations.TestOnly +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Mediator for [component][ComponentKey]s accessing each other. @@ -38,6 +40,7 @@ internal interface ComponentStorage { } internal fun buildComponentStorage(builderAction: MutableComponentStorage.() -> Unit): ComponentStorage { + contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } return ConcurrentComponentStorage(builderAction) } diff --git a/mirai-core/src/commonMain/kotlin/network/components/ClockComponent.kt b/mirai-core/src/commonMain/kotlin/network/components/ClockComponent.kt index b962acf37..035f8b853 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/ClockComponent.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/ClockComponent.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. @@ -12,10 +12,11 @@ package net.mamoe.mirai.internal.network.components import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.utils.Clock +import net.mamoe.mirai.utils.lateinitMutableProperty -internal class ClockHolder { - val local: Clock get() = Clock.SystemDefault - var server: Clock = local +internal open class ClockHolder { + open val local: Clock get() = Clock.SystemDefault + open var server: Clock by lateinitMutableProperty { local } companion object : ComponentKey { val QQAndroidBot.clock get() = components[ClockHolder] diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt index a419ee744..340d107b7 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt @@ -186,6 +186,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory Unit, ): List { @@ -226,7 +227,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory Unit, ): List { @@ -290,7 +292,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory Unit, ): List { @@ -422,7 +425,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory) -> Unit, ): List { @@ -512,7 +516,7 @@ internal inline fun MessageSvcPbSendMsg.createToTemp( target = member, time = client.bot.clock.server.currentTimeSeconds().toInt(), sequenceIds = intArrayOf(client.atomicNextMessageSequenceId()), - originalMessage = message, + originalMessage = originalMessage, ) sourceCallback(CompletableDeferred(source)) return createToTempImpl( @@ -526,7 +530,8 @@ internal inline fun MessageSvcPbSendMsg.createToTemp( internal inline fun MessageSvcPbSendMsg.createToStranger( client: QQAndroidClient, stranger: Stranger, - message: MessageChain, + message: MessageChain, // to send + originalMessage: MessageChain, // for Receipt fragmented: Boolean, crossinline sourceCallback: (Deferred) -> Unit, ): List { @@ -537,6 +542,7 @@ internal inline fun MessageSvcPbSendMsg.createToStranger( client, stranger, message, + originalMessage, fragmented, ) { sourceCallback(CompletableDeferred(it)) } } @@ -545,6 +551,7 @@ internal inline fun MessageSvcPbSendMsg.createToFriend( client: QQAndroidClient, qq: Friend, message: MessageChain, + originalMessage: MessageChain, fragmented: Boolean, crossinline sourceCallback: (Deferred) -> Unit, ): List { @@ -555,6 +562,7 @@ internal inline fun MessageSvcPbSendMsg.createToFriend( client, qq, message, + originalMessage, fragmented, ) { sourceCallback(CompletableDeferred(it)) } } @@ -564,6 +572,7 @@ internal inline fun MessageSvcPbSendMsg.createToGroup( client: QQAndroidClient, group: Group, message: MessageChain, + originalMessage: MessageChain, fragmented: Boolean, crossinline sourceCallback: (Deferred) -> Unit, ): List { @@ -574,6 +583,7 @@ internal inline fun MessageSvcPbSendMsg.createToGroup( client, group, message, + originalMessage, fragmented, ) { sourceCallback(CompletableDeferred(it)) } } \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt index 86ad0324b..999c38522 100644 --- a/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt @@ -33,8 +33,11 @@ internal class MessageProtocolFacadeTest : AbstractTest() { PttMessageProtocol RichMessageProtocol TextProtocol - UnsupportedMessageProtocol VipFaceProtocol + ForwardMessageProtocol + LongMessageProtocol + UnsupportedMessageProtocol + GeneralMessageSenderProtocol """.trimIndent(), MessageProtocolFacadeImpl().loaded.joinToString("\n") { it::class.simpleName.toString() } ) 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 6d82321ad..f1e9b4971 100644 --- a/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt @@ -12,8 +12,12 @@ package net.mamoe.mirai.internal.message.protocol.impl import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import kotlinx.coroutines.ExperimentalCoroutinesApi +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.internal.AbstractBot +import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.data.inferMessageSourceKind import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade @@ -21,17 +25,31 @@ import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacadeImpl import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderPipelineImpl import net.mamoe.mirai.internal.message.protocol.decodeAndRefineLight import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderPipelineImpl +import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader +import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy +import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToFriendImpl +import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToGroupImpl +import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.QQAndroidClient +import net.mamoe.mirai.internal.network.component.ComponentStorage +import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg 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 net.mamoe.mirai.internal.test.runBlockingUnit +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.Clock +import net.mamoe.mirai.utils.md5 +import net.mamoe.mirai.utils.toUHexString import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import kotlin.contracts.InvocationKind import kotlin.contracts.contract +import kotlin.test.Asserter +import kotlin.test.assertEquals +import kotlin.test.asserter internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest(), GroupExtensions { @@ -60,7 +78,10 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler } protected fun facadeOf(vararg protocols: MessageProtocol): MessageProtocolFacade { - return MessageProtocolFacadeImpl(protocols.toList()) + return MessageProtocolFacadeImpl( + protocols.toList(), + remark = "MessageProtocolFacade with ${protocols.joinToString { it::class.simpleName!! }}" + ) } /////////////////////////////////////////////////////////////////////////// @@ -211,5 +232,121 @@ 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") + } + } + + } + components[HighwayUploader] = object : HighwayUploader { + override suspend fun uploadMessages( + contact: AbstractContact, + components: ComponentStorage, + nodes: Collection, + isLong: Boolean, + senderName: String + ): String { + return "(size=${nodes.size})${ + nodes.joinToString().replace(bot.id.toString(), "123123").md5().toUHexString("") + }" + } + } + components[ClockHolder] = object : ClockHolder() { + override val local: Clock = object : Clock { + override fun currentTimeMillis(): Long = 160023456 + } + } + } + + fun runWithFacade(action: suspend MessageProtocolFacade.() -> Unit) { + runBlockingUnit { + facadeOf(*protocols).run { action() } + MessageProtocolFacade.INSTANCE.run { action() } + } + } + + companion object { + fun assertMessageEquals(expected: Message, actual: Message) { + val expectedChain = expected.toMessageChain() + val actualChain = actual.toMessageChain() + + val message = String.format( + """ + Expected: %s + + Actual: %s + """.trimIndent(), expectedChain.render(), actualChain.render() + ) + assertEquals(expectedChain.size, actualChain.size, message) + asserter.assertEquals(message, expectedChain, actualChain) + } + + fun MessageProtocolFacade.assertMessageEquals(expected: Message, actual: Message) { + val expectedChain = expected.toMessageChain() + val actualChain = actual.toMessageChain() + + val message = String.format( + """ + Facade: ${this.remark} + Expected: %s + + Actual: %s + """.trimIndent(), expectedChain.render(), actualChain.render() + ) + assertEquals(expectedChain.size, actualChain.size, message) + asserter.assertEquals(message, expectedChain, actualChain) + } + + inline fun Asserter.assertEquals(crossinline message: () -> String, expected: Any?, actual: Any?) { + assertTrue({ message() + ". Expected <$expected>, actual <$actual>." }, actual == expected) + } + + + fun MessageChain.render(): String = buildString { + appendLine("size = $size") + for (singleMessage in distinct()) { + val count = this@render.count { it == singleMessage } + appendLine("$count x [${singleMessage::class.simpleName}] $singleMessage") + } + } + } } \ 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 new file mode 100644 index 000000000..702e6c1b4 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/message/protocol/impl/LongMessageProtocolTest.kt @@ -0,0 +1,89 @@ +/* + * 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.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.message.data.Image +import net.mamoe.mirai.message.data.repeat +import net.mamoe.mirai.message.data.toPlainText +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 LongMessageProtocolTest : AbstractMessageProtocolTest() { + override val protocols: Array = + arrayOf( + TextProtocol(), + ImageProtocol(), + LongMessageProtocol(), + 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 messages to LongMessageInternal`() { + var message = "test".toPlainText() + getRandomString(5000, Random(1)) + + Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200) + + message += IgnoreLengthCheck + message += ForceAsLongMessage + + runWithFacade { + preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> + val receipt = receipts.single() + assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) + + assertMessageEquals( + LongMessageInternal( + """ + + + + testqGnJ1R... + + 点击查看完整消息 + + + + """.trimIndent(), "(size=1)DBD2AB20196EEB631C95DEF40E20C709" + ) + IgnoreLengthCheck + ForceAsLongMessage, context.currentMessageChain + ) + } + } + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt b/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt index 14725e596..093c3577e 100644 --- a/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt +++ b/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt @@ -17,7 +17,10 @@ import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.components.EventDispatcher +import net.mamoe.mirai.internal.network.components.PacketLoggingStrategy +import net.mamoe.mirai.internal.network.components.PacketLoggingStrategyImpl import net.mamoe.mirai.internal.network.components.SsoProcessor +import net.mamoe.mirai.internal.network.framework.components.TestImagePatcher import net.mamoe.mirai.internal.network.framework.components.TestSsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.state.LoggingStateObserver @@ -61,7 +64,8 @@ internal abstract class AbstractMockNetworkHandlerTest : AbstractNetworkHandlerT MiraiLogger.Factory.create(SafeStateObserver::class, "StateObserver errors") ) ) - set(ImagePatcher, ImagePatcher()) + set(ImagePatcher, TestImagePatcher()) + set(PacketLoggingStrategy, PacketLoggingStrategyImpl(bot)) } fun NetworkHandler.assertState(state: NetworkHandler.State) { diff --git a/mirai-core/src/jvmTest/kotlin/message/data/MessageReceiptTest.kt b/mirai-core/src/jvmTest/kotlin/message/data/MessageReceiptTest.kt index 9eeba02ba..8ee1434f8 100644 --- a/mirai-core/src/jvmTest/kotlin/message/data/MessageReceiptTest.kt +++ b/mirai-core/src/jvmTest/kotlin/message/data/MessageReceiptTest.kt @@ -16,7 +16,9 @@ import net.mamoe.mirai.internal.message.protocol.impl.GeneralMessageSenderProtoc import net.mamoe.mirai.internal.message.protocol.outgoing.* import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToGroupImpl import net.mamoe.mirai.internal.message.source.createMessageReceipt +import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage +import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.internal.notice.processors.GroupExtensions import net.mamoe.mirai.internal.pipeline.replaceProcessor import net.mamoe.mirai.internal.test.AbstractTest @@ -24,6 +26,7 @@ import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.message.data.ForwardMessage import net.mamoe.mirai.message.data.buildForwardMessage import net.mamoe.mirai.message.data.toMessageChain +import net.mamoe.mirai.utils.Clock import net.mamoe.mirai.utils.currentTimeSeconds import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -79,15 +82,21 @@ internal class MessageReceiptTest : AbstractTest(), GroupExtensions { set(HighwayUploader, object : HighwayUploader { override suspend fun uploadMessages( contact: AbstractContact, - strategy: MessageProtocolStrategy<*>, + components: ComponentStorage, nodes: Collection, isLong: Boolean, - facade: MessageProtocolFacade, senderName: String ): String { return "id" } }) + set(ClockHolder, object : ClockHolder() { + override val local: Clock = object : Clock { + override fun currentTimeMillis(): Long { + return 160023456 + } + } + }) }) assertIs(result.source.originalMessage[ForwardMessage])