From 3f523e6f7777d651c3788b552dc9cb18d284d3b3 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 27 Mar 2020 13:27:34 +0800 Subject: [PATCH 01/11] 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 消息 */ From 8d7e984b1c4c66960e4bee22d94b0c40c9cea743 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Fri, 27 Mar 2020 22:19:14 +0800 Subject: [PATCH 02/11] Remove useless files --- .../qqandroid/utils/cryptor/arraycopy.kt | 9 - .../utils/cryptor/MultiMsgCryptor.kt | 32 -- .../qqandroid/utils/cryptor/class_1457.kt | 382 ------------------ .../qqandroid/utils/cryptor/arraycopy.kt | 9 - 4 files changed, 432 deletions(-) delete mode 100644 mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt delete mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/MultiMsgCryptor.kt delete mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/class_1457.kt delete 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 deleted file mode 100644 index 0a0470e79..000000000 --- a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt +++ /dev/null @@ -1,9 +0,0 @@ -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/utils/cryptor/MultiMsgCryptor.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/MultiMsgCryptor.kt deleted file mode 100644 index f9a1e6914..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/MultiMsgCryptor.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 deleted file mode 100644 index 01e384f2d..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/class_1457.kt +++ /dev/null @@ -1,382 +0,0 @@ -@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 deleted file mode 100644 index 0a0470e79..000000000 --- a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/arraycopy.kt +++ /dev/null @@ -1,9 +0,0 @@ -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 From 8f2843cbfab958bb06f96a801325b36ccc8e61ad Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 28 Mar 2020 14:16:39 +0800 Subject: [PATCH 03/11] Handle mute result as an IllegalStateException --- .../net/mamoe/mirai/qqandroid/contact/GroupImpl.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt index 00e838145..730919295 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt @@ -288,9 +288,12 @@ internal class GroupImpl( source = it source.startWaitingSequenceId(this) }.sendAndExpect() - check( - response is MessageSvc.PbSendMsg.Response.SUCCESS - ) { "send message failed: $response" } + if (response is MessageSvc.PbSendMsg.Response.Failed) { + when (response.errorCode) { + 120 -> error("bot is being muted.") + else -> error("send message failed: $response") + } + } } return MessageReceipt(source, this, botAsMember) From 56728378782e786a82a2defbcae8b92cd49ad734 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 28 Mar 2020 21:53:33 +0800 Subject: [PATCH 04/11] Add `gzip` and `ungzip` --- .../net/mamoe/mirai/utils/platformAndroid.kt | 17 +++++++++++++++++ .../kotlin/net.mamoe.mirai/utils/platform.kt | 4 ++++ .../net/mamoe/mirai/utils/PlatformUtilsTest.kt | 7 ++++++- .../net/mamoe/mirai/utils/PlatformUtilsJvm.kt | 16 ++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt index 4e4de1eff..ea2c41cd1 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt @@ -21,6 +21,8 @@ import java.io.InputStream import java.net.Inet4Address import java.security.MessageDigest import java.util.zip.Deflater +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream import java.util.zip.Inflater @@ -63,6 +65,7 @@ actual object MiraiPlatformUtils { } } + actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray { data.checkOffsetAndLength(offset, length) return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest() @@ -99,4 +102,18 @@ actual object MiraiPlatformUtils { block(read) } } + + actual fun gzip(data: ByteArray, offset: Int, length: Int): ByteArray { + ByteArrayOutputStream().use { buf -> + GZIPOutputStream(buf).use { gzip -> + data.inputStream(offset, length).use { t -> t.copyTo(gzip) } + } + buf.flush() + return buf.toByteArray() + } + } + + actual fun ungzip(data: ByteArray, offset: Int, length: Int): ByteArray { + return GZIPInputStream(data.inputStream(offset, length)).use { it.readBytes() } + } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt index fd5f8a083..f40840a37 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt @@ -30,6 +30,10 @@ expect object MiraiPlatformUtils { fun zip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray + fun gzip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray + + fun ungzip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray + fun md5(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/PlatformUtilsTest.kt b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/PlatformUtilsTest.kt index 9bf4d397b..cead3f84e 100644 --- a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/PlatformUtilsTest.kt +++ b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/PlatformUtilsTest.kt @@ -14,11 +14,16 @@ import net.mamoe.mirai.utils.io.encodeToString import kotlin.test.Test import kotlin.test.assertEquals +@OptIn(MiraiInternalAPI::class) internal class PlatformUtilsTest { - @OptIn(MiraiInternalAPI::class) @Test fun testZip() { assertEquals("test", MiraiPlatformUtils.unzip(MiraiPlatformUtils.zip("test".toByteArray())).encodeToString()) } + + @Test + fun testGZip() { + assertEquals("test", MiraiPlatformUtils.ungzip(MiraiPlatformUtils.gzip("test".toByteArray())).encodeToString()) + } } \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt index e142b46d1..d5af19847 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt @@ -22,6 +22,8 @@ import java.io.OutputStream import java.net.Inet4Address import java.security.MessageDigest import java.util.zip.Deflater +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream import java.util.zip.Inflater /** @@ -64,6 +66,20 @@ actual object MiraiPlatformUtils { } } + actual fun gzip(data: ByteArray, offset: Int, length: Int): ByteArray { + ByteArrayOutputStream().use { buf -> + GZIPOutputStream(buf).use { gzip -> + data.inputStream(offset, length).use { t -> t.copyTo(gzip) } + } + buf.flush() + return buf.toByteArray() + } + } + + actual fun ungzip(data: ByteArray, offset: Int, length: Int): ByteArray { + return GZIPInputStream(data.inputStream(offset, length)).use { it.readBytes() } + } + actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray { data.checkOffsetAndLength(offset, length) return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest() From c854be95a72c4b743b179bcdcaa06aa3a4c16409 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 28 Mar 2020 23:55:05 +0800 Subject: [PATCH 05/11] Adjust visibility --- .../kotlin/net.mamoe.mirai/utils/numbers.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt index 079f857b1..3c40e4a36 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt @@ -18,8 +18,7 @@ import kotlin.jvm.JvmName /** * 要求 [this] 最小为 [min]. */ -@Suppress("NOTHING_TO_INLINE") -inline fun Int.coerceAtLeastOrFail(min: Int): Int { +internal fun Int.coerceAtLeastOrFail(min: Int): Int { require(this >= min) return this } @@ -27,8 +26,7 @@ inline fun Int.coerceAtLeastOrFail(min: Int): Int { /** * 要求 [this] 最小为 [min]. */ -@Suppress("NOTHING_TO_INLINE") -inline fun Long.coerceAtLeastOrFail(min: Long): Long { +internal fun Long.coerceAtLeastOrFail(min: Long): Long { require(this >= min) return this } @@ -36,12 +34,10 @@ inline fun Long.coerceAtLeastOrFail(min: Long): Long { /** * 要求 [this] 最大为 [max]. */ -@Suppress("NOTHING_TO_INLINE") -inline fun Int.coerceAtMostOrFail(max: Int): Int = +internal fun Int.coerceAtMostOrFail(max: Int): Int = if (this >= max) error("value is greater than its expected maximum value $max") else this -@Suppress("NOTHING_TO_INLINE") -inline fun Long.coerceAtMostOrFail(max: Long): Long = +internal fun Long.coerceAtMostOrFail(max: Long): Long = if (this >= max) error("value is greater than its expected maximum value $max") else this \ No newline at end of file From e30a1ea4b909ad7f32bc16adedaf3bf9e55cf19c Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 28 Mar 2020 23:58:09 +0800 Subject: [PATCH 06/11] Fix length limitation --- .../src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt index 803e579a6..38077fe9e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt @@ -41,7 +41,7 @@ inline fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray): Int { inline fun BytePacketBuilder.writeIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long) = {it}, builder: BytePacketBuilder.() -> Unit): Int = BytePacketBuilder().apply(builder).build().use { if (tag != null) writeUByte(tag) - val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFL) + val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFFFFFL) writeInt(length.toInt()) writePacket(it) return length.toInt() @@ -50,7 +50,7 @@ inline fun BytePacketBuilder.writeIntLVPacket(tag: UByte? = null, lengthOffset: inline fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long) = {it}, builder: BytePacketBuilder.() -> Unit): Int = BytePacketBuilder().apply(builder).build().use { if (tag != null) writeUByte(tag) - val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFL) + val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFFFFFL) writeUShort(length.toUShort()) writePacket(it) return length.toInt() From a7e9b151e44f18cade3547cc897fff4e18bb8a0f Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 29 Mar 2020 02:26:59 +0800 Subject: [PATCH 07/11] Support long messages --- .../net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 92 ++++++++++++++++--- .../mirai/qqandroid/contact/GroupImpl.kt | 7 +- .../mamoe/mirai/qqandroid/message/messages.kt | 72 +++++++++++---- .../network/QQAndroidBotNetworkHandler.kt | 81 ++++++++-------- .../qqandroid/network/QQAndroidClient.kt | 3 + .../network/highway/HighwayHelper.kt | 8 +- .../qqandroid/network/highway/highway.kt | 14 ++- .../network/protocol/data/proto/Highway.kt | 10 +- .../network/protocol/data/proto/Msg.kt | 2 +- .../network/protocol/data/proto/MultiMsg.kt | 2 +- .../network/protocol/packet/chat/MultiMsg.kt | 88 +++++++++++------- .../packet/chat/receive/MessageSvc.kt | 2 +- .../message/data/RichMessage.kt | 48 ++++++---- .../net.mamoe.mirai/utils/BotConfiguration.kt | 31 +++++++ .../net.mamoe.mirai/utils/io/conversion.kt | 14 +-- .../kotlin/net.mamoe.mirai/utils/numbers.kt | 4 + .../kotlin/net.mamoe.mirai/utils/platform.kt | 2 +- 17 files changed, 319 insertions(+), 161 deletions(-) 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 5c6e8ceea..2aff75912 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,18 +33,20 @@ 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.io.serialization.toByteArray 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.highway.HighwayHelper +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.* import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList +import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.encodeToString +import net.mamoe.mirai.utils.io.toReadPacket import kotlin.collections.asSequence import kotlin.coroutines.CoroutineContext import kotlin.math.absoluteValue @@ -367,27 +369,95 @@ internal abstract class QQAndroidBotBase constructor( @LowLevelAPI @MiraiExperimentalAPI override suspend fun _lowLevelSendLongMessage(groupCode: Long, message: Message) { + val chain = message.asMessageChain() + check(chain.toString().length <= 3000 && chain.count { it is Image } <= 10) { "message is too large" } + val group = getGroup(groupCode) + val source = MessageSourceFromSendFriend( messageRandom = Random.nextInt().absoluteValue, senderId = client.uin, toUin = Group.calculateGroupUinByGroupCode(groupCode), time = currentTimeSeconds, groupId = groupCode, - originalMessage = message.asMessageChain(), - sequenceId = 0 + originalMessage = chain, + sequenceId = client.atomicNextMessageSequenceId() // sourceMessage = message ) // TODO: 2020/3/26 util 方法来添加单例元素 - val toSend = buildMessageChain { - source.originalMessage.filter { it !is MessageSource }.forEach { - add(it) + val toSend = buildMessageChain(chain) { + source.originalMessage.forEach { + if (it !is MessageSource){ + add(it) + } } add(source) } + network.run { - val response = MultiMsg.ApplyUp.createForLongMessage(this@QQAndroidBotBase.client, toSend, groupCode) - .sendAndExpect<MultiMsg.ApplyUp.Response>() + val data = toSend.calculateValidationDataForGroup(group) + + val response = + MultiMsg.ApplyUp.createForLongMessage( + client = this@QQAndroidBotBase.client, + messageData = data, + dstUin = Group.calculateGroupUinByGroupCode(groupCode) + ).sendAndExpect<MultiMsg.ApplyUp.Response>() + + val resId: String + when (response) { + is MultiMsg.ApplyUp.Response.MessageTooLarge -> + error("message is too large") + is MultiMsg.ApplyUp.Response.OK -> { + resId = response.resId + } + is MultiMsg.ApplyUp.Response.RequireUpload -> { + resId = response.proto.msgResid + + val body = LongMsg.ReqBody( + subcmd = 1, + platformType = 9, + termType = 5, + msgUpReq = listOf( + LongMsg.MsgUpReq( + msgType = 3, // group + dstUin = Group.calculateGroupUinByGroupCode(groupCode), + msgId = 0, + msgUkey = response.proto.msgUkey, + needCache = 0, + storeType = 2, + msgContent = data.data + ) + ) + ).toByteArray(LongMsg.ReqBody.serializer()) + + HighwayHelper.uploadImage( + client, + serverIp = response.proto.uint32UpIp!!.first().toIpV4AddressString(), + serverPort = response.proto.uint32UpPort!!.first(), + ticket = response.proto.msgSig, // 104 + imageInput = body.toReadPacket(), + inputSize = body.size, + fileMd5 = MiraiPlatformUtils.md5(body), + commandId = 27 // long msg + ) + } + } + + group.sendMessage( + RichMessage.longMessage( + brief = toSend.joinToString { + when (it) { + is PlainText -> it.stringValue + is At -> it.toString() + else -> "" + } + }, + resId = resId, + timeSeconds = source.time + ) + ) + println(response._miraiContentToString()) } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt index 730919295..2f66dcc24 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt @@ -65,6 +65,8 @@ internal class GroupImpl( companion object; override val bot: QQAndroidBot by bot.unsafeWeakRef() + + @OptIn(LowLevelAPI::class) val uin: Long = groupInfo.uin override lateinit var owner: Member @@ -289,8 +291,9 @@ internal class GroupImpl( source.startWaitingSequenceId(this) }.sendAndExpect() if (response is MessageSvc.PbSendMsg.Response.Failed) { - when (response.errorCode) { + when (response.resultType) { 120 -> error("bot is being muted.") + 34 -> error("internal error: send message failed, illegal arguments: $response") else -> error("send message failed: $response") } } @@ -352,7 +355,7 @@ internal class GroupImpl( imageInput = image.input, inputSize = image.inputSize.toInt(), fileMd5 = image.md5, - uKey = response.uKey, + ticket = response.uKey, commandId = 2 ) } ?: error("timeout uploading image: ${image.filename}") 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 0d11ee9aa..aff7d4f7d 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 @@ -234,11 +234,25 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB } } + var longTextResId: String? = null fun transformOneMessage(it: Message) { if (it is RichMessage) { val content = MiraiPlatformUtils.zip(it.content.toByteArray()) when (it) { + is LongMessage -> { + check(longTextResId == null) { "There must be no more than one LongMessage element in the message chain" } + elements.add( + ImMsgBody.Elem( + richMsg = ImMsgBody.RichMsg( + serviceId = 35, // ok + template1 = byteArrayOf(1) + content + ) + ) + ) + transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN) + longTextResId = it.resId + } is LightApp -> elements.add( ImMsgBody.Elem( lightApp = ImMsgBody.LightAppElem( @@ -246,24 +260,13 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB ) ) ) - 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 + // is MergedForwardedMessage -> 35 else -> error("unsupported RichMessage: ${it::class.simpleName}") }, template1 = byteArrayOf(1) + content @@ -296,8 +299,9 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB transformOneMessage(PlainText(" ")) } } - is QuoteReply, - is MessageSource, + is QuoteReply, // already transformed above + is MessageSource, // mirai only + is RichMessage, // already transformed above -> { } @@ -306,10 +310,24 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB } this.forEach(::transformOneMessage) - if (this.any<RichMessage>()) { - // 08 09 78 00 A0 01 81 DC 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00 - elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes()))) - } else elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()))) + when { + longTextResId != null -> { + elements.add( + ImMsgBody.Elem( + generalFlags = ImMsgBody.GeneralFlags( + longTextFlag = 1, + longTextResid = longTextResId!!, + pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes() + ), + ) + ) + } + this.any<RichMessage>() -> { + // 08 09 78 00 A0 01 81 DC 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00 + elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes()))) + } + else -> elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()))) + } return elements } @@ -402,7 +420,7 @@ private fun MessageChain.cleanupRubbishMessageElements(): MessageChain { last = element return@forEach } else { - if (last is MergedForwardedMessage && element is PlainText) { + if (last is LongMessage && element is PlainText) { if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) { last = element return@forEach @@ -416,6 +434,15 @@ private fun MessageChain.cleanupRubbishMessageElements(): MessageChain { } } +internal inline fun <reified R> Iterable<*>.firstIsInstance(): R { + this.forEach { + if (it is R) { + return it + } + } + throw NoSuchElementException("Collection contains no element matching the predicate.") +} + /* if (this.any<QuoteReply>()) { var removed = false @@ -464,7 +491,12 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilde when (it.richMsg.serviceId) { 1 -> message.add(JsonMessage(content)) 60 -> message.add(XmlMessage(content)) - 35 -> message.add(MergedForwardedMessage(content)) + 35 -> message.add( + LongMessage( + content, + this.firstIsInstance<ImMsgBody.GeneralFlags>().longTextResid + ) + ) else -> { @Suppress("DEPRECATION") MiraiLogger.debug { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index dc7ffd736..dda1901bc 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -262,32 +262,32 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler bot.groups.delegate.addLast( @Suppress("DuplicatedCode") (GroupImpl( - bot = bot, - coroutineContext = bot.coroutineContext, - id = troopNum.groupCode, - groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply { - this as GroupInfoImpl + bot = bot, + coroutineContext = bot.coroutineContext, + id = troopNum.groupCode, + groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply { + this as GroupInfoImpl - if (this.delegate.groupName == null) { - this.delegate.groupName = troopNum.groupName - } + if (this.delegate.groupName == null) { + this.delegate.groupName = troopNum.groupName + } - if (this.delegate.groupMemo == null) { - this.delegate.groupMemo = troopNum.groupMemo - } + if (this.delegate.groupMemo == null) { + this.delegate.groupMemo = troopNum.groupMemo + } - if (this.delegate.groupUin == null) { - this.delegate.groupUin = troopNum.groupUin - } + if (this.delegate.groupUin == null) { + this.delegate.groupUin = troopNum.groupUin + } - this.delegate.groupCode = troopNum.groupCode - }, - members = bot._lowLevelQueryGroupMemberList( - troopNum.groupUin, - troopNum.groupCode, - troopNum.dwGroupOwnerUin - ) - )) + this.delegate.groupCode = troopNum.groupCode + }, + members = bot._lowLevelQueryGroupMemberList( + troopNum.groupUin, + troopNum.groupCode, + troopNum.dwGroupOwnerUin + ) + )) ) }?.let { logger.error { "群${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试" } @@ -581,32 +581,25 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't send any packet" } suspend fun doSendAndReceive(handler: PacketListener, data: Any, length: Int): E { - val result = async { - withTimeoutOrNull(3000) { - withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { - PacketLogger.debug { "Channel sending: $commandName" } - when (data) { - is ByteArray -> channel.send(data, 0, length) - is ByteReadPacket -> channel.send(data) - else -> error("Internal error: unexpected data type: ${data::class.simpleName}") - } - PacketLogger.debug { "Channel send done: $commandName" } + withTimeoutOrNull(3000) { + withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { + PacketLogger.debug { "Channel sending: $commandName" } + when (data) { + is ByteArray -> channel.send(data, 0, length) + is ByteReadPacket -> channel.send(data) + else -> error("Internal error: unexpected data type: ${data::class.simpleName}") } - } ?: return@async "timeout sending packet $commandName" + PacketLogger.debug { "Channel send done: $commandName" } + } + } ?: throw TimeoutException("timeout sending packet $commandName") - logger.verbose("Send done: $commandName") - - withTimeoutOrNull(timeoutMillis) { - handler.await() - // 不要 `withTimeout`. timeout 的报错会不正常. - } ?: return@async "timeout receiving response of $commandName" - } + logger.verbose("Send done: $commandName") @Suppress("UNCHECKED_CAST") - when (val value = result.await()) { - is String -> throw TimeoutException(value) - else -> return value as E - } + return withTimeoutOrNull(timeoutMillis) { + handler.await() + // 不要 `withTimeout`. timeout 的报错会不正常. + } as E? ?: throw TimeoutException("timeout receiving response of $commandName") } if (retry == 0) { 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 6d0aeccbd..da22f797b 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 @@ -115,6 +115,9 @@ internal open class QQAndroidClient( private val highwayDataTransSequenceIdForFriend: AtomicInt = atomic(43973) internal fun nextHighwayDataTransSequenceIdForFriend(): Int = highwayDataTransSequenceIdForFriend.getAndAdd(2) + private val highwayDataTransSequenceIdForApplyUp: AtomicInt = atomic(77918) + internal fun nextHighwayDataTransSequenceIdForApplyUp(): Int = highwayDataTransSequenceIdForApplyUp.getAndAdd(2) + val appClientVersion: Int = 0 var networkType: NetworkType = NetworkType.WIFI diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt index 02989aeb0..d913d4431 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt @@ -101,7 +101,7 @@ internal object HighwayHelper { client: QQAndroidClient, serverIp: String, serverPort: Int, - uKey: ByteArray, + ticket: ByteArray, imageInput: Any, inputSize: Int, fileMd5: ByteArray, @@ -109,8 +109,8 @@ internal object HighwayHelper { ) { require(imageInput is Input || imageInput is InputStream || imageInput is ByteReadChannel) { "unsupported imageInput: ${imageInput::class.simpleName}" } require(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" } - require(uKey.size == 128) { "bad uKey. Required size=128, got ${uKey.size}" } - require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" } + // require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" } + // require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" } val socket = PlatformSocket() socket.connect(serverIp, serverPort) @@ -119,7 +119,7 @@ internal object HighwayHelper { client = client, command = "PicUp.DataUp", commandId = commandId, - uKey = uKey, + ticket = ticket, data = imageInput, dataSize = inputSize, fileMd5 = fileMd5 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt index 64c570031..9f38d8f7a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt @@ -36,7 +36,7 @@ internal fun createImageDataPacketSequence( // RequestDataTrans dataFlag: Int = 4096, commandId: Int, localId: Int = 2052, - uKey: ByteArray, + ticket: ByteArray, data: Any, dataSize: Int, @@ -45,7 +45,7 @@ internal fun createImageDataPacketSequence( // RequestDataTrans ): Flow<ByteReadPacket> { ByteArrayPool.checkBufferSize(sizePerPacket) require(data is Input || data is InputStream || data is ByteReadChannel) { "unsupported data: ${data::class.simpleName}" } - require(uKey.size == 128) { "bad uKey. Required size=128, got ${uKey.size}" } + // require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" } require(data !is ByteReadPacket || data.remaining.toInt() == dataSize) { "bad input. given dataSize=$dataSize, but actual readRemaining=${(data as ByteReadPacket).remaining}" } val flow = when (data) { @@ -64,8 +64,12 @@ internal fun createImageDataPacketSequence( // RequestDataTrans version = 1, uin = client.uin.toString(), command = command, - seq = if (commandId == 2) client.nextHighwayDataTransSequenceIdForGroup() - else client.nextHighwayDataTransSequenceIdForFriend(), + seq = when (commandId) { + 2 -> client.nextHighwayDataTransSequenceIdForGroup() + 1 -> client.nextHighwayDataTransSequenceIdForFriend() + 27 -> client.nextHighwayDataTransSequenceIdForApplyUp() + else -> error("illegal commandId: $commandId") + }, retryTimes = 0, appid = appId, dataflag = dataFlag, @@ -77,7 +81,7 @@ internal fun createImageDataPacketSequence( // RequestDataTrans datalength = chunkedInput.bufferSize, dataoffset = offset, filesize = dataSize.toLong(), - serviceticket = uKey, + serviceticket = ticket, md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize), fileMd5 = fileMd5, flag = 0, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt index 8b9db91a0..82ae61362 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt @@ -17,7 +17,7 @@ import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY @Serializable -class BdhExtinfo : ProtoBuf { +internal class BdhExtinfo : ProtoBuf { @Serializable class CommFileExtReq( @ProtoId(1) val actionType: Int = 0, @@ -140,7 +140,7 @@ class BdhExtinfo : ProtoBuf { } @Serializable -class CSDataHighwayHead : ProtoBuf { +internal class CSDataHighwayHead : ProtoBuf { @Serializable class C2CCommonExtendinfo( @ProtoId(1) val infoId: Int = 0, @@ -283,7 +283,7 @@ class CSDataHighwayHead : ProtoBuf { } @Serializable -class HwConfigPersistentPB : ProtoBuf { +internal class HwConfigPersistentPB : ProtoBuf { @Serializable class HwConfigItemPB( @ProtoId(1) val ingKey: String = "", @@ -315,7 +315,7 @@ class HwConfigPersistentPB : ProtoBuf { } @Serializable -class HwSessionInfoPersistentPB : ProtoBuf { +internal class HwSessionInfoPersistentPB : ProtoBuf { @Serializable class HwSessionInfoPB( @ProtoId(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, @@ -324,7 +324,7 @@ class HwSessionInfoPersistentPB : ProtoBuf { } @Serializable -class Subcmd0x501 : ProtoBuf { +internal class Subcmd0x501 : ProtoBuf { @Serializable class ReqBody( @ProtoId(1281) val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt index 2d606cb12..4833a66ca 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt @@ -467,7 +467,7 @@ internal class ImMsgBody : ProtoBuf { @ProtoId(4) val rpId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(5) val prpFold: Int = 0, @ProtoId(6) val longTextFlag: Int = 0, - @ProtoId(7) val longTextResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val longTextResid: String = "", @ProtoId(8) val groupType: Int = 0, @ProtoId(9) val toUinFlag: Int = 0, @ProtoId(10) val glamourLevel: Int = 0, 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 index f77a52c39..1127fd142 100644 --- 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 @@ -44,7 +44,7 @@ internal class MultiMsg : ProtoBuf { @Serializable class MultiMsgApplyUpRsp( @ProtoId(1) val result: Int = 0, - @ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val msgResid: String = "", @ProtoId(3) val msgUkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val uint32UpIp: List<Int>? = null, @ProtoId(5) val uint32UpPort: List<Int>? = null, 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 index 245bb7496..32115c51d 100644 --- 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 @@ -12,7 +12,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat import kotlinx.io.core.ByteReadPacket -import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Group import net.mamoe.mirai.message.data.* import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf @@ -29,6 +29,7 @@ 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.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiPlatformUtils @@ -44,8 +45,8 @@ internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor } @OptIn(MiraiInternalAPI::class) -internal fun MessageChain.calculateValidationData( - bot: Bot +internal fun MessageChain.calculateValidationDataForGroup( + group: Group ): MessageValidationData { // top_package.akkv#method_42702 val source: MessageSource by this.orElse { error("internal error: calculateValidationData: cannot find MessageSource, chain=${this._miraiContentToString()}") } @@ -55,37 +56,39 @@ internal fun MessageChain.calculateValidationData( } val richTextElems = this.toRichTextElems(source is MessageSourceFromSendGroup) + .filterNot { it.generalFlags != null } val msgTransmit = MsgTransmit.PbMultiMsgTransmit( msg = listOf( MsgComm.Msg( msgHead = MsgComm.MsgHead( - fromUin = source.senderId, + fromUin = group.bot.uin, msgSeq = source.sequenceId, msgTime = source.time.toInt(), - msgUid = source.messageRandom.toLong(), // TODO: 2020/3/26 CHECK IT + msgUid = 0x01000000000000000L or 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, + groupCode = group.id, + groupCard = "Cinnamon"// group.botAsMember.nameCard, // Cinnamon ), + isSrcMsg = false ), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( - elems = richTextElems + elems = richTextElems.toMutableList() ) - ) - ) + ), + ), ) ) val bytes = msgTransmit.toByteArray(MsgTransmit.PbMultiMsgTransmit.serializer()) - return MessageValidationData(MiraiPlatformUtils.zip(bytes)) + return MessageValidationData(MiraiPlatformUtils.gzip(bytes)) } /* @@ -154,18 +157,27 @@ Packet 20:02:50 : =======================共有 0 个包======================= internal class MultiMsg { object ApplyUp : OutgoingPacketFactory<ApplyUp.Response>("MultiMsg.ApplyUp") { - class Response( - val proto: MultiMsg.MultiMsgApplyUpRsp - ) : Packet + sealed class Response : Packet { + data class RequireUpload( + val proto: MultiMsg.MultiMsgApplyUpRsp + ) : Response() { + override fun toString(): String { + if (PacketLogger.isEnabled) { + return _miraiContentToString() + } + return "MultiMsg.ApplyUp.Response.RequireUpload(proto=$proto)" + } + } - fun createForLongMessage( - client: QQAndroidClient, - message: MessageChain, - dstUin: Long, // group uin - ): OutgoingPacket = createForLongMessage(client, message.calculateValidationData(client.bot), dstUin) + object MessageTooLarge : Response() + + data class OK( + val resId: String + ) : Response() + } // captured from group - private fun createForLongMessage( + fun createForLongMessage( client: QQAndroidClient, messageData: MessageValidationData, dstUin: Long // group uin @@ -173,21 +185,24 @@ internal class MultiMsg { writeProtoBuf( MultiMsg.ReqBody.serializer(), MultiMsg.ReqBody( - subcmd = 1, - termType = 5, - platformType = 9, - netType = 3, // wifi=3, wap=5 - buildVer = client.buildVer, buType = 1, + buildVer = "8.2.0.1296", multimsgApplyupReq = listOf( MultiMsg.MultiMsgApplyUpReq( applyId = 0, dstUin = dstUin, msgMd5 = messageData.md5, - msgSize = messageData.data.size.toLong(), + msgSize = messageData.data.size.toLong().also { + println("data.size = $it") + }, msgType = 3 // TODO 3 for group? - ) - ) + ), + ), + netType = 3, // wifi=3, wap=5 + platformType = 9, + subcmd = 1, + termType = 5, + reqChannelType = 0, ) ) } @@ -210,13 +225,18 @@ internal class MultiMsg { } */ 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}" } + val body = readProtoBuf(MultiMsg.RspBody.serializer()) + val response = body.multimsgApplyupRsp!!.first() + return when (response.result) { + 0 -> Response.RequireUpload(response) + 193 -> Response.MessageTooLarge + //1 -> Response.OK(resId = response.msgResid) + else -> { + error(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/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt index 73a93646c..531074246 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt @@ -126,7 +126,7 @@ internal class MessageSvc { object EmptyResponse : GetMsgSuccess(emptyList()) - @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class, FlowPreview::class) + @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class, FlowPreview::class, LowLevelAPI::class) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00 val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer()) 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 2c6272f25..e3dc780ac 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 @@ -13,6 +13,7 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.SinceMirai import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -46,25 +47,33 @@ interface RichMessage : MessageContent { * * @param brief 消息内容纯文本, 显示在图片的前面 */ + @SinceMirai("0.31.0") + @OptIn(MiraiInternalAPI::class) @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> - """ + fun longMessage(brief: String, resId: String, timeSeconds: Long): RichMessage { + val limited: String = if (brief.length > 30) { + brief.take(30) + "…" + } else { + brief + } - return XmlMessage(template) + val template = """ + <?xml version='1.0' encoding='UTF-8' standalone='yes' ?> + <msg serviceID="35" templateID="1" action="viewMultiMsg" + brief="$limited" + m_resid="$resId" + m_fileName="$timeSeconds" sourceMsgId="0" url="" + flag="3" adverSign="0" multiMsgFlag="1"> + <item layout="1"> + <title>$limited</title> + <hr hidden="false" style="0"/> + <summary>点击查看完整消息</summary> + </item> + <source name="聊天记录" icon="" action="" appid="-1"/> + </msg> + """.trimIndent() + + return LongMessage(template, resId) } @MiraiExperimentalAPI @@ -142,11 +151,12 @@ class XmlMessage constructor(override val content: String) : RichMessage { } /** - * 合并转发消息 + * 长消息 */ @SinceMirai("0.31.0") @MiraiExperimentalAPI -class MergedForwardedMessage(override val content: String) : RichMessage { +@MiraiInternalAPI +class LongMessage(override val content: String, val resId: String) : RichMessage { companion object Key : Message.Key<XmlMessage> // serviceId = 35 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt index 49a691efe..4f170b68d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.utils import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler +import net.mamoe.mirai.network.LoginFailedException import kotlin.coroutines.CoroutineContext import kotlin.jvm.JvmStatic @@ -18,10 +19,33 @@ import kotlin.jvm.JvmStatic * 验证码, 设备锁解决器 */ expect abstract class LoginSolver { + /** + * 处理图片验证码. + * 返回 null 以表示无法处理验证码, 将会刷新验证码或重试登录. + * 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止 + * + * @throws LoginFailedException + */ abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? + /** + * 处理滑动验证码. + * 返回 null 以表示无法处理验证码, 将会刷新验证码或重试登录. + * 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止 + * + * @throws LoginFailedException + * @return 验证码解决成功后获得的 ticket. + */ abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? + /** + * 处理不安全设备验证. + * 在处理完成后返回任意内容 (包含 `null`) 均视为处理成功. + * 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止. + * + * @return 任意内容. 返回值保留以供未来更新. + * @throws LoginFailedException + */ abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? companion object { @@ -38,10 +62,12 @@ expect open class BotConfiguration() { * 日志记录器 */ var botLoggerSupplier: ((Bot) -> MiraiLogger) + /** * 网络层日志构造器 */ var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) + /** * 设备信息覆盖. 默认使用随机的设备信息. */ @@ -56,23 +82,28 @@ expect open class BotConfiguration() { * 心跳周期. 过长会导致被服务器断开连接. */ var heartbeatPeriodMillis: Long + /** * 每次心跳时等待结果的时间. * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响. */ var heartbeatTimeoutMillis: Long + /** * 心跳失败后的第一次重连前的等待时间. */ var firstReconnectDelayMillis: Long + /** * 重连失败后, 继续尝试的每次等待时间 */ var reconnectPeriodMillis: Long + /** * 最多尝试多少次重连 */ var reconnectionRetryTimes: Int + /** * 验证码处理器 */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt index c47f22772..2b6206171 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt @@ -11,8 +11,6 @@ package net.mamoe.mirai.utils.io -import kotlinx.io.core.IoBuffer -import kotlinx.io.pool.ObjectPool import kotlin.random.Random import kotlin.random.nextInt @@ -201,14 +199,4 @@ fun ByteArray.toUShort(): UShort = fun ByteArray.toInt(): Int = (this[0].toInt().and(255) shl 24) + (this[1].toInt().and(255) shl 16) + (this[2].toInt().and(255) shl 8) + (this[3].toInt().and( 255 - ) shl 0) - -/** - * 从 [IoBuffer.Pool] [borrow][ObjectPool.borrow] 一个 [IoBuffer] 然后将 [this] 写入. - * 注意回收 ([ObjectPool.recycle]) - */ -fun ByteArray.toIoBuffer( - offset: Int = 0, - length: Int = this.size - offset, - pool: ObjectPool<IoBuffer> = IoBuffer.Pool -): IoBuffer = pool.borrow().let { it.writeFully(this, offset, length); it } \ No newline at end of file + ) shl 0) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt index 3c40e4a36..8ed919284 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt @@ -18,6 +18,7 @@ import kotlin.jvm.JvmName /** * 要求 [this] 最小为 [min]. */ +@PublishedApi internal fun Int.coerceAtLeastOrFail(min: Int): Int { require(this >= min) return this @@ -26,6 +27,7 @@ internal fun Int.coerceAtLeastOrFail(min: Int): Int { /** * 要求 [this] 最小为 [min]. */ +@PublishedApi internal fun Long.coerceAtLeastOrFail(min: Long): Long { require(this >= min) return this @@ -34,10 +36,12 @@ internal fun Long.coerceAtLeastOrFail(min: Long): Long { /** * 要求 [this] 最大为 [max]. */ +@PublishedApi internal fun Int.coerceAtMostOrFail(max: Int): Int = if (this >= max) error("value is greater than its expected maximum value $max") else this +@PublishedApi internal fun Long.coerceAtMostOrFail(max: Long): Long = if (this >= max) error("value is greater than its expected maximum value $max") else this \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt index f40840a37..1d9c96cbe 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt @@ -14,7 +14,7 @@ package net.mamoe.mirai.utils import io.ktor.client.HttpClient /** - * 时间戳 + * 时间戳. */ expect val currentTimeMillis: Long From e7a691dc4ca539b07ada234f88cf12da231998c0 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 29 Mar 2020 02:33:37 +0800 Subject: [PATCH 08/11] Adjust name --- .../kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 12 +++++------- .../network/protocol/packet/chat/MultiMsg.kt | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) 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 2aff75912..80484ce0b 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 @@ -370,7 +370,7 @@ internal abstract class QQAndroidBotBase constructor( @MiraiExperimentalAPI override suspend fun _lowLevelSendLongMessage(groupCode: Long, message: Message) { val chain = message.asMessageChain() - check(chain.toString().length <= 3000 && chain.count { it is Image } <= 10) { "message is too large" } + check(chain.toString().length <= 4500 && chain.count { it is Image } <= 50) { "message is too large" } val group = getGroup(groupCode) val source = MessageSourceFromSendFriend( @@ -385,9 +385,9 @@ internal abstract class QQAndroidBotBase constructor( ) // TODO: 2020/3/26 util 方法来添加单例元素 - val toSend = buildMessageChain(chain) { + val toSend = buildMessageChain(chain.size) { source.originalMessage.forEach { - if (it !is MessageSource){ + if (it !is MessageSource) { add(it) } } @@ -398,7 +398,7 @@ internal abstract class QQAndroidBotBase constructor( val data = toSend.calculateValidationDataForGroup(group) val response = - MultiMsg.ApplyUp.createForLongMessage( + MultiMsg.ApplyUp.createForGroupLongMessage( client = this@QQAndroidBotBase.client, messageData = data, dstUin = Group.calculateGroupUinByGroupCode(groupCode) @@ -446,7 +446,7 @@ internal abstract class QQAndroidBotBase constructor( group.sendMessage( RichMessage.longMessage( - brief = toSend.joinToString { + brief = toSend.joinToString(limit = 30) { when (it) { is PlainText -> it.stringValue is At -> it.toString() @@ -457,8 +457,6 @@ internal abstract class QQAndroidBotBase constructor( timeSeconds = source.time ) ) - - println(response._miraiContentToString()) } } 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 index 32115c51d..4b8176f49 100644 --- 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 @@ -177,7 +177,7 @@ internal class MultiMsg { } // captured from group - fun createForLongMessage( + fun createForGroupLongMessage( client: QQAndroidClient, messageData: MessageValidationData, dstUin: Long // group uin From aa553ebb75090934ca3594745a0bffd30d97dbbf Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 29 Mar 2020 02:33:49 +0800 Subject: [PATCH 09/11] Add `MessageChain.size` --- .../message/data/MessageChain.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index 6aad7bbd9..c31a8d3cd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -16,6 +16,7 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.message.data.NullMessageChain.equals import net.mamoe.mirai.message.data.NullMessageChain.toString import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.SinceMirai import kotlin.js.JsName import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -45,6 +46,12 @@ interface MessageChain : Message, Iterable<SingleMessage> { override operator fun contains(sub: String): Boolean override fun toString(): String + /** + * 元素数量 + */ + @SinceMirai("0.31.1") + val size: Int + /** * 获取第一个类型为 [key] 的 [Message] 实例 * @@ -374,7 +381,6 @@ inline fun MessageChain.flatten(): Sequence<SingleMessage> = this.asSequence() / // endregion converters -// region implementations /** * 不含任何元素的 [MessageChain] @@ -389,19 +395,25 @@ object EmptyMessageChain : MessageChain by MessageChainImplByIterable(emptyList( */ object NullMessageChain : MessageChain { override fun toString(): String = "NullMessageChain" + override val size: Int get() = 0 override fun equals(other: Any?): Boolean = other === this override fun contains(sub: String): Boolean = error("accessing NullMessageChain") override fun followedBy(tail: Message): CombinedMessage = CombinedMessage(left = EmptyMessageChain, tail = tail) override fun iterator(): MutableIterator<SingleMessage> = error("accessing NullMessageChain") } + +// region implementations + + /** * 使用 [Iterable] 作为委托的 [MessageChain] */ @PublishedApi -internal inline class MessageChainImplByIterable constructor( +internal class MessageChainImplByIterable constructor( private val delegate: Iterable<SingleMessage> ) : Message, Iterable<SingleMessage>, MessageChain { + override val size: Int by lazy { delegate.count() } override fun iterator(): Iterator<SingleMessage> = delegate.iterator() override fun toString(): String = this.delegate.joinToString("") { it.toString() } override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } @@ -411,9 +423,10 @@ internal inline class MessageChainImplByIterable constructor( * 使用 [Collection] 作为委托的 [MessageChain] */ @PublishedApi -internal inline class MessageChainImplByCollection constructor( +internal class MessageChainImplByCollection constructor( private val delegate: Collection<SingleMessage> ) : Message, Iterable<SingleMessage>, MessageChain { + override val size: Int get() = delegate.size override fun iterator(): Iterator<SingleMessage> = delegate.iterator() override fun toString(): String = this.delegate.joinToString("") { it.toString() } override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } @@ -426,11 +439,12 @@ internal inline class MessageChainImplByCollection constructor( internal class MessageChainImplBySequence constructor( delegate: Sequence<SingleMessage> ) : Message, Iterable<SingleMessage>, MessageChain { + override val size: Int by lazy { collected.size } + /** * [Sequence] 可能只能消耗一遍, 因此需要先转为 [List] */ private val collected: List<SingleMessage> by lazy { delegate.toList() } - override fun iterator(): Iterator<SingleMessage> = collected.iterator() override fun toString(): String = this.collected.joinToString("") { it.toString() } override operator fun contains(sub: String): Boolean = collected.any { it.contains(sub) } @@ -440,9 +454,10 @@ internal class MessageChainImplBySequence constructor( * 单个 [SingleMessage] 作为 [MessageChain] */ @PublishedApi -internal inline class SingleMessageChainImpl constructor( +internal class SingleMessageChainImpl constructor( private val delegate: SingleMessage ) : Message, Iterable<SingleMessage>, MessageChain { + override val size: Int get() = 1 override fun toString(): String = this.delegate.toString() override fun iterator(): Iterator<SingleMessage> = iterator { yield(delegate) } override operator fun contains(sub: String): Boolean = sub in delegate From 7efb7834dbf302abc61dd1565605eaa78f5a1da0 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 29 Mar 2020 02:34:55 +0800 Subject: [PATCH 10/11] Remove senseless response --- .../kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 2 -- .../mirai/qqandroid/network/protocol/packet/chat/MultiMsg.kt | 4 ---- 2 files changed, 6 deletions(-) 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 80484ce0b..23c7dc093 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 @@ -408,8 +408,6 @@ internal abstract class QQAndroidBotBase constructor( when (response) { is MultiMsg.ApplyUp.Response.MessageTooLarge -> error("message is too large") - is MultiMsg.ApplyUp.Response.OK -> { - resId = response.resId } is MultiMsg.ApplyUp.Response.RequireUpload -> { resId = response.proto.msgResid 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 index 4b8176f49..fbc332b7a 100644 --- 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 @@ -170,10 +170,6 @@ internal class MultiMsg { } object MessageTooLarge : Response() - - data class OK( - val resId: String - ) : Response() } // captured from group From 90ef83e34556e1a31da619206aa7351bf8c6e959 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 29 Mar 2020 02:46:38 +0800 Subject: [PATCH 11/11] Move protocol-specific classes to mirai-core-qqandroid --- .../mamoe/mirai/qqandroid}/utils/cryptor/ECDHAndroid.kt | 2 +- .../kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 1 - .../net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt | 4 +--- .../mirai/qqandroid/io/serialization/jce/JceInput.kt | 2 +- .../net/mamoe/mirai/qqandroid/io/serialization/utils.kt | 2 +- .../mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt | 4 ++-- .../net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt | 4 ++-- .../mirai/qqandroid/network/highway/HighwayHelper.kt | 2 +- .../qqandroid/network/protocol/packet/EncryptMethod.kt | 8 ++++---- .../network/protocol/packet/OutgoingPacketAndroid.kt | 6 +++--- .../qqandroid/network/protocol/packet/PacketFactory.kt | 8 ++++++-- .../mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt | 1 + .../network/protocol/packet/chat/receive/OnlinePush.kt | 2 +- .../qqandroid/network/protocol/packet/login/WtLogin.kt | 3 ++- .../net/mamoe/mirai/qqandroid}/utils/cryptor/ECDH.kt | 2 +- .../net/mamoe/mirai/qqandroid}/utils/cryptor/TEA.kt | 2 +- .../kotlin/net/mamoe/mirai/qqandroid}/utils/io/input.kt | 5 ++++- .../kotlin/net/mamoe/mirai/qqandroid}/utils/io/output.kt | 6 +++--- .../kotlin/net/mamoe/mirai/qqandroid}/utils/numbers.kt | 2 +- .../net/mamoe/mirai/qqandroid}/utils/cryptor/ECDHJvm.kt | 8 +++++--- 20 files changed, 41 insertions(+), 33 deletions(-) rename {mirai-core/src/androidMain/kotlin/net/mamoe/mirai => mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid}/utils/cryptor/ECDHAndroid.kt (98%) rename {mirai-core/src/commonMain/kotlin/net.mamoe.mirai => mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid}/utils/cryptor/ECDH.kt (98%) rename {mirai-core/src/commonMain/kotlin/net.mamoe.mirai => mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid}/utils/cryptor/TEA.kt (99%) rename {mirai-core/src/commonMain/kotlin/net.mamoe.mirai => mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid}/utils/io/input.kt (96%) rename {mirai-core/src/commonMain/kotlin/net.mamoe.mirai => mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid}/utils/io/output.kt (94%) rename {mirai-core/src/commonMain/kotlin/net.mamoe.mirai => mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid}/utils/numbers.kt (96%) rename {mirai-core/src/jvmMain/kotlin/net/mamoe/mirai => mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid}/utils/cryptor/ECDHJvm.kt (93%) diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHAndroid.kt similarity index 98% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt rename to mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHAndroid.kt index b6b5a6a93..46c00f2ba 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHAndroid.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.cryptor +package net.mamoe.mirai.qqandroid.utils.cryptor import android.annotation.SuppressLint import net.mamoe.mirai.utils.MiraiInternalAPI 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 23c7dc093..53c1e009c 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 @@ -408,7 +408,6 @@ internal abstract class QQAndroidBotBase constructor( when (response) { is MultiMsg.ApplyUp.Response.MessageTooLarge -> error("message is too large") - } is MultiMsg.ApplyUp.Response.RequireUpload -> { resId = response.proto.msgResid diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt index 1acaa21e2..d3eb05a7b 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt @@ -18,10 +18,8 @@ import kotlinx.serialization.builtins.SetSerializer import kotlinx.serialization.internal.* import kotlinx.serialization.modules.EmptyModule import kotlinx.serialization.modules.SerialModule -import kotlinx.serialization.protobuf.ProtoId import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.ProtoBuf -import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.BYTE import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.DOUBLE import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.FLOAT @@ -39,7 +37,7 @@ import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.STRUCT_END import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.ZERO_TYPE import net.mamoe.mirai.qqandroid.io.serialization.jce.JceHead import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId -import net.mamoe.mirai.utils.io.readString +import net.mamoe.mirai.qqandroid.utils.io.readString import net.mamoe.mirai.utils.io.toReadPacket @PublishedApi diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt index 595b41269..fdfb7f2f2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt @@ -11,7 +11,7 @@ package net.mamoe.mirai.qqandroid.io.serialization.jce import kotlinx.io.core.* import net.mamoe.mirai.qqandroid.io.serialization.JceCharset -import net.mamoe.mirai.utils.io.readString +import net.mamoe.mirai.qqandroid.utils.io.readString /** diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt index 1b72418a1..48c06f82d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt @@ -25,7 +25,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.firstValue import net.mamoe.mirai.utils.io.read -import net.mamoe.mirai.utils.io.readPacketExact +import net.mamoe.mirai.qqandroid.utils.io.readPacketExact import net.mamoe.mirai.utils.io.toReadPacket import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index dda1901bc..7241c58e4 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -39,8 +39,8 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.PlatformSocket -import net.mamoe.mirai.utils.io.readPacketExact -import net.mamoe.mirai.utils.io.useBytes +import net.mamoe.mirai.qqandroid.utils.io.readPacketExact +import net.mamoe.mirai.qqandroid.utils.io.useBytes import kotlin.coroutines.CoroutineContext import kotlin.jvm.Volatile import kotlin.time.ExperimentalTime 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 da22f797b..fda220a5c 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 @@ -22,9 +22,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv import net.mamoe.mirai.qqandroid.utils.NetworkType +import net.mamoe.mirai.qqandroid.utils.cryptor.ECDH import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.cryptor.ECDH -import net.mamoe.mirai.utils.cryptor.TEA +import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.utils.io.* /* diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt index d913d4431..03ab3af1a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt @@ -33,7 +33,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.copyAndClose import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.PlatformSocket -import net.mamoe.mirai.utils.io.withUse +import net.mamoe.mirai.qqandroid.utils.io.withUse import kotlinx.serialization.InternalSerializationApi @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/EncryptMethod.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/EncryptMethod.kt index fcafc4cda..f663a0f9a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/EncryptMethod.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/EncryptMethod.kt @@ -14,10 +14,10 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.buildPacket import kotlinx.io.core.writeFully import net.mamoe.mirai.qqandroid.network.QQAndroidClient -import net.mamoe.mirai.utils.cryptor.ECDH -import net.mamoe.mirai.utils.cryptor.ECDHKeyPair -import net.mamoe.mirai.utils.io.encryptAndWrite -import net.mamoe.mirai.utils.io.writeShortLVByteArray +import net.mamoe.mirai.qqandroid.utils.cryptor.ECDH +import net.mamoe.mirai.qqandroid.utils.cryptor.ECDHKeyPair +import net.mamoe.mirai.qqandroid.utils.io.encryptAndWrite +import net.mamoe.mirai.qqandroid.utils.io.writeShortLVByteArray @OptIn(ExperimentalUnsignedTypes::class) internal interface EncryptMethod { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt index ef95247a5..1ae7d3289 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt @@ -16,9 +16,9 @@ import kotlinx.io.core.buildPacket import kotlinx.io.core.writeFully import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.io.encryptAndWrite -import net.mamoe.mirai.utils.io.writeHex -import net.mamoe.mirai.utils.io.writeIntLVPacket +import net.mamoe.mirai.qqandroid.utils.io.encryptAndWrite +import net.mamoe.mirai.qqandroid.utils.io.writeHex +import net.mamoe.mirai.qqandroid.utils.io.writeIntLVPacket internal class OutgoingPacket constructor( name: String?, 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 54556697e..4a11f2c1f 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 @@ -27,9 +27,13 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin import net.mamoe.mirai.qqandroid.network.readUShortLVByteArray +import net.mamoe.mirai.qqandroid.utils.io.readPacketExact +import net.mamoe.mirai.qqandroid.utils.io.readString +import net.mamoe.mirai.qqandroid.utils.io.useBytes +import net.mamoe.mirai.qqandroid.utils.io.withUse import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.cryptor.TEA -import net.mamoe.mirai.utils.cryptor.adjustToPublicKey +import net.mamoe.mirai.qqandroid.utils.cryptor.TEA +import net.mamoe.mirai.qqandroid.utils.cryptor.adjustToPublicKey import net.mamoe.mirai.utils.io.* import kotlin.jvm.JvmName diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt index 91e6ab2a5..ce54b8901 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt @@ -17,6 +17,7 @@ import kotlinx.io.core.toByteArray import kotlinx.io.core.writeFully import net.mamoe.mirai.qqandroid.network.protocol.LoginType import net.mamoe.mirai.qqandroid.utils.NetworkType +import net.mamoe.mirai.qqandroid.utils.io.* import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiPlatformUtils import net.mamoe.mirai.utils.currentTimeMillis diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt index d4c61ba14..b11c38266 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt @@ -38,7 +38,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.io.read -import net.mamoe.mirai.utils.io.readString +import net.mamoe.mirai.qqandroid.utils.io.readString import net.mamoe.mirai.utils.io.toUHexString internal class OnlinePush { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt index 8df0f0188..c8bc93354 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt @@ -20,8 +20,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.* import net.mamoe.mirai.qqandroid.utils.GuidSource import net.mamoe.mirai.qqandroid.utils.MacOrAndroidIdChangeFlag import net.mamoe.mirai.qqandroid.utils.guidFlag +import net.mamoe.mirai.qqandroid.utils.io.* import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.cryptor.TEA +import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.utils.io.* internal class WtLogin { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDH.kt similarity index 98% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDH.kt index 0b93f83b5..85434c3ab 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDH.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.cryptor +package net.mamoe.mirai.qqandroid.utils.cryptor import net.mamoe.mirai.utils.io.chunkedHexToBytes diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/TEA.kt similarity index 99% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/TEA.kt index 3e43b3fde..ea889b47c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/TEA.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.cryptor +package net.mamoe.mirai.qqandroid.utils.cryptor import kotlinx.io.core.ByteReadPacket import kotlinx.io.pool.useInstance diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/input.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt similarity index 96% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/input.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt index 128fffbd0..4471a7be8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/input.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt @@ -11,7 +11,7 @@ @file:JvmMultifileClass @file:JvmName("Utils") -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils.io import kotlinx.io.OutputStream import kotlinx.io.charsets.Charset @@ -27,6 +27,9 @@ import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic import kotlinx.serialization.InternalSerializationApi +import net.mamoe.mirai.utils.io.ByteArrayPool +import net.mamoe.mirai.utils.io.toReadPacket +import net.mamoe.mirai.utils.io.toUHexString @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) fun ByteReadPacket.copyTo(outputStream: OutputStream) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/output.kt similarity index 94% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/output.kt index 38077fe9e..e6579d96d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/output.kt @@ -11,12 +11,12 @@ @file:JvmMultifileClass @file:JvmName("Utils") -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils.io import kotlinx.io.core.* +import net.mamoe.mirai.qqandroid.utils.coerceAtMostOrFail +import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.coerceAtMostOrFail -import net.mamoe.mirai.utils.cryptor.TEA import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/numbers.kt similarity index 96% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/numbers.kt index 8ed919284..856584958 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/numbers.kt @@ -10,7 +10,7 @@ @file:JvmMultifileClass @file:JvmName("Utils") -package net.mamoe.mirai.utils +package net.mamoe.mirai.qqandroid.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHJvm.kt similarity index 93% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt rename to mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHJvm.kt index 0cb60c7e9..04f36145f 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHJvm.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.cryptor +package net.mamoe.mirai.qqandroid.utils.cryptor import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiPlatformUtils @@ -27,11 +27,13 @@ internal actual class ECDHKeyPairImpl( override val privateKey: ECDHPrivateKey get() = delegate.private override val publicKey: ECDHPublicKey get() = delegate.public - override val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) + override val initialShareKey: ByteArray = + ECDH.calculateShareKey(privateKey, initialPublicKey) } @Suppress("FunctionName") -actual fun ECDH() = ECDH(ECDH.generateKeyPair()) +actual fun ECDH() = + ECDH(ECDH.generateKeyPair()) actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { actual companion object {