From 3f523e6f7777d651c3788b552dc9cb18d284d3b3 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 27 Mar 2020 13:27:34 +0800 Subject: [PATCH] Long message --- .../qqandroid/utils/cryptor/arraycopy.kt | 9 + .../net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 32 ++ .../qqandroid/message/MessageSourceImpl.kt | 2 +- .../mamoe/mirai/qqandroid/message/messages.kt | 98 +++-- .../qqandroid/network/QQAndroidClient.kt | 2 +- .../network/protocol/data/proto/LongMsg.kt | 72 ++++ .../protocol/data/proto/MsgTransmit.kt | 25 ++ .../network/protocol/data/proto/MultiMsg.kt | 80 ++++ .../network/protocol/packet/PacketFactory.kt | 6 +- .../network/protocol/packet/chat/MultiMsg.kt | 222 ++++++++++ .../utils/cryptor/MultiMsgCryptor.kt | 32 ++ .../qqandroid/utils/cryptor/class_1457.kt | 382 ++++++++++++++++++ .../qqandroid/utils/cryptor/arraycopy.kt | 9 + .../kotlin/net.mamoe.mirai/lowLevelApi.kt | 9 + .../net.mamoe.mirai/message/data/PlainText.kt | 18 +- .../message/data/RichMessage.kt | 47 +++ 16 files changed, 996 insertions(+), 49 deletions(-) create mode 100644 mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/LongMsg.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgTransmit.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MultiMsg.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/MultiMsg.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/MultiMsgCryptor.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/class_1457.kt create mode 100644 mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt diff --git a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt new file mode 100644 index 000000000..0a0470e79 --- /dev/null +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt @@ -0,0 +1,9 @@ +package net.mamoe.mirai.qqandroid.utils.cryptor + +internal actual fun arraycopy( + src: ByteArray, + srcPos: Int, + dest: ByteArray, + destPos: Int, + length: Int +) = System.arraycopy(src, srcPos, dest, destPos, length) \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index cf5ea1bf2..5c6e8ceea 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -33,11 +33,13 @@ import net.mamoe.mirai.message.data.* import net.mamoe.mirai.qqandroid.contact.MemberInfoImpl import net.mamoe.mirai.qqandroid.contact.QQImpl import net.mamoe.mirai.qqandroid.contact.checkIsGroupImpl +import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend import net.mamoe.mirai.qqandroid.message.OnlineFriendImageImpl import net.mamoe.mirai.qqandroid.message.OnlineGroupImageImpl import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.MultiMsg import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList @@ -45,6 +47,8 @@ import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.encodeToString import kotlin.collections.asSequence import kotlin.coroutines.CoroutineContext +import kotlin.math.absoluteValue +import kotlin.random.Random @OptIn(MiraiInternalAPI::class) internal expect class QQAndroidBot constructor( @@ -360,6 +364,34 @@ internal abstract class QQAndroidBotBase constructor( return json.parse(GroupActiveData.serializer(), rep) } + @LowLevelAPI + @MiraiExperimentalAPI + override suspend fun _lowLevelSendLongMessage(groupCode: Long, message: Message) { + val source = MessageSourceFromSendFriend( + messageRandom = Random.nextInt().absoluteValue, + senderId = client.uin, + toUin = Group.calculateGroupUinByGroupCode(groupCode), + time = currentTimeSeconds, + groupId = groupCode, + originalMessage = message.asMessageChain(), + sequenceId = 0 + // sourceMessage = message + ) + + // TODO: 2020/3/26 util 方法来添加单例元素 + val toSend = buildMessageChain { + source.originalMessage.filter { it !is MessageSource }.forEach { + add(it) + } + add(source) + } + network.run { + val response = MultiMsg.ApplyUp.createForLongMessage(this@QQAndroidBotBase.client, toSend, groupCode) + .sendAndExpect() + println(response._miraiContentToString()) + } + } + override suspend fun queryImageUrl(image: Image): String = when (image) { is OnlineFriendImageImpl -> image.originUrl is OnlineGroupImageImpl -> image.originUrl diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt index 4b9be436e..c33a7543d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt @@ -254,7 +254,7 @@ internal class MessageSourceFromSendGroup( override val groupId: Long, override val originalMessage: MessageChain ) : MessageSourceFromSend() { - private lateinit var sequenceIdDeferred: Deferred + internal lateinit var sequenceIdDeferred: Deferred @OptIn(ExperimentalCoroutinesApi::class) override val id: Long diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt index 3c14f3c97..0d11ee9aa 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt @@ -6,6 +6,7 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file: OptIn(MiraiExperimentalAPI::class, MiraiInternalAPI::class, LowLevelAPI::class, ExperimentalUnsignedTypes::class) package net.mamoe.mirai.qqandroid.message @@ -218,6 +219,8 @@ private val atAllData = ImMsgBody.Elem( ) ) +private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。") + @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList { val elements = mutableListOf() @@ -233,31 +236,49 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList elements.add( + ImMsgBody.Elem( + lightApp = ImMsgBody.LightAppElem( + data = byteArrayOf(1) + content + ) + ) + ) + is MergedForwardedMessage -> { + elements.add( + ImMsgBody.Elem( + richMsg = ImMsgBody.RichMsg( + serviceId = 35, + template1 = byteArrayOf(1) + content + ) + ) + ) + transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN) // required + } + else -> elements.add( + ImMsgBody.Elem( + richMsg = ImMsgBody.RichMsg( + serviceId = when (it) { + is XmlMessage -> 60 + is JsonMessage -> 1 + is MergedForwardedMessage -> 35 + else -> error("unsupported RichMessage: ${it::class.simpleName}") + }, + template1 = byteArrayOf(1) + content + ) + ) + ) + } + } + when (it) { is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue))) is At -> { elements.add(ImMsgBody.Elem(text = it.toJceData())) elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) } - is LightApp -> elements.add( - ImMsgBody.Elem( - lightApp = ImMsgBody.LightAppElem( - data = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray()) - ) - ) - ) - is RichMessage -> elements.add( - ImMsgBody.Elem( - richMsg = ImMsgBody.RichMsg( - serviceId = when (it) { - is XmlMessage -> 60 - is JsonMessage -> 1 - else -> error("unsupported RichMessage") - }, - template1 = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray()) - ) - ) - ) is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData())) is OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) @@ -267,16 +288,17 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList { if (forGroup) { check(it is QuoteReplyToSend.ToGroup) { - "sending a quote to group using QuoteReplyToSend.ToFriend" + "sending a quote to group using QuoteReplyToSend.ToFriend is prohibited" } if (it.sender is Member) { transformOneMessage(it.createAt()) } - transformOneMessage(" ".toMessage()) + transformOneMessage(PlainText(" ")) } } is QuoteReply, - is MessageSource -> { + is MessageSource, + -> { } else -> error("unsupported message type: ${it::class.simpleName}") @@ -358,7 +380,7 @@ internal fun MsgComm.Msg.toMessageChain(): MessageChain { return buildMessageChain(elements.size + 1) { +MessageSourceFromMsg(delegate = this@toMessageChain) elements.joinToMessageChain(this) - }.removeAtIfHasQuoteReply() + }.cleanupRubbishMessageElements() } // These two functions are not identical, dont combine. @@ -369,11 +391,31 @@ internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain { return buildMessageChain(elements.size + 1) { +MessageSourceFromServer(delegate = this@toMessageChain) elements.joinToMessageChain(this) - }.removeAtIfHasQuoteReply() + }.cleanupRubbishMessageElements() +} + +private fun MessageChain.cleanupRubbishMessageElements(): MessageChain { + var last: SingleMessage? = null + return buildMessageChain(initialSize = this.count()) { + this@cleanupRubbishMessageElements.forEach { element -> + if (last == null) { + last = element + return@forEach + } else { + if (last is MergedForwardedMessage && element is PlainText) { + if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) { + last = element + return@forEach + } + } + } + + add(element) + last = element + } + } } -private fun MessageChain.removeAtIfHasQuoteReply(): MessageChain = - this /* if (this.any()) { var removed = false @@ -387,9 +429,6 @@ private fun MessageChain.removeAtIfHasQuoteReply(): MessageChain = }.asMessageChain() } else this*/ -@OptIn( - MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class, LowLevelAPI::class -) internal fun List.joinToMessageChain(message: MessageChainBuilder) { this.forEach { when { @@ -425,6 +464,7 @@ internal fun List.joinToMessageChain(message: MessageChainBuilde when (it.richMsg.serviceId) { 1 -> message.add(JsonMessage(content)) 60 -> message.add(XmlMessage(content)) + 35 -> message.add(MergedForwardedMessage(content)) else -> { @Suppress("DEPRECATION") MiraiLogger.debug { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index 3abcf94ee..6d0aeccbd 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -101,7 +101,7 @@ internal open class QQAndroidClient( var openAppId: Long = 715019303L val apkVersionName: ByteArray get() = "8.2.7".toByteArray() - val buildVer: String get() = "8.2.7.4410" + val buildVer: String get() = "8.2.7.4410" // 8.2.0.1296 private val messageSequenceId: AtomicInt = atomic(22911) internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/LongMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/LongMsg.kt new file mode 100644 index 000000000..73e88414a --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/LongMsg.kt @@ -0,0 +1,72 @@ +package net.mamoe.mirai.qqandroid.network.protocol.data.proto + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoId +import net.mamoe.mirai.qqandroid.io.ProtoBuf +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY + +internal class LongMsg : ProtoBuf { + @Serializable + class MsgDeleteReq( + @ProtoId(1) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val msgType: Int = 0 + ) : ProtoBuf + + @Serializable + class MsgDeleteRsp( + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MsgDownReq( + @ProtoId(1) val srcUin: Int = 0, + @ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val msgType: Int = 0, + @ProtoId(4) val needCache: Int = 0 + ) : ProtoBuf + + @Serializable + class MsgDownRsp( + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val msgContent: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MsgUpReq( + @ProtoId(1) val msgType: Int = 0, + @ProtoId(2) val dstUin: Long = 0L, + @ProtoId(3) val msgId: Int = 0, + @ProtoId(4) val msgContent: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val storeType: Int = 0, + @ProtoId(6) val msgUkey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val needCache: Int = 0 + ) : ProtoBuf + + @Serializable + class MsgUpRsp( + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val msgId: Int = 0, + @ProtoId(3) val msgResid: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class ReqBody( + @ProtoId(1) val subcmd: Int = 0, + @ProtoId(2) val termType: Int = 0, + @ProtoId(3) val platformType: Int = 0, + @ProtoId(4) val msgUpReq: List? = null, + @ProtoId(5) val msgDownReq: List? = null, + @ProtoId(6) val msgDelReq: List? = null, + @ProtoId(10) val agentType: Int = 0 + ) : ProtoBuf + + @Serializable + class RspBody( + @ProtoId(1) val subcmd: Int = 0, + @ProtoId(2) val msgUpRsp: List? = null, + @ProtoId(3) val msgDownRsp: List? = null, + @ProtoId(4) val msgDelRsp: List? = null + ) : ProtoBuf +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgTransmit.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgTransmit.kt new file mode 100644 index 000000000..fcd15eb7c --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgTransmit.kt @@ -0,0 +1,25 @@ +package net.mamoe.mirai.qqandroid.network.protocol.data.proto + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoId +import net.mamoe.mirai.qqandroid.io.ProtoBuf +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY + +internal class MsgTransmit : ProtoBuf { + @Serializable + class PbMultiMsgItem( + @ProtoId(1) val fileName: String = "", + @ProtoId(2) val buffer: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class PbMultiMsgNew( + @ProtoId(1) val msg: List? = null + ) : ProtoBuf + + @Serializable + class PbMultiMsgTransmit( + @ProtoId(1) val msg: List? = null, + @ProtoId(2) val pbItemList: List? = null + ) : ProtoBuf +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MultiMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MultiMsg.kt new file mode 100644 index 000000000..f77a52c39 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MultiMsg.kt @@ -0,0 +1,80 @@ +package net.mamoe.mirai.qqandroid.network.protocol.data.proto + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoId +import net.mamoe.mirai.qqandroid.io.ProtoBuf +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY + +@Serializable +internal class MultiMsg : ProtoBuf { + @Serializable + class ExternMsg( + @ProtoId(1) val channelType: Int = 0 + ) : ProtoBuf + + @Serializable + class MultiMsgApplyDownReq( + @ProtoId(1) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val msgType: Int = 0, + @ProtoId(3) val srcUin: Long = 0L + ) : ProtoBuf + + @Serializable + class MultiMsgApplyDownRsp( + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val msgKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val uint32DownIp: List? = null, + @ProtoId(5) val uint32DownPort: List? = null, + @ProtoId(6) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val msgExternInfo: MultiMsg.ExternMsg? = null, + @ProtoId(8) val bytesDownIpV6: List? = null, + @ProtoId(9) val uint32DownV6Port: List? = null + ) : ProtoBuf + + @Serializable + class MultiMsgApplyUpReq( + @ProtoId(1) val dstUin: Long = 0L, + @ProtoId(2) val msgSize: Long = 0L, + @ProtoId(3) val msgMd5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val msgType: Int = 0, + @ProtoId(5) val applyId: Int = 0 + ) : ProtoBuf + + @Serializable + class MultiMsgApplyUpRsp( + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val msgUkey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val uint32UpIp: List? = null, + @ProtoId(5) val uint32UpPort: List? = null, + @ProtoId(6) val blockSize: Long = 0L, + @ProtoId(7) val upOffset: Long = 0L, + @ProtoId(8) val applyId: Int = 0, + @ProtoId(9) val msgKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val msgSig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(11) val msgExternInfo: MultiMsg.ExternMsg? = null, + @ProtoId(12) val bytesUpIpV6: List? = null, + @ProtoId(13) val uint32UpV6Port: List? = null + ) : ProtoBuf + + @Serializable + class ReqBody( + @ProtoId(1) val subcmd: Int = 0, + @ProtoId(2) val termType: Int = 0, + @ProtoId(3) val platformType: Int = 0, + @ProtoId(4) val netType: Int = 0, + @ProtoId(5) val buildVer: String = "", + @ProtoId(6) val multimsgApplyupReq: List? = null, + @ProtoId(7) val multimsgApplydownReq: List? = null, + @ProtoId(8) val buType: Int = 0, + @ProtoId(9) val reqChannelType: Int = 0 + ) : ProtoBuf + + @Serializable + class RspBody( + @ProtoId(1) val subcmd: Int = 0, + @ProtoId(2) val multimsgApplyupRsp: List? = null, + @ProtoId(3) val multimsgApplydownRsp: List? = null + ) : ProtoBuf +} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index 5ee2ded4a..54556697e 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -11,9 +11,10 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet import kotlinx.io.core.* import kotlinx.io.pool.useInstance -import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.event.Event import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.network.Packet +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.MultiMsg import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore @@ -144,7 +145,8 @@ internal object KnownPacketFactories { TroopManagement.EditGroupNametag, TroopManagement.Kick, Heartbeat.Alive, - PbMessageSvc.PbMsgWithDraw + PbMessageSvc.PbMsgWithDraw, + MultiMsg.ApplyUp ) object IncomingFactories : List> by mutableListOf( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/MultiMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/MultiMsg.kt new file mode 100644 index 000000000..245bb7496 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/MultiMsg.kt @@ -0,0 +1,222 @@ +/* + * 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 + */ + +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai.qqandroid.network.protocol.packet.chat + +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.Bot +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf +import net.mamoe.mirai.qqandroid.io.serialization.toByteArray +import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf +import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend +import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup +import net.mamoe.mirai.qqandroid.message.toRichTextElems +import net.mamoe.mirai.qqandroid.network.Packet +import net.mamoe.mirai.qqandroid.network.QQAndroidClient +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgTransmit +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MultiMsg +import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory +import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.MiraiPlatformUtils +import net.mamoe.mirai.utils._miraiContentToString + +internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor( + val data: ByteArray, + val md5: ByteArray = MiraiPlatformUtils.md5(data), +) { + override fun toString(): String { + return "MessageValidationData(data=, md5=${md5.contentToString()})" + } +} + +@OptIn(MiraiInternalAPI::class) +internal fun MessageChain.calculateValidationData( + bot: Bot +): MessageValidationData { + // top_package.akkv#method_42702 + val source: MessageSource by this.orElse { error("internal error: calculateValidationData: cannot find MessageSource, chain=${this._miraiContentToString()}") } + + check(source is MessageSourceFromSendGroup || source is MessageSourceFromSendFriend) { + "internal error: calculateValidationData: MessageSource must be " + } + + val richTextElems = this.toRichTextElems(source is MessageSourceFromSendGroup) + + val msgTransmit = MsgTransmit.PbMultiMsgTransmit( + msg = listOf( + MsgComm.Msg( + msgHead = MsgComm.MsgHead( + fromUin = source.senderId, + msgSeq = source.sequenceId, + msgTime = source.time.toInt(), + msgUid = source.messageRandom.toLong(), // TODO: 2020/3/26 CHECK IT + mutiltransHead = MsgComm.MutilTransHead( + status = 0, + msgId = 1 + ), + msgType = 82, // troop + groupInfo = MsgComm.GroupInfo( + groupCode = source.toUin, + groupCard = bot.nick, + ), + ), + msgBody = ImMsgBody.MsgBody( + richText = ImMsgBody.RichText( + elems = richTextElems + ) + ) + ) + ) + ) + + val bytes = msgTransmit.toByteArray(MsgTransmit.PbMultiMsgTransmit.serializer()) + + return MessageValidationData(MiraiPlatformUtils.zip(bytes)) +} + +/* + +=======================处理客户端到服务器======================= +flag1=0x0000000B(11), flag2=1, sequenceId = 00 00 E0 90, flag3=00, // 解密 bodyouter by D2 key +Packet 20:02:51 : ByteReadPacket outer body decrypted=00 00 00 28 00 00 00 18 4D 65 73 73 61 67 65 53 76 63 2E 50 62 53 65 6E 64 4D 73 67 00 00 00 08 8A 51 B1 25 00 00 00 04 00 00 02 7A 0A 08 12 06 08 F6 DD 96 FC 03 12 07 08 01 10 00 18 F2 46 1A D5 04 0A D2 04 12 A9 03 62 A6 03 0A A1 03 01 78 9C 7D 91 4B 4F DB 40 10 C7 BF CA 6A 2F 3E 81 1F 21 0D 91 6C 23 F1 48 15 CA A3 28 04 89 5C AA C5 1E 9B 15 6B 3B F5 AE 97 38 27 E8 05 04 5C B9 21 71 A1 95 B8 D0 1E 2A 24 54 F5 CB 20 25 F0 31 18 1B AA DE 90 46 AB 9D 9D D9 F9 CF 6F C6 5D 18 25 82 68 C8 25 CF 52 CF B0 67 2D 83 40 1A 64 21 4F 63 CF E8 6F 77 66 E6 0D 22 15 4B 43 26 B2 14 3C A3 04 69 90 05 DF 4D 64 4C 24 E4 9A 07 D0 5D F6 68 A3 49 89 82 64 28 98 AA 7D 9B 12 16 A8 AA 26 D5 1C 0E D7 0B A1 F8 BA 8C 29 D9 CB 39 44 18 77 1A 68 8E 8D 16 49 A9 A5 0E AB 83 E9 10 4D 6B 4A 92 2F 39 48 1E 7A 74 73 BB BD B1 16 7C B4 07 65 B9 37 E2 6D 4B 1F 9A 81 B5 3B B6 16 57 F9 4A F0 69 D5 71 3E 2C 2D F6 0F 76 3A 5D 25 C7 73 5B 03 D9 1F 8D 8B AF 83 9E 39 6A 44 9F 75 6F 4D 55 A5 22 2E 60 83 25 80 B2 CD F9 A6 DD 68 B5 5A 4E DB B6 28 91 59 91 07 80 7D 75 51 09 FD 22 17 1E A5 24 12 2C 46 24 44 08 71 32 3D 1E A7 75 34 79 83 E8 D4 61 9B FA 2E 47 64 22 58 99 15 EA F5 41 71 25 C0 7F 1F EE F1 E8 87 6B BE 26 BA FB 39 D9 E7 61 08 28 10 31 21 01 5B 52 A5 80 5A CE F4 5D 59 24 09 CB 4B FF E9 DB C3 E4 E4 CF F4 FA FB D3 D5 F9 E4 EE 62 7A F9 7B 7A 7F 3A 3D FE E9 9A FF 32 5C B3 EA 05 7F D4 44 24 AD 69 9F 8F CF 26 37 B7 CF 77 BF 26 7F 2F 29 E1 41 B5 8D FF 7B C1 DB 70 58 8D 78 C6 AE C5 4C 5C A9 FF 02 0F 69 BA A1 10 23 12 4D 0A 4B 0A 49 E4 BD A0 E7 9A 84 51 51 E6 9A 82 E4 B8 8D E6 94 AF E6 8C 81 E6 9F A5 E7 9C 8B 5B E8 BD AC E5 8F 91 E5 A4 9A E6 9D A1 E6 B6 88 E6 81 AF 5D EF BC 8C E8 AF B7 E6 9C 9F E5 BE 85 E5 90 8E E7 BB AD E7 89 88 E6 9C AC E3 80 82 12 55 AA 02 52 30 01 3A 40 4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 92 3F 28 E9 9B 97 86 04 40 00 +// 尝试解 Uni +// head +Packet Debug 20:02:51 : head=00 00 00 18 4D 65 73 73 61 67 65 53 76 63 2E 50 62 53 65 6E 64 4D 73 67 00 00 00 08 8A 51 B1 25 00 00 00 04 +Packet 20:02:51 : commandName=MessageSvc.PbSendMsg +MessageSvc.PbSendMsg + unknown4Bytes=8A 51 B1 25 + extraData= +Packet Debug 20:02:51 : Real body=0A 08 12 06 08 F6 DD 96 FC 03 12 07 08 01 10 00 18 F2 46 1A D5 04 0A D2 04 12 A9 03 62 A6 03 0A A1 03 01 78 9C 7D 91 4B 4F DB 40 10 C7 BF CA 6A 2F 3E 81 1F 21 0D 91 6C 23 F1 48 15 CA A3 28 04 89 5C AA C5 1E 9B 15 6B 3B F5 AE 97 38 27 E8 05 04 5C B9 21 71 A1 95 B8 D0 1E 2A 24 54 F5 CB 20 25 F0 31 18 1B AA DE 90 46 AB 9D 9D D9 F9 CF 6F C6 5D 18 25 82 68 C8 25 CF 52 CF B0 67 2D 83 40 1A 64 21 4F 63 CF E8 6F 77 66 E6 0D 22 15 4B 43 26 B2 14 3C A3 04 69 90 05 DF 4D 64 4C 24 E4 9A 07 D0 5D F6 68 A3 49 89 82 64 28 98 AA 7D 9B 12 16 A8 AA 26 D5 1C 0E D7 0B A1 F8 BA 8C 29 D9 CB 39 44 18 77 1A 68 8E 8D 16 49 A9 A5 0E AB 83 E9 10 4D 6B 4A 92 2F 39 48 1E 7A 74 73 BB BD B1 16 7C B4 07 65 B9 37 E2 6D 4B 1F 9A 81 B5 3B B6 16 57 F9 4A F0 69 D5 71 3E 2C 2D F6 0F 76 3A 5D 25 C7 73 5B 03 D9 1F 8D 8B AF 83 9E 39 6A 44 9F 75 6F 4D 55 A5 22 2E 60 83 25 80 B2 CD F9 A6 DD 68 B5 5A 4E DB B6 28 91 59 91 07 80 7D 75 51 09 FD 22 17 1E A5 24 12 2C 46 24 44 08 71 32 3D 1E A7 75 34 79 83 E8 D4 61 9B FA 2E 47 64 22 58 99 15 EA F5 41 71 25 C0 7F 1F EE F1 E8 87 6B BE 26 BA FB 39 D9 E7 61 08 28 10 31 21 01 5B 52 A5 80 5A CE F4 5D 59 24 09 CB 4B FF E9 DB C3 E4 E4 CF F4 FA FB D3 D5 F9 E4 EE 62 7A F9 7B 7A 7F 3A 3D FE E9 9A FF 32 5C B3 EA 05 7F D4 44 24 AD 69 9F 8F CF 26 37 B7 CF 77 BF 26 7F 2F 29 E1 41 B5 8D FF 7B C1 DB 70 58 8D 78 C6 AE C5 4C 5C A9 FF 02 0F 69 BA A1 10 23 12 4D 0A 4B 0A 49 E4 BD A0 E7 9A 84 51 51 E6 9A 82 E4 B8 8D E6 94 AF E6 8C 81 E6 9F A5 E7 9C 8B 5B E8 BD AC E5 8F 91 E5 A4 9A E6 9D A1 E6 B6 88 E6 81 AF 5D EF BC 8C E8 AF B7 E6 9C 9F E5 BE 85 E5 90 8E E7 BB AD E7 89 88 E6 9C AC E3 80 82 12 55 AA 02 52 30 01 3A 40 4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 92 3F 28 E9 9B 97 86 04 40 00 +Packet 20:02:51 : ByteReadPacket uni packet= +Packet 20:02:51 : =======================共有 1 个包======================= + +=======================处理服务器到客户端客户端======================= + +=======================处理服务器到客户端客户端======================= +Packet 20:02:51 : ByteReadPacket 正在处理=00 00 00 0B 01 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 0B 1C 26 01 C3 F8 46 01 ED 8D 1E C8 86 C1 62 89 9C F4 16 57 67 99 4F 39 E7 69 4F 74 33 6E 92 89 74 49 09 84 19 10 6F 3C 81 DA 0C 92 DD 04 B7 60 C9 DB C8 4F F8 60 57 A2 3F CF 95 5F 01 F8 0A 79 E7 28 B9 6A F6 AD 0A 71 BA 54 F8 8C DF AF CF D3 +Packet 20:02:51 : flag1(0A/0B) = 0B +Packet 20:02:51 : 包类型(flag2) = 1. (可能是 uni) +Packet 20:02:51 : 成功使用 d2Key 解密 +Packet 20:02:51 : ByteReadPacket sso/uni body==00 00 00 34 00 00 E0 90 00 00 00 00 00 00 00 04 00 00 00 18 4D 65 73 73 61 67 65 53 76 63 2E 50 62 53 65 6E 64 4D 73 67 00 00 00 08 8A 51 B1 25 00 00 00 00 00 00 00 0C 08 00 18 EA 90 ED F3 05 +Packet 20:02:51 : sequenceId = 57488 +Packet 20:02:51 : sso(inner)extraData = +Packet Debug 20:02:51 : commandName=MessageSvc.PbSendMsg +Packet 20:02:51 : 不是oicq response(可能是 UNI/PB)= 00 00 00 0C 08 00 18 EA 90 ED F3 05 +Packet 20:02:51 : =======================共有 0 个包======================= + + */ + +/* + +=======================处理客户端到服务器======================= +flag1=0x0000000B(11), flag2=1, sequenceId = 00 00 E0 8D, flag3=00, // 解密 bodyouter by D2 key +Packet 20:02:50 : ByteReadPacket outer body decrypted=00 00 00 24 00 00 00 14 4D 75 6C 74 69 4D 73 67 2E 41 70 70 6C 79 55 70 00 00 00 08 8A 51 B1 25 00 00 00 04 00 00 00 3B 08 01 10 05 18 09 20 03 2A 0A 38 2E 32 2E 30 2E 31 32 39 36 32 1F 08 F6 DD 96 FC 03 10 CF 05 1A 10 BB 45 B9 71 2C F4 D3 06 5D A7 A7 A2 FF D4 62 D2 20 03 28 00 40 01 +// 尝试解 Uni +// head +Packet Debug 20:02:50 : head=00 00 00 14 4D 75 6C 74 69 4D 73 67 2E 41 70 70 6C 79 55 70 00 00 00 08 8A 51 B1 25 00 00 00 04 +Packet 20:02:50 : commandName=MultiMsg.ApplyUp +MultiMsg.ApplyUp + unknown4Bytes=8A 51 B1 25 + extraData= +Packet Debug 20:02:50 : Real body=08 01 10 05 18 09 20 03 2A 0A 38 2E 32 2E 30 2E 31 32 39 36 32 1F 08 F6 DD 96 FC 03 10 CF 05 1A 10 BB 45 B9 71 2C F4 D3 06 5D A7 A7 A2 FF D4 62 D2 20 03 28 00 40 01 +Packet 20:02:50 : ByteReadPacket uni packet= +Packet 20:02:50 : =======================共有 1 个包======================= + +=======================处理服务器到客户端客户端======================= +Packet 20:02:50 : ByteReadPacket 正在处理=00 00 00 0B 01 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 8A B2 8A B1 DA C9 60 28 D8 55 AB 39 B9 07 A6 D8 BA F2 55 87 C2 C9 29 08 53 CC AF 99 3F 22 26 1F 66 01 09 60 F2 2A 3C F1 A4 DC 74 5A 27 1C 47 E2 F0 7E 57 0C 9B 50 7D 0D 52 A3 17 BB B7 8D 9B 62 3A B3 E2 65 6D 7C 74 24 79 11 A5 23 78 83 63 35 8C C9 34 4A D9 CD 61 4D 0D 73 74 DF 49 F3 AD 65 2D 1A 87 14 2F 03 5F 0B 16 1F 87 CE 2A 53 3E 9F 8F CF 0F B8 C3 6B E1 6C 42 46 0D 59 F2 89 7E 8A 47 A8 CC 52 C0 E7 5C E4 CD 00 A0 00 61 FA AF 95 C1 C4 1B 8C C3 24 48 A5 4D 4F D7 59 38 F1 AE 4A 3B 18 7E 52 96 D5 2D 5D 67 D0 B8 0C BC F0 FD 3E 45 2C 7F 2E 1B AC FF F1 86 04 9B 8E 16 DF 7F C0 1C 25 13 36 21 D8 87 B1 FA BA 6E D2 DA E3 02 D2 31 45 9D 61 D4 43 07 F6 B5 D3 B0 6D 72 8B 83 FA B5 90 A7 BA 7A 32 2C 28 96 67 AC AB 42 37 EF 51 5B A1 A8 2D 17 93 F9 2C 22 51 6C 49 0A ED 38 AF 88 A1 E4 C7 09 BC DA 11 3F 46 DF D3 60 51 0E 92 89 56 D6 0D B4 66 DC 74 77 64 42 95 56 BE 89 61 75 CB F7 8C 33 D4 6B 40 4F 07 43 5B D9 A4 38 E1 DC 2A 0D 4D D6 8D 2B F5 E4 A2 45 3D EF 77 E5 24 F5 09 5E 1C 9C 14 CA 33 4D 3D 63 83 2E 38 94 13 1D 7A 0D 62 DB 89 0D 27 8D E2 58 5D 24 25 BC 9F D3 E3 3A 55 F2 FB 93 69 61 F0 25 E6 7F 7F B6 25 87 33 5B 5F 35 C1 E0 C4 6E 25 41 A0 12 B5 E6 DA 1A C9 F4 20 31 86 D3 B2 C9 D3 2D 96 40 92 BC BD 38 AD D6 94 E9 25 14 12 2D B6 32 6E D5 37 7D C6 E3 A8 E5 1E AD 97 52 FA DD CC 7E 96 5A E0 CB AF 79 4B CB BC E3 9F 57 4C 94 C7 9D 58 83 D0 11 41 BD E6 9C E1 98 7B BB 5B +Packet 20:02:50 : flag1(0A/0B) = 0B +Packet 20:02:50 : 包类型(flag2) = 1. (可能是 uni) +Packet 20:02:50 : 成功使用 d2Key 解密 +Packet 20:02:50 : ByteReadPacket sso/uni body==00 00 00 30 00 00 E0 8D 00 00 00 00 00 00 00 04 00 00 00 14 4D 75 6C 74 69 4D 73 67 2E 41 70 70 6C 79 55 70 00 00 00 08 8A 51 B1 25 00 00 00 00 00 00 01 88 08 01 12 FF 02 08 00 12 40 4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74 1A 98 01 1B 76 62 FB B2 C6 24 C3 1F 39 47 0D 45 5C 77 BD 0C 8F 69 FB C8 4F D8 76 83 26 60 EA A3 24 BC FD F6 C8 B4 64 DA 47 9D 6C 1A FA F4 EF 02 FC A4 76 1F 87 EB FF 51 62 20 E9 1F 74 6B 2F 7B 7C 53 EC 6D A2 53 AC 2B 93 B4 79 83 6D E6 D8 86 E1 D5 E2 4D EE 75 03 A3 3B 72 EB 0A 3E 13 3A 80 70 EF CC B4 0D F9 42 E3 DF 5F 7A 4C 36 BC 3B 9C 31 5A B1 40 B4 5B 49 26 CE 65 BD 2F 86 8D 9D 0C 34 1B 5E 32 6E EF 60 4B E1 60 7F 1A 98 CF 14 42 85 A6 F8 BE A5 EE A7 A6 C7 9E 11 20 FB AE FA 95 0A 20 B7 87 A4 8F 0E 20 FB AE FA 9D 0A 20 E5 B6 95 B0 0A 28 50 28 90 3F 28 BB 03 28 50 40 00 4A 10 4E 64 43 67 6D 61 71 35 6D 52 73 43 53 38 41 58 52 68 AF 63 72 0B 4D 5B 17 6E D8 35 C1 D3 3F C8 D7 FC F0 A8 0A 67 4D B5 A6 B3 B7 E2 E1 9F 96 68 D3 BC AD 4A 6A 20 72 E8 D2 44 C3 8B 93 60 F3 3C 4B 46 83 E4 75 A2 3C 72 A4 F7 31 D9 88 89 23 34 9A AF EF FC 17 29 5D 6C D0 2B F1 63 D5 9F E2 B9 B5 49 D2 62 E3 D0 F9 19 C5 0D 20 AF 78 D5 34 7E BB B7 E2 8E 5C 69 F4 38 38 E7 +Packet 20:02:50 : sequenceId = 57485 +Packet 20:02:50 : sso(inner)extraData = +Packet Debug 20:02:50 : commandName=MultiMsg.ApplyUp +找不到包 PacketFactory +Packet 20:02:50 : 传递给 PacketFactory 的数据 = 00 00 01 88 08 01 12 FF 02 08 00 12 40 4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74 1A 98 01 1B 76 62 FB B2 C6 24 C3 1F 39 47 0D 45 5C 77 BD 0C 8F 69 FB C8 4F D8 76 83 26 60 EA A3 24 BC FD F6 C8 B4 64 DA 47 9D 6C 1A FA F4 EF 02 FC A4 76 1F 87 EB FF 51 62 20 E9 1F 74 6B 2F 7B 7C 53 EC 6D A2 53 AC 2B 93 B4 79 83 6D E6 D8 86 E1 D5 E2 4D EE 75 03 A3 3B 72 EB 0A 3E 13 3A 80 70 EF CC B4 0D F9 42 E3 DF 5F 7A 4C 36 BC 3B 9C 31 5A B1 40 B4 5B 49 26 CE 65 BD 2F 86 8D 9D 0C 34 1B 5E 32 6E EF 60 4B E1 60 7F 1A 98 CF 14 42 85 A6 F8 BE A5 EE A7 A6 C7 9E 11 20 FB AE FA 95 0A 20 B7 87 A4 8F 0E 20 FB AE FA 9D 0A 20 E5 B6 95 B0 0A 28 50 28 90 3F 28 BB 03 28 50 40 00 4A 10 4E 64 43 67 6D 61 71 35 6D 52 73 43 53 38 41 58 52 68 AF 63 72 0B 4D 5B 17 6E D8 35 C1 D3 3F C8 D7 FC F0 A8 0A 67 4D B5 A6 B3 B7 E2 E1 9F 96 68 D3 BC AD 4A 6A 20 72 E8 D2 44 C3 8B 93 60 F3 3C 4B 46 83 E4 75 A2 3C 72 A4 F7 31 D9 88 89 23 34 9A AF EF FC 17 29 5D 6C D0 2B F1 63 D5 9F E2 B9 B5 49 D2 62 E3 D0 F9 19 C5 0D 20 AF 78 D5 34 7E BB B7 E2 8E 5C 69 F4 38 38 E7 +Packet 20:02:50 : 不是oicq response(可能是 UNI/PB)= +Packet 20:02:50 : =======================共有 0 个包======================= + */ + +internal class MultiMsg { + + object ApplyUp : OutgoingPacketFactory("MultiMsg.ApplyUp") { + class Response( + val proto: MultiMsg.MultiMsgApplyUpRsp + ) : Packet + + fun createForLongMessage( + client: QQAndroidClient, + message: MessageChain, + dstUin: Long, // group uin + ): OutgoingPacket = createForLongMessage(client, message.calculateValidationData(client.bot), dstUin) + + // captured from group + private fun createForLongMessage( + client: QQAndroidClient, + messageData: MessageValidationData, + dstUin: Long // group uin + ): OutgoingPacket = buildOutgoingUniPacket(client) { + writeProtoBuf( + MultiMsg.ReqBody.serializer(), + MultiMsg.ReqBody( + subcmd = 1, + termType = 5, + platformType = 9, + netType = 3, // wifi=3, wap=5 + buildVer = client.buildVer, + buType = 1, + multimsgApplyupReq = listOf( + MultiMsg.MultiMsgApplyUpReq( + applyId = 0, + dstUin = dstUin, + msgMd5 = messageData.md5, + msgSize = messageData.data.size.toLong(), + msgType = 3 // TODO 3 for group? + ) + ) + ) + ) + } + + /* + RspBody#195600860 { + multimsgApplyupRsp=[MultiMsgApplyUpRsp#314337396 { + applyId=0x00000000(0) + blockSize=0x0000000000000000(0) + msgKey=4E 64 43 67 6D 61 71 35 6D 52 73 43 53 38 41 58 + msgResid=4F 54 39 4E 4C 63 47 31 5A 79 79 62 78 69 39 30 76 77 2F 63 30 59 7A 30 42 4A 69 45 63 4B 4A 32 32 36 43 42 55 6B 56 46 49 74 73 7A 34 51 5A 73 55 78 7A 75 71 5A 53 2F 78 33 66 50 76 53 4C 74 + msgSig=AF 63 72 0B 4D 5B 17 6E D8 35 C1 D3 3F C8 D7 FC F0 A8 0A 67 4D B5 A6 B3 B7 E2 E1 9F 96 68 D3 BC AD 4A 6A 20 72 E8 D2 44 C3 8B 93 60 F3 3C 4B 46 83 E4 75 A2 3C 72 A4 F7 31 D9 88 89 23 34 9A AF EF FC 17 29 5D 6C D0 2B F1 63 D5 9F E2 B9 B5 49 D2 62 E3 D0 F9 19 C5 0D 20 AF 78 D5 34 7E BB B7 E2 8E 5C 69 F4 38 38 E7 + msgUkey=1B 76 62 FB B2 C6 24 C3 1F 39 47 0D 45 5C 77 BD 0C 8F 69 FB C8 4F D8 76 83 26 60 EA A3 24 BC FD F6 C8 B4 64 DA 47 9D 6C 1A FA F4 EF 02 FC A4 76 1F 87 EB FF 51 62 20 E9 1F 74 6B 2F 7B 7C 53 EC 6D A2 53 AC 2B 93 B4 79 83 6D E6 D8 86 E1 D5 E2 4D EE 75 03 A3 3B 72 EB 0A 3E 13 3A 80 70 EF CC B4 0D F9 42 E3 DF 5F 7A 4C 36 BC 3B 9C 31 5A B1 40 B4 5B 49 26 CE 65 BD 2F 86 8D 9D 0C 34 1B 5E 32 6E EF 60 4B E1 60 7F 1A 98 CF 14 42 85 A6 F8 BE A5 EE A7 A6 C7 9E 11 + result=0x00000000(0) + uint32UpIp=[0xA2BE977B(-1564567685), 0xE1E903B7(-504822857), 0xA3BE977B(-1547790469), 0xA6055B65(-1509598363)] + uint32UpPort=[0x00000050(80), 0x00001F90(8080), 0x000001BB(443), 0x00000050(80)] + upOffset=0x0000000000000000(0) + }] + subcmd=0x00000001(1) + } + */ + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val response = readProtoBuf(MultiMsg.MultiMsgApplyUpRsp.serializer()) + check(response.result == 0) { + kotlin.run { + println(response._miraiContentToString()) + }.let { "Protocol error: MultiMsg.ApplyUp failed with result ${response.result}" } + } + return Response(response) + } + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/MultiMsgCryptor.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/MultiMsgCryptor.kt new file mode 100644 index 000000000..f9a1e6914 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/MultiMsgCryptor.kt @@ -0,0 +1,32 @@ +/* + * 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.qqandroid.utils.cryptor + +import net.mamoe.mirai.utils.io.toUHexString + +internal object MultiMsgCryptor { + private val impl = class_1457() + + fun decrypt(data: ByteArray, offset: Int, length: Int, key: ByteArray): ByteArray { + return this.impl.method_67425(data, offset, length, key) ?: error("MultiMsgCryptor decypt failed: key=${key.toUHexString()}, data=${data.drop(offset).take(length).toByteArray().toUHexString()}") + } + + fun decrypt(data: ByteArray, key: ByteArray): ByteArray { + return this.impl.method_67426(data, key) ?: error("MultiMsgCryptor decrypt failed: key=${key.toUHexString()}, data=${data.toUHexString()}") + } + + fun enableResultRandom(enabled: Boolean) { + this.impl.method_67424(enabled) + } + + fun encrypt(data: ByteArray, key: ByteArray): ByteArray { + return this.impl.method_67427(data, key) + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/class_1457.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/class_1457.kt new file mode 100644 index 000000000..01e384f2d --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/class_1457.kt @@ -0,0 +1,382 @@ +@file:Suppress("NAME_SHADOWING") + +package net.mamoe.mirai.qqandroid.utils.cryptor + +import kotlinx.io.core.buildPacket +import kotlinx.io.core.readBytes +import net.mamoe.mirai.utils.io.toByteArray +import kotlin.experimental.and +import kotlin.experimental.xor +import kotlin.random.Random + +// $FF: renamed from: com.tencent.qphone.base.util.b +internal class class_1457 { + // $FF: renamed from: a byte[] + private lateinit var field_71278: ByteArray + + // $FF: renamed from: b byte[] + private var field_71279: ByteArray? = null + + // $FF: renamed from: c byte[] + private lateinit var field_71280: ByteArray + + // $FF: renamed from: d int + private var field_71281 = 0 + + // $FF: renamed from: e int + private var field_71282 = 0 + + // $FF: renamed from: f int + private var field_71283 = 0 + + // $FF: renamed from: g int + private var field_71284 = 0 + + // $FF: renamed from: h byte[] + private var field_71285: ByteArray? = null + + // $FF: renamed from: i boolean + private var field_71286 = true + + // $FF: renamed from: j int + private var field_71287 = 0 + + // $FF: renamed from: k java.util.Random + private val field_71288: Random = Random + + // $FF: renamed from: l boolean + private var field_71289 = true + + // $FF: renamed from: a () void + private fun method_67415() { + var var1: Int + var var2: ByteArray + field_71283 = 0 + while (field_71283 < 8) { + if (field_71286) { + var2 = field_71278 + var1 = field_71283 + var2[var1] = var2[var1] xor field_71279!![field_71283] + } else { + var2 = field_71278 + var1 = field_71283 + var2[var1] = var2[var1] xor field_71280[field_71282 + field_71283] + } + ++field_71283 + } + arraycopy(method_67417(field_71278), 0, field_71280, field_71281, 8) + field_71283 = 0 + while (field_71283 < 8) { + var2 = field_71280 + var1 = field_71281 + field_71283 + var2[var1] = var2[var1] xor field_71279!![field_71283] + ++field_71283 + } + arraycopy(field_71278, 0, field_71279!!, 0, 8) + field_71282 = field_71281 + field_71281 += 8 + field_71283 = 0 + field_71286 = false + } + + // $FF: renamed from: a (int) byte[] + private fun method_67416(var1: Int): ByteArray { + val var2 = ByteArray(var1) + field_71288.nextBytes(var2) + return var2 + } + + // $FF: renamed from: a (byte[]) byte[] + private fun method_67417(var1: ByteArray): ByteArray { + var var1: ByteArray? = var1 + var var2 = 16 + var var5: Long + var var7: Long + val var9: Long + val var11: Long + val var13: Long + val var15: Long + var7 = method_67414(var1, 0, 4) + var5 = method_67414(var1, 4, 4) + var9 = method_67414(field_71285, 0, 4) + var11 = method_67414(field_71285, 4, 4) + var13 = method_67414(field_71285, 8, 4) + var15 = method_67414(field_71285, 12, 4) + var var3 = 0L + while (var2 > 0) { + var3 = var3 + (-1640531527L and 4294967295L) and 4294967295L + var7 = var7 + ((var5 shl 4) + var9 xor var5 + var3 xor (var5 ushr 5) + var11) and 4294967295L + var5 = var5 + ((var7 shl 4) + var13 xor var7 + var3 xor (var7 ushr 5) + var15) and 4294967295L + --var2 + } + return buildPacket { + writeInt(var7.toInt()) + writeInt(var5.toInt()) + }.readBytes() + } + + // $FF: renamed from: a (byte[], int) byte[] + private fun method_67418(var1: ByteArray?, var2: Int): ByteArray? { + var var1 = var1 + var var2 = var2 + val var3: Byte = 16 + var var6: Long + var var8: Long + val var10: Long + val var12: Long + val var14: Long + val var16: Long + var8 = method_67414(var1, var2, 4) + var6 = method_67414(var1, var2 + 4, 4) + var10 = method_67414(field_71285, 0, 4) + var12 = method_67414(field_71285, 4, 4) + var14 = method_67414(field_71285, 8, 4) + var16 = method_67414(field_71285, 12, 4) + var var4 = -478700656L and 4294967295L + var2 = var3.toInt() + while (var2 > 0) { + var6 = var6 - ((var8 shl 4) + var14 xor var8 + var4 xor (var8 ushr 5) + var16) and 4294967295L + var8 = var8 - ((var6 shl 4) + var10 xor var6 + var4 xor (var6 ushr 5) + var12) and 4294967295L + var4 = var4 - (-1640531527L and 4294967295L) and 4294967295L + --var2 + } + return var8.toByteArray() + var6.toByteArray() + } + + // $FF: renamed from: a (byte[], byte[], int) byte[] + private fun method_67419(var1: ByteArray, var2: ByteArray, var3: Int): ByteArray? { + var var1: ByteArray? = var1 + var var2: ByteArray? = var2 + var2 = method_67425(var1, 0, var1!!.size, var2) + var1 = var2 + if (var2 == null) { + var1 = method_67416(var3) + } + return var1 + } + + // $FF: renamed from: b () int + private fun method_67420(): Int { + return if (field_71289) field_71288.nextInt() else 16711935 + } + + // $FF: renamed from: b (byte[], int, int) boolean + private fun method_67421(var1: ByteArray?, var2: Int, var3: Int): Boolean { + field_71283 = 0 + while (field_71283 < 8) { + if (field_71287 + field_71283 >= var3) { + return true + } + val var5 = field_71279 + val var4 = field_71283 + var5!![var4] = var5[var4] xor var1!![field_71281 + var2 + field_71283] + ++field_71283 + } + field_71279 = method_67422(field_71279) + return if (field_71279 == null) { + false + } else { + field_71287 += 8 + field_71281 += 8 + field_71283 = 0 + true + } + } + + // $FF: renamed from: b (byte[]) byte[] + private fun method_67422(var1: ByteArray?): ByteArray? { + return method_67418(var1, 0) + } + + // $FF: renamed from: b (byte[], int, int, byte[]) byte[] + private fun method_67423(var1: ByteArray, var2: Int, var3: Int, var4: ByteArray): ByteArray { + var var1 = var1 + var var2 = var2 + var var3 = var3 + var var4 = var4 + field_71278 = ByteArray(8) + field_71279 = ByteArray(8) + field_71283 = 1 + field_71284 = 0 + field_71282 = 0 + field_71281 = 0 + field_71285 = var4 + field_71286 = true + field_71283 = (var3 + 10) % 8 + if (field_71283 != 0) { + field_71283 = 8 - field_71283 + } + field_71280 = ByteArray(field_71283 + var3 + 10) + field_71278[0] = (method_67420() and 248 or field_71283).toByte() + var var5: Int + var5 = 1 + while (var5 <= field_71283) { + field_71278[var5] = (method_67420() and 255).toByte() + ++var5 + } + ++field_71283 + var5 = 0 + while (var5 < 8) { + field_71279!![var5] = 0 + ++var5 + } + field_71284 = 1 + while (field_71284 <= 2) { + if (field_71283 < 8) { + var4 = field_71278 + var5 = field_71283++ + var4[var5] = (method_67420() and 255).toByte() + ++field_71284 + } + if (field_71283 == 8) { + method_67415() + } + } + while (var3 > 0) { + if (field_71283 < 8) { + var4 = field_71278 + val var6 = field_71283++ + var5 = var2 + 1 + var4[var6] = var1[var2] + --var3 + var2 = var5 + } + if (field_71283 == 8) { + method_67415() + } + } + field_71284 = 1 + while (field_71284 <= 7) { + if (field_71283 < 8) { + var1 = field_71278 + var2 = field_71283++ + var1[var2] = 0 + ++field_71284 + } + if (field_71283 == 8) { + method_67415() + } + } + return field_71280 + } + + // $FF: renamed from: a (boolean) void + fun method_67424(var1: Boolean) { + field_71289 = var1 + } + + // $FF: renamed from: a (byte[], int, int, byte[]) byte[] + internal fun method_67425( + var1: ByteArray?, + var2: Int, + var3: Int, + var4: ByteArray? + ): ByteArray? { + var var4 = var4 + field_71282 = 0 + field_71281 = 0 + field_71285 = var4 + var4 = ByteArray(var2 + 8) + return if (var3 % 8 == 0 && var3 >= 16) { + field_71279 = method_67418(var1, var2) + field_71283 = (field_71279!![0] and 7).toInt() + var var6 = var3 - field_71283 - 10 + if (var6 < 0) { + null + } else { + var var5: Int + var5 = var2 + while (var5 < var4.size) { + var4[var5] = 0 + ++var5 + } + field_71280 = ByteArray(var6) + field_71282 = 0 + field_71281 = 8 + field_71287 = 8 + ++field_71283 + field_71284 = 1 + while (field_71284 <= 2) { + if (field_71283 < 8) { + ++field_71283 + ++field_71284 + } + if (field_71283 == 8) { + if (!method_67421(var1, var2, var3)) { + return null + } + var4 = var1 + } + } + var5 = 0 + while (var6 != 0) { + if (field_71283 < 8) { + field_71280[var5] = + (var4!![field_71282 + var2 + field_71283] xor field_71279!![field_71283]) + ++var5 + ++field_71283 + --var6 + } + if (field_71283 == 8) { + field_71282 = field_71281 - 8 + if (!method_67421(var1, var2, var3)) { + return null + } + var4 = var1 + } + } + field_71284 = 1 + while (field_71284 < 8) { + if (field_71283 < 8) { + if (var4!![field_71282 + var2 + field_71283] xor field_71279!![field_71283] != 0.toByte()) { + return null + } + ++field_71283 + } + if (field_71283 == 8) { + field_71282 = field_71281 + if (!method_67421(var1, var2, var3)) { + return null + } + var4 = var1 + } + ++field_71284 + } + field_71280 + } + } else { + null + } + } + + // $FF: renamed from: a (byte[], byte[]) byte[] + internal fun method_67426(var1: ByteArray, var2: ByteArray?): ByteArray? { + return method_67425(var1, 0, var1.size, var2) + } + + // $FF: renamed from: b (byte[], byte[]) byte[] + internal fun method_67427(var1: ByteArray, var2: ByteArray): ByteArray { + return method_67423(var1, 0, var1.size, var2) + } + + companion object { + // $FF: renamed from: a (byte[], int, int) long + private fun method_67414(var0: ByteArray?, var1: Int, var2: Int): Long { + var var1 = var1 + var var2 = var2 + var var3 = 0L + if (var2 > 8) { + var2 = var1 + 8 + } else { + var2 += var1 + } + while (var1 < var2) { + var3 = var3 shl 8 or (var0!![var1] and 255.toByte()).toLong() + ++var1 + } + return 4294967295L and var3 or var3 ushr 32 + } + } +} + +internal expect fun arraycopy(src: ByteArray, srcPos: Int, dest: ByteArray, destPos: Int, length: Int) \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt new file mode 100644 index 000000000..0a0470e79 --- /dev/null +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt @@ -0,0 +1,9 @@ +package net.mamoe.mirai.qqandroid.utils.cryptor + +internal actual fun arraycopy( + src: ByteArray, + srcPos: Int, + dest: ByteArray, + destPos: Int, + length: Int +) = System.arraycopy(src, srcPos, dest, destPos, length) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt index b509acdf6..bc99d768f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.Job import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.data.* +import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI @@ -139,6 +140,14 @@ interface LowLevelBotAPIAccessor { @LowLevelAPI @MiraiExperimentalAPI suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData + + /** + * 发送长消息 + */ + @SinceMirai("0.31.0") + @LowLevelAPI + @MiraiExperimentalAPI + suspend fun _lowLevelSendLongMessage(groupCode: Long, message: Message) } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt index 950d05920..ec890d3b9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt @@ -15,7 +15,6 @@ package net.mamoe.mirai.message.data import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName -import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic /** @@ -28,26 +27,13 @@ class PlainText(val stringValue: String) : Comparable by stringValue, CharSequence by stringValue { + @Suppress("unused") constructor(charSequence: CharSequence) : this(charSequence.toString()) override operator fun contains(sub: String): Boolean = sub in stringValue override fun toString(): String = stringValue - companion object Key : Message.Key { - @JvmStatic - val Empty = PlainText("") - - @JvmStatic - val Null = PlainText("null") - - inline fun of(value: String): PlainText { - return PlainText(value) - } - - inline fun of(value: CharSequence): PlainText { - return PlainText(value) - } - } + companion object Key : Message.Key<PlainText> } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt index 26e49d506..2c6272f25 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt @@ -33,6 +33,40 @@ interface RichMessage : MessageContent { @SinceMirai("0.30.0") companion object Templates : Message.Key<RichMessage> { + /** + * 合并转发. + */ + @MiraiExperimentalAPI + fun mergedForward(): Nothing { + TODO() + } + + /** + * 长消息. + * + * @param brief 消息内容纯文本, 显示在图片的前面 + */ + @MiraiExperimentalAPI + fun longMessage(brief: String, resId: String, time: Long): XmlMessage { + val template = """ +<?xml version='1.0' encoding='UTF-8' standalone='yes' ?> +<msg serviceID="35" templateID="1" action="viewMultiMsg" + brief="$brief" + m_resid="$resId" + m_fileName="$time" sourceMsgId="0" url="" + flag="3" adverSign="0" multiMsgFlag="1"> + <item layout="1"> + <title>$brief…</title> + <hr hidden="false" style="0"/> + <summary>点击查看完整消息</summary> + </item> + <source name="聊天记录" icon="" action="" appid="-1"/> +</msg> + """ + + return XmlMessage(template) + } + @MiraiExperimentalAPI @SinceMirai("0.30.0") fun share(url: String, title: String? = null, content: String? = null, coverUrl: String? = null): XmlMessage = @@ -107,6 +141,19 @@ class XmlMessage constructor(override val content: String) : RichMessage { override fun toString(): String = content } +/** + * 合并转发消息 + */ +@SinceMirai("0.31.0") +@MiraiExperimentalAPI +class MergedForwardedMessage(override val content: String) : RichMessage { + companion object Key : Message.Key<XmlMessage> + + // serviceId = 35 + override fun toString(): String = content +} + + /** * 构造一条 XML 消息 */