From 74c4369931fbc49b56ddc8a5837c954aaf4251de Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 28 Jan 2021 18:04:17 +0800 Subject: [PATCH] Implement long message for private session and unify message send as `SendMessageHandler`, fix send failure with quoted image fix #892 --- mirai-core/src/commonMain/kotlin/MiraiImpl.kt | 18 +- .../commonMain/kotlin/contact/AbstractUser.kt | 29 +- .../commonMain/kotlin/contact/FriendImpl.kt | 17 +- .../commonMain/kotlin/contact/GroupImpl.kt | 5 +- .../kotlin/contact/GroupSendMessageImpl.kt | 231 +----------- .../kotlin/contact/NormalMemberImpl.kt | 73 +--- .../kotlin/contact/SendMessageHandler.kt | 357 ++++++++++++++++++ .../commonMain/kotlin/contact/StrangerImpl.kt | 27 +- .../src/commonMain/kotlin/contact/util.kt | 16 +- .../kotlin/message/ForceAsLongMessage.kt | 21 +- .../kotlin/message/incomingSourceImpl.kt | 2 +- .../kotlin/message/outgoingSourceImpl.kt | 11 + .../kotlin/network/highway/Highway.kt | 4 +- .../network/protocol/packet/chat/MultiMsg.kt | 15 +- .../packet/chat/SendMessageMultiProtocol.kt | 30 -- .../chat/receive/MessageSvc.PbSendMsg.kt | 88 +++-- 16 files changed, 550 insertions(+), 394 deletions(-) create mode 100644 mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index fd7ea1305..0a34d0df7 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -695,9 +695,9 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { return jsonText?.let { json.decodeFromString(GroupHonorListData.serializer(), it) } } - internal suspend fun uploadGroupMessageHighway( + internal suspend fun uploadMessageHighway( bot: Bot, - groupCode: Long, + sendMessageHandler: SendMessageHandler<*>, message: Collection, isLong: Boolean, ): String = with(bot.asQQAndroidBot()) { @@ -705,14 +705,12 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { it.messageChain.ensureSequenceIdAvailable() } - val group = getGroupOrFail(groupCode) - val sequenceId = client.atomicNextMessageSequenceId() - val data = message.calculateValidationDataForGroup( + val data = message.calculateValidationData( sequenceId = sequenceId, random = Random.nextInt().absoluteValue, - group + sendMessageHandler ) val response = network.run { @@ -720,7 +718,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { buType = if (isLong) 1 else 2, client = bot.client, messageData = data, - dstUin = Mirai.calculateGroupUinByGroupCode(groupCode) + dstUin = sendMessageHandler.targetUin ).sendAndExpect() } @@ -740,7 +738,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { msgUpReq = listOf( LongMsg.MsgUpReq( msgType = 3, // group - dstUin = Mirai.calculateGroupUinByGroupCode(groupCode), + dstUin = sendMessageHandler.targetUin, msgId = 0, msgUkey = response.proto.msgUkey, needCache = 0, @@ -755,8 +753,8 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { bot = bot, resource = resource, kind = when (isLong) { - true -> ResourceKind.GROUP_LONG_MESSAGE - false -> ResourceKind.GROUP_FORWARD_MESSAGE + true -> ResourceKind.LONG_MESSAGE + false -> ResourceKind.FORWARD_MESSAGE }, commandId = 27, initialTicket = response.proto.msgSig diff --git a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt index 10bbdeb78..0ceb28011 100644 --- a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt +++ b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt @@ -17,9 +17,7 @@ import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.contact.User import net.mamoe.mirai.data.UserInfo import net.mamoe.mirai.event.broadcast -import net.mamoe.mirai.event.events.BeforeImageUploadEvent -import net.mamoe.mirai.event.events.EventCancelledException -import net.mamoe.mirai.event.events.ImageUploadEvent +import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.message.OfflineFriendImage import net.mamoe.mirai.internal.message.getImageType import net.mamoe.mirai.internal.network.highway.ChannelKind @@ -29,7 +27,11 @@ import net.mamoe.mirai.internal.network.highway.postImage import net.mamoe.mirai.internal.network.highway.tryServers import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352 import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn +import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.utils.* import kotlin.coroutines.CoroutineContext @@ -135,4 +137,25 @@ internal abstract class AbstractUser( } } } +} + +@Suppress("DuplicatedCode") +internal suspend fun SendMessageHandler.sendMessageImpl( + message: Message, + preSendEventConstructor: (C, Message) -> MessagePreSendEvent, + postSendEventConstructor: (C, MessageChain, Throwable?, MessageReceipt?) -> MessagePostSendEvent, +): MessageReceipt { + require(!message.isContentEmpty()) { "message is empty" } + + val chain = contact.broadcastMessagePreSendEvent(message, preSendEventConstructor) + + val result = this + .runCatching { sendMessage(message, chain, SendMessageStep.FIRST) } + + // logMessageSent(result.getOrNull()?.source?.plus(chain) ?: chain) // log with source + contact.logMessageSent(chain) + + postSendEventConstructor(contact, chain, result.exceptionOrNull(), result.getOrNull()).broadcast() + + return result.getOrThrow() } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt index 30db216d3..417f7401d 100644 --- a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt @@ -24,12 +24,13 @@ import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.FriendInfoImpl +import net.mamoe.mirai.event.events.FriendMessagePostSendEvent +import net.mamoe.mirai.event.events.FriendMessagePreSendEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList import net.mamoe.mirai.internal.utils.C2CPkgMsgParsingCache import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.message.data.isContentEmpty +import net.mamoe.mirai.message.data.* import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext @@ -80,16 +81,12 @@ internal class FriendImpl( } } + + private val handler: FriendSendMessageHandler by lazy { FriendSendMessageHandler(this) } + @Suppress("DuplicatedCode") override suspend fun sendMessage(message: Message): MessageReceipt { - require(!message.isContentEmpty()) { "message is empty" } - return sendMessageImpl( - message, - friendReceiptConstructor = { MessageReceipt(it, this) }, - tReceiptConstructor = { MessageReceipt(it, this) } - ).also { - logMessageSent(message) - } + return handler.sendMessageImpl(message, ::FriendMessagePreSendEvent, ::FriendMessagePostSendEvent) } override fun toString(): String = "Friend($id)" diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index 06d5f84b2..9b1ee5680 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -120,9 +120,10 @@ internal class GroupImpl( require(!message.isContentEmpty()) { "message is empty" } check(!isBotMuted) { throw BotIsBeingMutedException(this) } - val chain = broadcastGroupMessagePreSendEvent(message) + val chain = broadcastMessagePreSendEvent(message, ::GroupMessagePreSendEvent) - val result = sendMessageImpl(message, chain, GroupMessageSendingStep.FIRST) + val result = GroupSendMessageHandler(this) + .runCatching { sendMessage(message, chain, SendMessageStep.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 f9fd6fa74..0e0843c24 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt @@ -12,61 +12,22 @@ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.MessageTooLargeException import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.EventCancelledException -import net.mamoe.mirai.event.events.GroupMessagePreSendEvent -import net.mamoe.mirai.event.nextEventOrNull -import net.mamoe.mirai.internal.MiraiImpl -import net.mamoe.mirai.internal.forwardMessage -import net.mamoe.mirai.internal.longMessage -import net.mamoe.mirai.internal.message.* -import net.mamoe.mirai.internal.network.Packet -import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket -import net.mamoe.mirai.internal.network.protocol.packet.chat.SendMessageMultiProtocol -import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore -import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg -import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushGroupMsg -import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.utils.currentTimeSeconds - -/** - * Might be recalled with [transformedMessage] `is` [LongMessageInternal] if length estimation failed ([sendMessagePacket]) - */ -internal suspend fun GroupImpl.sendMessageImpl( - originalMessage: Message, - transformedMessage: Message, - step: GroupMessageSendingStep, -): Result> { // Result> - val chain = transformedMessage - .transformSpecialMessages(this) - .convertToLongMessageIfNeeded(step, this) - - chain.findIsInstance()?.source?.ensureSequenceIdAvailable() - - chain.asSequence().filterIsInstance().forEach { image -> - updateFriendImageForGroupMessage(image) - } - - return kotlin.runCatching { - sendMessagePacket( - originalMessage, - transformedMessage, - chain, - step - ) - } -} - +import net.mamoe.mirai.event.events.MessagePreSendEvent +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.toMessageChain /** * Called only in 'public' apis. */ -internal suspend fun GroupImpl.broadcastGroupMessagePreSendEvent(message: Message): MessageChain { +internal suspend fun C.broadcastMessagePreSendEvent( + message: Message, + eventConstructor: (C, Message) -> MessagePreSendEvent +): MessageChain { return kotlin.runCatching { - GroupMessagePreSendEvent(this, message).broadcast() + eventConstructor(this, message).broadcast() }.onSuccess { check(!it.isCancelled) { throw EventCancelledException("cancelled by GroupMessagePreSendEvent") @@ -77,178 +38,6 @@ internal suspend fun GroupImpl.broadcastGroupMessagePreSendEvent(message: Messag } -/** - * - [ForwardMessage] -> [ForwardMessageInternal] (by uploading through highway) - * - ... any others for future - */ -private suspend fun Message.transformSpecialMessages(contact: Contact): MessageChain { - return takeSingleContent()?.let { forward -> - check(forward.nodeList.size <= 200) { - throw MessageTooLargeException( - contact, forward, forward, - "ForwardMessage allows up to 200 nodes, but found ${forward.nodeList.size}" - ) - } - - val resId = MiraiImpl.uploadGroupMessageHighway(contact.bot, contact.id, forward.nodeList, false) - RichMessage.forwardMessage( - resId = resId, - timeSeconds = currentTimeSeconds(), - forwardMessage = forward, - ) - }?.toMessageChain() ?: toMessageChain() -} - -internal enum class GroupMessageSendingStep { +internal enum class SendMessageStep { FIRST, LONG_MESSAGE, FRAGMENTED } - -/** - * Final process - */ -private suspend fun GroupImpl.sendMessagePacket( - originalMessage: Message, - transformedMessage: Message, - finalMessage: MessageChain, - step: GroupMessageSendingStep, -): MessageReceipt { - - val group = this - - var source: OnlineMessageSourceToGroupImpl? = null - - bot.network.run { - SendMessageMultiProtocol.createToGroup( - bot.client, group, finalMessage, - step == GroupMessageSendingStep.FRAGMENTED - ) { source = it }.forEach { packet -> - - when (val resp = packet.sendAndExpect()) { - is MessageSvcPbSendMsg.Response -> { - if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) { - 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" - } - } - is MusicSharePacket.Response -> { - resp.pkg.checkSuccess("send group music share") - - val receipt: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt = - nextEventOrNull(3000) { it.fromAppId == 3116 } - ?: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt.EMPTY - - source = OnlineMessageSourceToGroupImpl( - group, - internalIds = intArrayOf(receipt.messageRandom), - providedSequenceIds = intArrayOf(receipt.sequenceId), - sender = bot, - target = group, - time = currentTimeSeconds().toInt(), - originalMessage = finalMessage - ) - } - } - } - - check(source != null) { - "Internal error: source is not initialized" - } - - try { - source!!.ensureSequenceIdAvailable() - } catch (e: Exception) { - bot.network.logger.warning( - "Timeout awaiting sequenceId for group message(${finalMessage.content.take(10)}). Some features may not work properly", - e - - ) - } - - return MessageReceipt(source!!, group) - } -} - -private suspend fun GroupImpl.uploadGroupLongMessageHighway( - chain: MessageChain -) = MiraiImpl.uploadGroupMessageHighway( - bot, this.id, - listOf( - ForwardMessage.Node( - senderId = bot.id, - time = currentTimeSeconds().toInt(), - messageChain = chain, - senderName = bot.nick - ) - ), - true -) - -private suspend fun MessageChain.convertToLongMessageIfNeeded( - step: GroupMessageSendingStep, - groupImpl: GroupImpl, -): MessageChain { - suspend fun sendLongImpl(): MessageChain { - 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 -> { - // 只需要在第一次发送的时候验证长度 - // 后续重试直接跳过 - if (contains(ForceAsLongMessage)) { - sendLongImpl() - } - verityLength(this, groupImpl) - this - } - GroupMessageSendingStep.LONG_MESSAGE -> { - sendLongImpl() - } - GroupMessageSendingStep.FRAGMENTED -> this - } -} - -/** - * Ensures server holds the cache - */ -private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImage) { - bot.network.run { - ImgStore.GroupPicUp( - bot.client, - uin = bot.id, - groupCode = id, - md5 = image.md5, - size = if (image is OnlineFriendImageImpl) image.delegate.fileLen else 0 - ).sendAndExpect() - } -} diff --git a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt index 660bc46a0..b297e9847 100644 --- a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt @@ -20,13 +20,10 @@ import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.message.OnlineMessageSourceToTempImpl -import net.mamoe.mirai.internal.message.ensureSequenceIdAvailable -import net.mamoe.mirai.internal.message.firstIsInstanceOrNull import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement -import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg -import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToTemp import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -48,66 +45,24 @@ internal class NormalMemberImpl constructor( override fun toString(): String = "NormalMember($id)" - @Suppress("UNCHECKED_CAST") - @JvmSynthetic + private val handler by lazy { GroupTempSendMessageHandler(this) } + + @Suppress("DuplicatedCode") override suspend fun sendMessage(message: Message): MessageReceipt { - require(!message.isContentEmpty()) { "message is empty" } - - val asFriend = this.asFriendOrNull() - val asStranger = this.asStrangerOrNull() - - return (asFriend?.sendMessageImpl( - message, - friendReceiptConstructor = { MessageReceipt(it, asFriend) }, - tReceiptConstructor = { MessageReceipt(it, this) } - ) ?: asStranger?.sendMessageImpl( - message, - strangerReceiptConstructor = { MessageReceipt(it, asStranger) }, - tReceiptConstructor = { MessageReceipt(it, this) } - ) ?: sendMessageImpl(message)).also { logMessageSent(message) } + return asFriendOrNull()?.sendMessage(message)?.convert() + ?: asStrangerOrNull()?.sendMessage(message)?.convert() + ?: handler.sendMessageImpl( + message = message, + preSendEventConstructor = ::GroupTempMessagePreSendEvent, + postSendEventConstructor = ::GroupTempMessagePostSendEvent.cast() + ) } - private suspend fun sendMessageImpl(message: Message): MessageReceipt { - val chain = kotlin.runCatching { - GroupTempMessagePreSendEvent(this, message).broadcast() - }.onSuccess { - check(!it.isCancelled) { - throw EventCancelledException("cancelled by GroupTempMessagePreSendEvent") - } - }.getOrElse { - throw EventCancelledException("exception thrown when broadcasting GroupTempMessagePreSendEvent", it) - }.message.toMessageChain() - - chain.firstIsInstanceOrNull()?.source?.ensureSequenceIdAvailable() - - val result = bot.network.runCatching { - val source: OnlineMessageSourceToTempImpl - MessageSvcPbSendMsg.createToTemp( - bot.client, - this@NormalMemberImpl, - chain - ) { - source = it - }.sendAndExpect().let { - check(it is MessageSvcPbSendMsg.Response.SUCCESS) { - "Send temp message failed: $it" - } - } - MessageReceipt(source, this@NormalMemberImpl) - } - - result.fold( - onSuccess = { - GroupTempMessagePostSendEvent(this, chain, null, it) - }, - onFailure = { - GroupTempMessagePostSendEvent(this, chain, it, null) - } - ).broadcast() - - return result.getOrThrow() + private fun MessageReceipt.convert(): MessageReceipt { + return MessageReceipt(OnlineMessageSourceToTempImpl(source, this@NormalMemberImpl), this@NormalMemberImpl) } + @Suppress("PropertyName") internal var _nameCard: String = memberInfo.nameCard diff --git a/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt new file mode 100644 index 000000000..7a80794e0 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt @@ -0,0 +1,357 @@ +/* + * Copyright 2020 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/master/LICENSE + */ + +package net.mamoe.mirai.internal.contact + +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.event.nextEventOrNull +import net.mamoe.mirai.internal.MiraiImpl +import net.mamoe.mirai.internal.asQQAndroidBot +import net.mamoe.mirai.internal.forwardMessage +import net.mamoe.mirai.internal.longMessage +import net.mamoe.mirai.internal.message.* +import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.QQAndroidClient +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket +import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore +import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.* +import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToFriend +import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToGroup +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.castOrNull +import net.mamoe.mirai.utils.currentTimeSeconds +import java.lang.UnsupportedOperationException + +/** + * 通用处理器 + */ +internal abstract class SendMessageHandler { + abstract val contact: C + abstract val senderName: String + + val messageSourceKind: MessageSourceKind + get() { + return when (contact) { + is Group -> MessageSourceKind.GROUP + is Friend -> MessageSourceKind.FRIEND + is Member -> MessageSourceKind.TEMP + is Stranger -> MessageSourceKind.STRANGER + else -> error("Unsupported contact: $contact") + } + } + + val bot get() = contact.bot.asQQAndroidBot() + + val targetUserUin: Long? get() = contact.castOrNull()?.uin + val targetGroupUin: Long? get() = contact.castOrNull()?.uin + val targetGroupCode: Long? get() = contact.castOrNull()?.groupCode + + val targetOtherClientBotUin: Long? get() = contact.castOrNull()?.bot?.id + + val targetUin: Long get() = targetGroupUin ?: targetOtherClientBotUin ?: contact.id + + val groupInfo: MsgComm.GroupInfo? + get() = if (isToGroup) MsgComm.GroupInfo( + groupCode = targetGroupCode!!, + groupCard = senderName // Cinnamon + ) else null + + val isToGroup: Boolean get() = contact is Group + + suspend fun MessageChain.convertToLongMessageIfNeeded( + step: SendMessageStep, + ): MessageChain { + suspend fun sendLongImpl(): MessageChain { + val resId = uploadLongMessageHighway(this) + return this + RichMessage.longMessage( + brief = takeContent(27), + resId = resId, + timeSeconds = currentTimeSeconds() + ) // LongMessageInternal replaces all contents and preserves metadata + } + return when (step) { + SendMessageStep.FIRST -> { + // 只需要在第一次发送的时候验证长度 + // 后续重试直接跳过 + if (contains(ForceAsLongMessage)) { + sendLongImpl() + } + + if (!contains(IgnoreLengthCheck)) { + verityLength(this, contact) + } + + this + } + SendMessageStep.LONG_MESSAGE -> { + if (contains(DontAsLongMessage)) this // fragmented + else sendLongImpl() + } + SendMessageStep.FRAGMENTED -> this + } + } + + /** + * Final process + */ + suspend fun sendMessagePacket( + originalMessage: Message, + transformedMessage: Message, + finalMessage: MessageChain, + step: SendMessageStep, + ): MessageReceipt { + + val group = contact + + var source: OnlineMessageSource.Outgoing? = null + + bot.network.run { + sendMessageMultiProtocol( + bot.client, finalMessage, + fragmented = step == SendMessageStep.FRAGMENTED + ) { source = it }.forEach { packet -> + + when (val resp = packet.sendAndExpect()) { + is MessageSvcPbSendMsg.Response -> { + if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) { + return when (step) { + SendMessageStep.FIRST -> { + sendMessage(originalMessage, transformedMessage, SendMessageStep.LONG_MESSAGE) + } + SendMessageStep.LONG_MESSAGE -> { + sendMessage(originalMessage, transformedMessage, SendMessageStep.FRAGMENTED) + + } + else -> { + throw MessageTooLargeException( + group, + originalMessage, + finalMessage, + "Message '${finalMessage.content.take(10)}' is too large." + ) + } + } + } + check(resp is MessageSvcPbSendMsg.Response.SUCCESS) { + "Send group message failed: $resp" + } + } + is MusicSharePacket.Response -> { + resp.pkg.checkSuccess("send group music share") + + source = constructSourceFromMusicShareResponse(finalMessage, resp) + } + } + } + + check(source != null) { + "Internal error: source is not initialized" + } + + try { + source!!.ensureSequenceIdAvailable() + } catch (e: Exception) { + bot.network.logger.warning( + "Timeout awaiting sequenceId for group message(${finalMessage.content.take(10)}). Some features may not work properly", + e + + ) + } + + return MessageReceipt(source!!, contact) + } + } + + fun sendMessageMultiProtocol( + client: QQAndroidClient, + message: MessageChain, + fragmented: Boolean, + sourceCallback: (OnlineMessageSource.Outgoing) -> Unit + ): List { + message.takeSingleContent()?.let { musicShare -> + return listOf( + MusicSharePacket( + client, musicShare, contact.id, + targetKind = if (isToGroup) MessageSourceKind.GROUP else MessageSourceKind.FRIEND // always FRIEND + ) + ) + } + + return messageSvcSendMessage(client, contact, message, fragmented, sourceCallback) + } + + abstract val messageSvcSendMessage: ( + client: QQAndroidClient, + contact: C, + message: MessageChain, + fragmented: Boolean, + sourceCallback: (OnlineMessageSource.Outgoing) -> Unit, + ) -> List + + abstract suspend fun constructSourceFromMusicShareResponse( + finalMessage: MessageChain, + response: MusicSharePacket.Response + ): OnlineMessageSource.Outgoing + + open suspend fun uploadLongMessageHighway( + chain: MessageChain + ): String = with(contact) { + return MiraiImpl.uploadMessageHighway( + bot, this@SendMessageHandler, + listOf( + ForwardMessage.Node( + senderId = bot.id, + time = currentTimeSeconds().toInt(), + messageChain = chain, + senderName = bot.nick + ) + ), + true + ) + } + + + open suspend fun postTransformActions(chain: MessageChain) { + + } +} + +/** + * - [ForwardMessage] -> [ForwardMessageInternal] (by uploading through highway) + * - ... any others for future + */ +internal suspend fun SendMessageHandler.transformSpecialMessages(message: Message): MessageChain { + return message.takeSingleContent()?.let { forward -> + check(forward.nodeList.size <= 200) { + throw MessageTooLargeException( + contact, forward, forward, + "ForwardMessage allows up to 200 nodes, but found ${forward.nodeList.size}" + ) + } + + val resId = MiraiImpl.uploadMessageHighway( + bot = contact.bot, + sendMessageHandler = this, + message = forward.nodeList, + isLong = false, + ) + RichMessage.forwardMessage( + resId = resId, + timeSeconds = currentTimeSeconds(), + forwardMessage = forward, + ) + }?.toMessageChain() ?: message.toMessageChain() +} + +/** + * Might be recalled with [transformedMessage] `is` [LongMessageInternal] if length estimation failed ([sendMessagePacket]) + */ +internal suspend fun SendMessageHandler.sendMessage( + originalMessage: Message, + transformedMessage: Message, + step: SendMessageStep, +): MessageReceipt { // Result cannot be in interface. + val chain = transformSpecialMessages(transformedMessage) + .convertToLongMessageIfNeeded(step) + + chain.findIsInstance()?.source?.ensureSequenceIdAvailable() + + postTransformActions(chain) + + return sendMessagePacket(originalMessage, transformedMessage, chain, step) +} + +internal sealed class UserSendMessageHandler( + override val contact: C, +) : SendMessageHandler() { + override val senderName: String get() = bot.nick + + override suspend fun constructSourceFromMusicShareResponse( + finalMessage: MessageChain, + response: MusicSharePacket.Response + ): OnlineMessageSource.Outgoing { + throw UnsupportedOperationException("Sending MusicShare to user is not yet supported") + } +} + +internal class FriendSendMessageHandler( + contact: FriendImpl, +) : UserSendMessageHandler(contact) { + override val messageSvcSendMessage: (client: QQAndroidClient, contact: FriendImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List = + MessageSvcPbSendMsg::createToFriend +} + +internal class StrangerSendMessageHandler( + contact: StrangerImpl, +) : UserSendMessageHandler(contact) { + override val messageSvcSendMessage: (client: QQAndroidClient, contact: StrangerImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List = + MessageSvcPbSendMsg::createToStranger +} + +internal class GroupTempSendMessageHandler( + contact: NormalMemberImpl, +) : UserSendMessageHandler(contact) { + override val messageSvcSendMessage: (client: QQAndroidClient, contact: NormalMemberImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List = + MessageSvcPbSendMsg::createToTemp +} + +internal class GroupSendMessageHandler( + override val contact: GroupImpl, +) : SendMessageHandler() { + override val messageSvcSendMessage: (client: QQAndroidClient, contact: GroupImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List = + MessageSvcPbSendMsg::createToGroup + override val senderName: String + get() = contact.botAsMember.nameCardOrNick + + override suspend fun postTransformActions(chain: MessageChain) { + chain.asSequence().filterIsInstance().forEach { image -> + contact.updateFriendImageForGroupMessage(image) + } + } + + override suspend fun constructSourceFromMusicShareResponse( + finalMessage: MessageChain, + response: MusicSharePacket.Response + ): OnlineMessageSource.Outgoing { + + val receipt: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt = + nextEventOrNull(3000) { it.fromAppId == 3116 } + ?: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt.EMPTY + + return OnlineMessageSourceToGroupImpl( + contact, + internalIds = intArrayOf(receipt.messageRandom), + providedSequenceIds = intArrayOf(receipt.sequenceId), + sender = bot, + target = contact, + time = currentTimeSeconds().toInt(), + originalMessage = finalMessage + ) + } + + companion object { + /** + * Ensures server holds the cache + */ + private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImage) { + bot.network.run { + ImgStore.GroupPicUp( + bot.client, + uin = bot.id, + groupCode = id, + md5 = image.md5, + size = if (image is OnlineFriendImageImpl) image.delegate.fileLen else 0 + ).sendAndExpect() + } + } + } +} diff --git a/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt b/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt index 8d63d5629..7be4301cb 100644 --- a/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt @@ -20,14 +20,17 @@ package net.mamoe.mirai.internal.contact import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.atomic import net.mamoe.mirai.LowLevelApi -import net.mamoe.mirai.contact.Stranger +import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.FriendInfoImpl import net.mamoe.mirai.data.StrangerInfo +import net.mamoe.mirai.event.events.StrangerMessagePostSendEvent +import net.mamoe.mirai.event.events.StrangerMessagePreSendEvent import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.message.OnlineMessageSourceToStrangerImpl import net.mamoe.mirai.internal.network.protocol.packet.list.StrangerList import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.message.data.isContentEmpty +import net.mamoe.mirai.utils.cast import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext @@ -75,16 +78,20 @@ internal class StrangerImpl( } } + private val handler by lazy { StrangerSendMessageHandler(this) } + @Suppress("DuplicatedCode") override suspend fun sendMessage(message: Message): MessageReceipt { - require(!message.isContentEmpty()) { "message is empty" } - return sendMessageImpl( - message, - strangerReceiptConstructor = { MessageReceipt(it, this) }, - tReceiptConstructor = { MessageReceipt(it, this) } - ).also { - logMessageSent(message) - } + return asFriendOrNull()?.sendMessage(message)?.convert() + ?: handler.sendMessageImpl( + message = message, + preSendEventConstructor = ::StrangerMessagePreSendEvent, + postSendEventConstructor = ::StrangerMessagePostSendEvent.cast() + ) + } + + private fun MessageReceipt.convert(): MessageReceipt { + return MessageReceipt(OnlineMessageSourceToStrangerImpl(source, this@StrangerImpl), this@StrangerImpl) } override fun toString(): String = "Stranger($id)" diff --git a/mirai-core/src/commonMain/kotlin/contact/util.kt b/mirai-core/src/commonMain/kotlin/contact/util.kt index 5d1735d12..59738d968 100644 --- a/mirai-core/src/commonMain/kotlin/contact/util.kt +++ b/mirai-core/src/commonMain/kotlin/contact/util.kt @@ -58,12 +58,14 @@ internal suspend fun Friend.sendMessageImpl( chain.firstIsInstanceOrNull()?.source?.ensureSequenceIdAvailable() + lateinit var source: OnlineMessageSourceToFriendImpl val result = bot.network.runCatching { MessageSvcPbSendMsg.createToFriend( bot.client, this@sendMessageImpl, - chain + chain, + false ) { source = it }.forEach { packet -> @@ -116,12 +118,16 @@ internal suspend fun Stranger.sendMessageImpl( bot.client, this@sendMessageImpl, chain, + false, ) { source = it - }.sendAndExpect().let { - check(it is MessageSvcPbSendMsg.Response.SUCCESS) { - "Send stranger message failed: $it" - } + }.forEach { pk -> + pk.sendAndExpect() + .let { + kotlin.check(it is net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg.Response.SUCCESS) { + "Send temp message failed: $it" + } + } } strangerReceiptConstructor(source) } diff --git a/mirai-core/src/commonMain/kotlin/message/ForceAsLongMessage.kt b/mirai-core/src/commonMain/kotlin/message/ForceAsLongMessage.kt index cf415ee33..f8f426917 100644 --- a/mirai-core/src/commonMain/kotlin/message/ForceAsLongMessage.kt +++ b/mirai-core/src/commonMain/kotlin/message/ForceAsLongMessage.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.internal.message import net.mamoe.mirai.message.data.* @@ -19,7 +21,24 @@ internal object ForceAsLongMessage : MessageMetadata, ConstrainSingle, InternalF AbstractMessageKey({ it.safeCast() }) { override val key: MessageKey get() = this - override fun toString(): String = "ForceLongMessage" + override fun toString(): String = "" +} + +/** + * 强制不发 long + */ +internal object DontAsLongMessage : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage, + AbstractMessageKey({ it.safeCast() }) { + override val key: MessageKey get() = this + + override fun toString(): String = "" +} + +internal object IgnoreLengthCheck : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage, + AbstractMessageKey({ it.safeCast() }) { + override val key: MessageKey get() = this + + override fun toString(): String = "" } /** diff --git a/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt b/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt index 9e2553dbc..5ca0011d2 100644 --- a/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt @@ -33,7 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean internal interface MessageSourceInternal { @Transient - val sequenceIds: IntArray + val sequenceIds: IntArray // ids @Transient val internalIds: IntArray // randomId diff --git a/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt b/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt index 57571b8da..516c40399 100644 --- a/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt @@ -103,6 +103,12 @@ internal class OnlineMessageSourceToStrangerImpl( override val sender: Bot, override val target: Stranger ) : OnlineMessageSource.Outgoing.ToStranger(), MessageSourceInternal { + + constructor( + delegate: OnlineMessageSource.Outgoing, + target: Stranger + ) : this(delegate.ids, delegate.internalIds, delegate.time, delegate.originalMessage, delegate.sender, target) + object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceToStranger") override val bot: Bot @@ -123,6 +129,11 @@ internal class OnlineMessageSourceToTempImpl( override val sender: Bot, override val target: Member ) : OnlineMessageSource.Outgoing.ToTemp(), MessageSourceInternal { + constructor( + delegate: OnlineMessageSource.Outgoing, + target: Member + ) : this(delegate.ids, delegate.internalIds, delegate.time, delegate.originalMessage, delegate.sender, target) + object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceToTemp") override val bot: Bot diff --git a/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt b/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt index ee6115d87..6e3f51254 100644 --- a/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt +++ b/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt @@ -89,8 +89,8 @@ internal enum class ResourceKind( PRIVATE_VOICE("private voice"), GROUP_VOICE("group voice"), - GROUP_LONG_MESSAGE("group long message"), - GROUP_FORWARD_MESSAGE("group forward message"), + LONG_MESSAGE("long message"), + FORWARD_MESSAGE("forward message"), ; override fun toString(): String = display diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MultiMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MultiMsg.kt index 89e4094ac..4f9012a48 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MultiMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MultiMsg.kt @@ -12,9 +12,8 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat import kotlinx.io.core.ByteReadPacket -import net.mamoe.mirai.contact.Group import net.mamoe.mirai.internal.QQAndroidBot -import net.mamoe.mirai.internal.contact.groupCode +import net.mamoe.mirai.internal.contact.SendMessageHandler import net.mamoe.mirai.internal.message.toRichTextElems import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient @@ -45,15 +44,16 @@ internal class MessageValidationData( } } -internal fun Collection.calculateValidationDataForGroup( +internal fun Collection.calculateValidationData( sequenceId: Int, random: Int, - targetGroup: Group, + handler: SendMessageHandler<*>, ): MessageValidationData { val msgList = map { chain -> MsgComm.Msg( msgHead = MsgComm.MsgHead( fromUin = chain.senderId, + toUin = handler.targetUserUin ?: 0, msgSeq = sequenceId, msgTime = chain.time, msgUid = 0x01000000000000000L or random.toLongUnsigned(), @@ -62,16 +62,13 @@ internal fun Collection.calculateValidationDataForGroup( msgId = 1 ), msgType = 82, // troop - groupInfo = MsgComm.GroupInfo( - groupCode = targetGroup.groupCode, - groupCard = chain.senderName // Cinnamon - ), + groupInfo = handler.groupInfo, isSrcMsg = false ), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = chain.messageChain.toMessageChain() - .toRichTextElems(targetGroup, withGeneralFlags = false).toMutableList() + .toRichTextElems(handler.contact, withGeneralFlags = false).toMutableList() ) ) ) diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/SendMessageMultiProtocol.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/SendMessageMultiProtocol.kt index 4a05e11fe..a0c916ba8 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/SendMessageMultiProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/SendMessageMultiProtocol.kt @@ -9,33 +9,3 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.internal.contact.takeSingleContent -import net.mamoe.mirai.internal.message.OnlineMessageSourceToGroupImpl -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.internal.network.protocol.packet.chat.receive.createToGroup -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.MessageSourceKind -import net.mamoe.mirai.message.data.MusicShare -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -internal object SendMessageMultiProtocol { - inline fun createToGroup( - client: QQAndroidClient, - group: Group, - message: MessageChain, - fragmented: Boolean, - crossinline sourceCallback: (OnlineMessageSourceToGroupImpl) -> Unit - ): List { - contract { callsInPlace(sourceCallback, InvocationKind.AT_MOST_ONCE) } - - message.takeSingleContent()?.let { musicShare -> - return listOf(MusicSharePacket(client, musicShare, group.id, targetKind = MessageSourceKind.GROUP)) - } - - return MessageSvcPbSendMsg.createToGroup(client, group, message, fragmented, sourceCallback) - } -} \ No newline at end of file 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 eb03964f3..4db27e5f7 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 @@ -146,29 +146,55 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory Unit + ): List { - ///return@buildOutgoingUniPacket - writeProtoBuf( - MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq( - routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = target.uin)), - contentHead = MsgComm.ContentHead(pkgNum = 1), - msgBody = ImMsgBody.MsgBody( + val sequenceIds = AtomicReference() + val randIds = AtomicReference() + return buildOutgoingMessageCommon( + client = client, + message = message, + fragmentTranslator = { + ImMsgBody.MsgBody( richText = ImMsgBody.RichText( - elems = message.toRichTextElems(messageTarget = target, withGeneralFlags = true) + elems = it.toRichTextElems(messageTarget = target, withGeneralFlags = true) ) - ), - msgSeq = source.sequenceIds.single(), - msgRand = source.internalIds.single(), - syncCookie = client.syncingController.syncCookie ?: byteArrayOf() - // msgVia = 1 - ) + ) + }, + pbSendMsgReq = { msgBody, msgSeq, msgRand, contentHead -> + MsgSvc.PbSendMsgReq( + routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = target.uin)), + contentHead = contentHead, + msgBody = msgBody, + msgSeq = msgSeq, + msgRand = msgRand, + syncCookie = client.syncingController.syncCookie ?: byteArrayOf() + // msgVia = 1 + ) + }, + sequenceIds = sequenceIds, + randIds = randIds, + sequenceIdsInitializer = { size -> + IntArray(size) { client.nextFriendSeq() } + }, + postInit = { + source( + OnlineMessageSourceToStrangerImpl( + internalIds = randIds.get(), + sender = client.bot, + target = target, + time = currentTimeSeconds().toInt(), + sequenceIds = sequenceIds.get(), + originalMessage = message + ) + ) + }, + doFragmented = fragmented ) } @@ -180,6 +206,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory Unit ): List { contract { @@ -225,7 +252,8 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory Unit -): OutgoingPacket { +): List { contract { callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) } @@ -457,33 +487,27 @@ internal inline fun MessageSvcPbSendMsg.createToTemp( client, member, message, + fragmented, source - ) + ).let { listOf(it) } } internal inline fun MessageSvcPbSendMsg.createToStranger( client: QQAndroidClient, stranger: Stranger, message: MessageChain, + fragmented: Boolean, crossinline sourceCallback: (OnlineMessageSourceToStrangerImpl) -> Unit -): OutgoingPacket { +): List { contract { callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) } - val source = OnlineMessageSourceToStrangerImpl( - internalIds = intArrayOf(Random.nextInt().absoluteValue), - sender = client.bot, - target = stranger, - time = currentTimeSeconds().toInt(), - sequenceIds = intArrayOf(client.atomicNextMessageSequenceId()), - originalMessage = message - ) - sourceCallback(source) return createToStrangerImpl( client, stranger, message, - source + fragmented, + sourceCallback ) } @@ -491,6 +515,7 @@ internal inline fun MessageSvcPbSendMsg.createToFriend( client: QQAndroidClient, qq: Friend, message: MessageChain, + fragmented: Boolean, crossinline sourceCallback: (OnlineMessageSourceToFriendImpl) -> Unit ): List { contract { @@ -500,6 +525,7 @@ internal inline fun MessageSvcPbSendMsg.createToFriend( client, qq, message, + fragmented, sourceCallback ) }