diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index ca37142e2..534a3f840 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -116,7 +116,7 @@ internal class GroupImpl( val chain = broadcastGroupMessagePreSendEvent(message) - val result = sendMessageImpl(message, chain, false) + val result = sendMessageImpl(message, chain, GroupMessageSendingStep.FIRST) // logMessageSent(result.getOrNull()?.source?.plus(chain) ?: chain) // log with source logMessageSent(chain) diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt index ed4abf218..00c54fec7 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt @@ -34,11 +34,11 @@ import net.mamoe.mirai.utils.currentTimeSeconds internal suspend fun GroupImpl.sendMessageImpl( originalMessage: Message, transformedMessage: Message, - forceAsLongMessage: Boolean, + step: GroupMessageSendingStep, ): Result<MessageReceipt<Group>> { // Result<MessageReceipt<Group>> val chain = transformedMessage .transformSpecialMessages(this) - .convertToLongMessageIfNeeded(originalMessage, forceAsLongMessage, this) + .convertToLongMessageIfNeeded(step, this) chain.findIsInstance<QuoteReply>()?.source?.ensureSequenceIdAvailable() @@ -49,8 +49,9 @@ internal suspend fun GroupImpl.sendMessageImpl( return kotlin.runCatching { sendMessagePacket( originalMessage, + transformedMessage, chain, - allowResendAsLongMessage = transformedMessage.takeSingleContent<LongMessageInternal>() == null + step ) } } @@ -94,37 +95,68 @@ private suspend fun Message.transformSpecialMessages(contact: Contact): MessageC }?.toMessageChain() ?: toMessageChain() } +internal enum class GroupMessageSendingStep { + FIRST, LONG_MESSAGE, FRAGMENTED +} + /** * Final process */ private suspend fun GroupImpl.sendMessagePacket( originalMessage: Message, + transformedMessage: Message, finalMessage: MessageChain, - allowResendAsLongMessage: Boolean, + step: GroupMessageSendingStep, ): MessageReceipt<Group> { + + println("SENDING") + println(originalMessage) + println(finalMessage) + val group = this val source: OnlineMessageSourceToGroupImpl bot.network.run { - MessageSvcPbSendMsg.createToGroup(bot.client, group, finalMessage) { source = it } - .sendAndExpect<MessageSvcPbSendMsg.Response>().let { resp -> + MessageSvcPbSendMsg.createToGroup( + bot.client, + group, + finalMessage, + step == GroupMessageSendingStep.FRAGMENTED + ) { source = it }.forEach { packet -> + packet.sendAndExpect<MessageSvcPbSendMsg.Response>().let { resp -> if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) { - if (allowResendAsLongMessage) { - return sendMessageImpl(originalMessage, finalMessage, true).getOrThrow() - } else { - throw MessageTooLargeException( - group, - originalMessage, - finalMessage, - "Message '${finalMessage.content.take(10)}' is too large." - ) - } + return when (step) { + GroupMessageSendingStep.FIRST -> { + sendMessageImpl( + originalMessage, + transformedMessage, + GroupMessageSendingStep.LONG_MESSAGE + ) + } + GroupMessageSendingStep.LONG_MESSAGE -> { + sendMessageImpl( + originalMessage, + transformedMessage, + GroupMessageSendingStep.FRAGMENTED + ) + + } + else -> { + throw MessageTooLargeException( + group, + originalMessage, + finalMessage, + "Message '${finalMessage.content.take(10)}' is too large." + ) + } + }.getOrThrow() } check(resp is MessageSvcPbSendMsg.Response.SUCCESS) { "Send group message failed: $resp" } } + } } try { @@ -156,21 +188,26 @@ private suspend fun GroupImpl.uploadGroupLongMessageHighway( ) private suspend fun MessageChain.convertToLongMessageIfNeeded( - originalMessage: Message, - forceAsLongMessage: Boolean, + step: GroupMessageSendingStep, groupImpl: GroupImpl, ): MessageChain { - if (forceAsLongMessage || this.shouldSendAsLongMessage(originalMessage, groupImpl)) { - val resId = groupImpl.uploadGroupLongMessageHighway(this) - - return this + RichMessage.longMessage( - brief = takeContent(27), - resId = resId, - timeSeconds = currentTimeSeconds() - ) // LongMessageInternal replaces all contents and preserves metadata + return when (step) { + GroupMessageSendingStep.FIRST -> { + // 只需要在第一次发送的时候验证长度 + // 后续重试直接跳过 + verityLength(this, groupImpl) + this + } + GroupMessageSendingStep.LONG_MESSAGE -> { + val resId = groupImpl.uploadGroupLongMessageHighway(this) + this + RichMessage.longMessage( + brief = takeContent(27), + resId = resId, + timeSeconds = currentTimeSeconds() + ) // LongMessageInternal replaces all contents and preserves metadata + } + GroupMessageSendingStep.FRAGMENTED -> this } - - return this } /** @@ -187,9 +224,3 @@ private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImag ).sendAndExpect<ImgStore.GroupPicUp.Response>() } } - -private fun MessageChain.shouldSendAsLongMessage(originalMessage: Message, target: Contact): Boolean { - val length = verityLength(originalMessage, target) - - return length > 700 || countImages() > 1 -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt b/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt index 3deff0707..7791f9138 100644 --- a/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt @@ -56,9 +56,9 @@ private fun <T> T.toJceDataImpl(subject: ContactOrBot?): ImMsgBody.SourceMsg toUin = targetId, // group msgType = 9, // 82? c2cCmd = 11, - msgSeq = sequenceIds.single(), // TODO !! + msgSeq = sequenceIds.first(), msgTime = time, - msgUid = pdReserve.origUids!!.single(), // TODO !! + msgUid = pdReserve.origUids!!.first(), // groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode), isSrcMsg = true ), @@ -150,24 +150,30 @@ internal class OnlineMessageSourceToGroupImpl( get() = sender override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false) - private val sequenceIdDeferred: Deferred<Int?> = - coroutineScope.asyncFromEventOrNull<SendGroupMessageReceipt, Int>( - timeoutMillis = 3000 + private val sequenceIdDeferred: Deferred<IntArray?> = run { + val multi = mutableMapOf<Int, Int>() + coroutineScope.asyncFromEventOrNull<SendGroupMessageReceipt, IntArray>( + timeoutMillis = 3000L * this@OnlineMessageSourceToGroupImpl.internalIds.size ) { if (it.messageRandom in this@OnlineMessageSourceToGroupImpl.internalIds) { - it.sequenceId + multi[it.messageRandom] = it.sequenceId + if (multi.size == this@OnlineMessageSourceToGroupImpl.internalIds.size) { + IntArray(multi.size) { index -> + multi[this@OnlineMessageSourceToGroupImpl.internalIds[index]]!! + } + } else null } else null } + } @OptIn(ExperimentalCoroutinesApi::class) override val sequenceIds: IntArray - get() = intArrayOf( - when { - sequenceIdDeferred.isCompleted -> sequenceIdDeferred.getCompleted() ?: -1 - !sequenceIdDeferred.isActive -> -1 + get() = when { + sequenceIdDeferred.isCompleted -> sequenceIdDeferred.getCompleted() ?: intArrayOf() + !sequenceIdDeferred.isActive -> intArrayOf() else -> error("sequenceIds not yet available") } - ) + suspend fun ensureSequenceIdAvailable() = kotlin.run { sequenceIdDeferred.await() } 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 43c7e3e1a..eb03964f3 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 @@ -26,7 +26,6 @@ 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.network.protocol.data.proto.MsgCtrl import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc -import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket @@ -107,9 +106,13 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg. sequenceIds: AtomicReference<IntArray>, sequenceIdsInitializer: (Int) -> IntArray, randIds: AtomicReference<IntArray>, + doFragmented: Boolean = true, postInit: () -> Unit ): List<OutgoingPacket> { - val fragmented = message.fragmented() + val fragmented = if (doFragmented) + message.fragmented() + else listOf(message) + val response = mutableListOf<OutgoingPacket>() val div = if (fragmented.size == 1) 0 else Random.nextInt().absoluteValue val pkgNum = fragmented.size @@ -291,12 +294,88 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg. * 发送群消息 */ @Suppress("FunctionName") - internal fun createToGroupImpl( + internal inline fun createToGroupImpl( client: QQAndroidClient, targetGroup: Group, message: MessageChain, - source: OnlineMessageSourceToGroupImpl - ): OutgoingPacket = buildOutgoingUniPacket(client) { + fragmented: Boolean, + crossinline sourceCallback: (OnlineMessageSourceToGroupImpl) -> Unit + ): List<OutgoingPacket> { + val sequenceIds = AtomicReference<IntArray>() + val randIds = AtomicReference<IntArray>() + return buildOutgoingMessageCommon( + client = client, + message = message, + fragmentTranslator = { subChain -> + ImMsgBody.MsgBody( + richText = ImMsgBody.RichText( + elems = subChain.toRichTextElems(messageTarget = targetGroup, withGeneralFlags = true), + ptt = subChain[PttMessage]?.run { + ImMsgBody.Ptt( + fileName = fileName.toByteArray(), + fileMd5 = md5, + boolValid = true, + fileSize = fileSize.toInt(), + fileType = 4, + pbReserve = byteArrayOf(0), + format = let { + if (it is Voice) { + it.codec + } else { + 0 + } + } + ) + } + + ) + ) + }, + pbSendMsgReq = { msgBody, msgSeq, msgRand, contentHead -> + MsgSvc.PbSendMsgReq( + routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = targetGroup.groupCode)), + contentHead = contentHead, + msgBody = msgBody, + msgSeq = msgSeq, + msgRand = msgRand, + syncCookie = client.syncingController.syncCookie ?: byteArrayOf(), + msgVia = 1, + msgCtrl = + if (message[ForwardMessageInternal] != null) + MsgCtrl.MsgCtrl(msgFlag = 4) + else null + ) + }, + sequenceIds = sequenceIds, + randIds = randIds, + sequenceIdsInitializer = { size -> + IntArray(size) { client.nextFriendSeq() } + }, + postInit = { + randIds.get().forEach { id -> + client.syncingController.pendingGroupMessageReceiptCacheList.addCache( + PendingGroupMessageReceiptSyncId( + messageRandom = id, + ) + ) + } + sourceCallback( + OnlineMessageSourceToGroupImpl( + targetGroup, + internalIds = randIds.get(), + sender = client.bot, + target = targetGroup, + time = currentTimeSeconds().toInt(), + originalMessage = message//, + // sourceMessage = message + ) + ) + }, + doFragmented = fragmented + ) + } + /* + = buildOutgoingUniPacket(client) { ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) // DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString()) @@ -339,6 +418,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg. ) ) } + */ override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val response = readProtoBuf(MsgSvc.PbSendMsgResp.serializer()) @@ -429,33 +509,17 @@ internal inline fun MessageSvcPbSendMsg.createToGroup( client: QQAndroidClient, group: Group, message: MessageChain, + fragmented: Boolean, crossinline sourceCallback: (OnlineMessageSourceToGroupImpl) -> Unit -): OutgoingPacket { +): List<OutgoingPacket> { contract { callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) } - val messageRandom = Random.nextInt().absoluteValue - val source = OnlineMessageSourceToGroupImpl( - group, - internalIds = intArrayOf(messageRandom), - sender = client.bot, - target = group, - time = currentTimeSeconds().toInt(), - originalMessage = message//, - // sourceMessage = message - ) - - sourceCallback(source) - - client.syncingController.pendingGroupMessageReceiptCacheList.addCache( - PendingGroupMessageReceiptSyncId( - messageRandom = messageRandom, - ) - ) return createToGroupImpl( client, group, message, - source + fragmented, + sourceCallback ) } \ No newline at end of file