From 35dca403bff1f799df80910c2ba656bf0d3b5ae9 Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 3 Feb 2020 21:38:37 +0800 Subject: [PATCH] Image upload --- .../net/mamoe/mirai/qqandroid/ContactImpl.kt | 81 +++- .../qqandroid/network/QQAndroidClient.kt | 6 +- .../mirai/qqandroid/network/highway/Codec.kt | 129 +++++ .../network/protocol/data/proto/Cmd0x388.kt | 279 +++++++++++ .../network/protocol/data/proto/Highway.kt | 454 ++++++++++++++++++ .../network/protocol/packet/PacketFactory.kt | 8 +- .../packet/chat/image/ImageDownPacket.kt | 42 -- .../packet/chat/image/ImageUpPacket.kt | 19 +- .../protocol/packet/chat/image/ImgStore.kt | 101 ++++ .../protocol/packet/chat/image/LongConn.kt | 42 ++ .../kotlin/test/ProtoBufDataClassGenerator.kt | 4 +- .../kotlin/net.mamoe.mirai/contact/Group.kt | 2 - .../net.mamoe.mirai/utils/ExternalImage.kt | 64 ++- .../net/mamoe/mirai/utils/ExternalImageJvm.kt | 6 +- 14 files changed, 1156 insertions(+), 81 deletions(-) create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x388.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt delete mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageDownPacket.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/LongConn.kt diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index 5d68aef24..fb47841fc 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -1,15 +1,26 @@ package net.mamoe.mirai.qqandroid +import kotlinx.io.core.readBytes import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.FriendNameRemark import net.mamoe.mirai.data.PreviousNameList import net.mamoe.mirai.data.Profile import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.NotOnlineImageFromFile +import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf +import net.mamoe.mirai.qqandroid.network.highway.Highway +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc +import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse +import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.utils.getValue +import net.mamoe.mirai.utils.io.PlatformSocket +import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.unsafeWeakRef import kotlin.coroutines.CoroutineContext @@ -116,7 +127,73 @@ internal class GroupImpl( } override suspend fun uploadImage(image: ExternalImage): Image { - TODO("not implemented") - } + bot.network.run { + val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp( + bot.client, + uin = bot.uin, + groupCode = id, + md5 = image.md5, + size = image.inputSize, + picWidth = image.width, + picHeight = image.height, + picType = image.imageType, + filename = image.filename + ).sendAndExpect() + when (response) { + is ImgStore.GroupPicUp.Response.Failed -> error("upload group image failed with reason ${response.message}") + is ImgStore.GroupPicUp.Response.FileExists -> { + val resourceId = image.calculateImageResourceId() + return NotOnlineImageFromFile( + resourceId = resourceId, + md5 = response.fileInfo.fileMd5, + filepath = resourceId, + fileLength = response.fileInfo.fileSize.toInt(), + height = response.fileInfo.fileHeight, + width = response.fileInfo.fileWidth, + imageType = response.fileInfo.fileType + ) + } + is ImgStore.GroupPicUp.Response.RequireUpload -> { + + val socket = PlatformSocket() + socket.connect(response.uploadIpList.first().toIpV4AddressString().also { println("serverIp=$it") }, response.uploadPortList.first()) + // socket.use { + socket.send( + Highway.RequestDataTrans( + uin = bot.uin, + command = "PicUp.DataUp", + buildVer = bot.client.buildVer, + uKey = response.uKey, + data = image.input, + dataSize = image.inputSize.toInt(), + md5 = image.md5, + sequenceId = bot.client.nextHighwayDataTransSequenceId() + ) + ) + // } + + //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 + socket.read().withUse { + readByte() + val headLength = readInt() + val bodyLength = readInt() + val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength) + println(proto.contentToString()) + println(readBytes(bodyLength).toUHexString()) + } + val resourceId = image.calculateImageResourceId() + return NotOnlineImageFromFile( + resourceId = resourceId, + md5 = image.md5, + filepath = resourceId, + fileLength = image.inputSize.toInt(), + height = image.height, + width = image.width, + imageType = image.imageType + ) + } + } + } + } } \ No newline at end of file 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 e19f490ca..074118e57 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 @@ -95,7 +95,8 @@ internal open class QQAndroidClient( var openAppId: Long = 715019303L - val apkVersionName: ByteArray = "8.2.0".toByteArray() + val apkVersionName: ByteArray get() = "8.2.0".toByteArray() + val buildVer: String get() = "8.2.0.1296" private val messageSequenceId: AtomicInt = atomic(0) internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2) @@ -103,6 +104,9 @@ internal open class QQAndroidClient( private val requestPacketRequestId: AtomicInt = atomic(1921334513) internal fun nextRequestPacketRequestId(): Int = requestPacketRequestId.getAndAdd(2) + private val highwayDataTransSequenceId: AtomicInt = atomic(87017) + internal fun nextHighwayDataTransSequenceId(): Int = highwayDataTransSequenceId.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/Codec.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt new file mode 100644 index 000000000..c7092324b --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt @@ -0,0 +1,129 @@ +package net.mamoe.mirai.qqandroid.network.highway + +import io.ktor.client.HttpClient +import io.ktor.client.request.post +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.URLProtocol +import io.ktor.http.content.OutgoingContent +import io.ktor.http.userAgent +import kotlinx.coroutines.io.ByteWriteChannel +import kotlinx.io.core.* +import kotlinx.io.pool.useInstance +import net.mamoe.mirai.qqandroid.io.serialization.toByteArray +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY +import net.mamoe.mirai.utils.io.ByteArrayPool + +@Suppress("SpellCheckingInspection") +internal suspend inline fun HttpClient.postImage( + htcmd: String, + uin: Long, + groupcode: Long?, + imageInput: Input, + inputSize: Long, + uKeyHex: String +): Boolean = try { + post { + url { + protocol = URLProtocol.HTTP + host = "htdata2.qq.com" + path("cgi-bin/httpconn") + + parameters["htcmd"] = htcmd + parameters["uin"] = uin.toString() + + if (groupcode != null) parameters["groupcode"] = groupcode.toString() + + parameters["term"] = "pc" + parameters["ver"] = "5603" + parameters["filesize"] = inputSize.toString() + parameters["range"] = 0.toString() + parameters["ukey"] = uKeyHex + + userAgent("QQClient") + } + + body = object : OutgoingContent.WriteChannelContent() { + override val contentType: ContentType = ContentType.Image.Any + override val contentLength: Long = inputSize + + override suspend fun writeTo(channel: ByteWriteChannel) { + ByteArrayPool.useInstance { buffer: ByteArray -> + var size: Int + while (imageInput.readAvailable(buffer).also { size = it } != 0) { + channel.writeFully(buffer, 0, size) + } + } + } + } + } == HttpStatusCode.OK +} finally { + imageInput.close() +} + +object Highway { + fun RequestDataTrans( + uin: Long, + command: String, + sequenceId: Int, + buildVer: String, + appId: Int = 537062845, + dataFlag: Int = 4096, + commandId: Int = 2, + localId: Int = 2052, + + uKey: ByteArray, + data: Input, + dataSize: Int, + md5: ByteArray + ): ByteReadPacket { + val dataHighwayHead = CSDataHighwayHead.DataHighwayHead( + version = 1, + uin = uin.toString(), + command = command, + seq = sequenceId, + retryTimes = 0, + appid = appId, + dataflag = dataFlag, + commandId = commandId, + buildVer = buildVer, + localeId = localId + ) + val segHead = CSDataHighwayHead.SegHead( + datalength = dataSize, + filesize = dataSize.toLong() and 0xFFffFFff, + serviceticket = uKey, + md5 = md5, + fileMd5 = md5 + ) + return Codec.buildC2SData(dataHighwayHead, segHead, EMPTY_BYTE_ARRAY, null, data, dataSize) + } + + private object Codec { + fun buildC2SData( + dataHighwayHead: CSDataHighwayHead.DataHighwayHead, + segHead: CSDataHighwayHead.SegHead, + extendInfo: ByteArray, + loginSigHead: CSDataHighwayHead.LoginSigHead?, + body: Input, + bodySize: Int + ): ByteReadPacket { + val head = CSDataHighwayHead.ReqDataHighwayHead( + msgBasehead = dataHighwayHead, + msgSeghead = segHead, + reqExtendinfo = extendInfo, + msgLoginSigHead = loginSigHead + ).toByteArray(CSDataHighwayHead.ReqDataHighwayHead.serializer()) + + return buildPacket { + writeByte(40) + writeInt(head.size) + writeInt(bodySize) + writeFully(head) + body.copyTo(this) + writeByte(41) + } + } + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x388.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x388.kt new file mode 100644 index 000000000..e6ea3f23f --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x388.kt @@ -0,0 +1,279 @@ +package net.mamoe.mirai.qqandroid.network.protocol.data.proto + +import kotlinx.serialization.SerialId +import kotlinx.serialization.Serializable +import net.mamoe.mirai.qqandroid.io.ProtoBuf +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY + +@Serializable +class Cmd0x388 : ProtoBuf { + @Serializable + class DelImgReq( + @SerialId(1) val srcUin: Long = 0L, + @SerialId(2) val dstUin: Long = 0L, + @SerialId(3) val reqTerm: Int = 0, + @SerialId(4) val reqPlatformType: Int = 0, + @SerialId(5) val buType: Int = 0, + @SerialId(6) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(7) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(8) val picWidth: Int = 0, + @SerialId(9) val picHeight: Int = 0 + ) : ProtoBuf + + @Serializable + class DelImgRsp( + @SerialId(1) val result: Int = 0, + @SerialId(2) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class ExpRoamExtendInfo( + @SerialId(1) val resid: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class ExpRoamPicInfo( + @SerialId(1) val shopFlag: Int = 0, + @SerialId(2) val pkgId: Int = 0, + @SerialId(3) val picId: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class ExtensionCommPicTryUp( + @SerialId(1) val bytesExtinfo: List? = null + ) : ProtoBuf + + @Serializable + class ExtensionExpRoamTryUp( + @SerialId(1) val msgExproamPicInfo: List? = null + ) : ProtoBuf + + @Serializable + class GetImgUrlReq( + @SerialId(1) val groupCode: Long = 0L, + @SerialId(2) val dstUin: Long = 0L, + @SerialId(3) val fileid: Long = 0L, + @SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(5) val urlFlag: Int = 0, + @SerialId(6) val urlType: Int = 0, + @SerialId(7) val reqTerm: Int = 0, + @SerialId(8) val reqPlatformType: Int = 0, + @SerialId(9) val innerIp: Int = 0, + @SerialId(10) val buType: Int = 0, + @SerialId(11) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(12) val fileId: Long = 0L, + @SerialId(13) val fileSize: Long = 0L, + @SerialId(14) val originalPic: Int = 0, + @SerialId(15) val retryReq: Int = 0, + @SerialId(16) val fileHeight: Int = 0, + @SerialId(17) val fileWidth: Int = 0, + @SerialId(18) val picType: Int = 0, + @SerialId(19) val picUpTimestamp: Int = 0, + @SerialId(20) val reqTransferType: Int = 0 + ) : ProtoBuf + + @Serializable + class GetImgUrlRsp( + @SerialId(1) val fileid: Long = 0L, + @SerialId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val result: Int = 0, + @SerialId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(5) val msgImgInfo: ImgInfo? = null, + @SerialId(6) val bytesThumbDownUrl: List? = null, + @SerialId(7) val bytesOriginalDownUrl: List? = null, + @SerialId(8) val bytesBigDownUrl: List? = null, + @SerialId(9) val uint32DownIp: List? = null, + @SerialId(10) val uint32DownPort: List? = null, + @SerialId(11) val downDomain: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(12) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(13) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(14) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(15) val fileId: Long = 0L, + @SerialId(16) val autoDownType: Int = 0, + @SerialId(17) val uint32OrderDownType: List? = null, + @SerialId(19) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(20) val httpsUrlFlag: Int = 0, + @SerialId(26) val msgDownIp6: List? = null, + @SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class GetPttUrlReq( + @SerialId(1) val groupCode: Long = 0L, + @SerialId(2) val dstUin: Long = 0L, + @SerialId(3) val fileid: Long = 0L, + @SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(5) val reqTerm: Int = 0, + @SerialId(6) val reqPlatformType: Int = 0, + @SerialId(7) val innerIp: Int = 0, + @SerialId(8) val buType: Int = 0, + @SerialId(9) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(10) val fileId: Long = 0L, + @SerialId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(12) val codec: Int = 0, + @SerialId(13) val buId: Int = 0, + @SerialId(14) val reqTransferType: Int = 0, + @SerialId(15) val isAuto: Int = 0 + ) : ProtoBuf + + @Serializable + class GetPttUrlRsp( + @SerialId(1) val fileid: Long = 0L, + @SerialId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val result: Int = 0, + @SerialId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(5) val bytesDownUrl: List? = null, + @SerialId(6) val uint32DownIp: List? = null, + @SerialId(7) val uint32DownPort: List? = null, + @SerialId(8) val downDomain: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(9) val downPara: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(10) val fileId: Long = 0L, + @SerialId(11) val transferType: Int = 0, + @SerialId(12) val allowRetry: Int = 0, + @SerialId(26) val msgDownIp6: List? = null, + @SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(28) val strDomain: String = "" + ) : ProtoBuf + + @Suppress("ArrayInDataClass") + @Serializable + data class ImgInfo( + @SerialId(1) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val fileType: Int = 0, + @SerialId(3) val fileSize: Long = 0L, + @SerialId(4) val fileWidth: Int = 0, + @SerialId(5) val fileHeight: Int = 0 + ) : ProtoBuf { + override fun toString(): String { + return "ImgInfo(fileMd5=${fileMd5.contentToString()}, fileType=$fileType, fileSize=$fileSize, fileWidth=$fileWidth, fileHeight=$fileHeight)" + } + } + + @Serializable + class IPv6Info( + @SerialId(1) val ip6: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val port: Int = 0 + ) : ProtoBuf + + @Serializable + class PicSize( + @SerialId(1) val original: Int = 0, + @SerialId(2) val thumb: Int = 0, + @SerialId(3) val high: Int = 0 + ) : ProtoBuf + + @Serializable + class ReqBody( + @SerialId(1) val netType: Int = 0, + @SerialId(2) val subcmd: Int = 0, + @SerialId(3) val msgTryupImgReq: List? = null, + @SerialId(4) val msgGetimgUrlReq: List? = null, + @SerialId(5) val msgTryupPttReq: List? = null, + @SerialId(6) val msgGetpttUrlReq: List? = null, + @SerialId(7) val commandId: Int = 0, + @SerialId(8) val msgDelImgReq: List? = null, + @SerialId(1001) val extension: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class RspBody( + @SerialId(1) val clientIp: Int = 0, + @SerialId(2) val subcmd: Int = 0, + @SerialId(3) val msgTryupImgRsp: List? = null, + @SerialId(4) val msgGetimgUrlRsp: List? = null, + @SerialId(5) val msgTryupPttRsp: List? = null, + @SerialId(6) val msgGetpttUrlRsp: List? = null, + @SerialId(7) val msgDelImgRsp: List? = null + ) : ProtoBuf + + @Serializable + class TryUpImgReq( + @SerialId(1) val groupCode: Long = 0L, + @SerialId(2) val srcUin: Long = 0L, + @SerialId(3) val fileId: Long = 0L, + @SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(5) val fileSize: Long = 0L, + @SerialId(6) val fileName: String ="", + @SerialId(7) val srcTerm: Int = 0, + @SerialId(8) val platformType: Int = 0, + @SerialId(9) val buType: Int = 0, + @SerialId(10) val picWidth: Int = 0, + @SerialId(11) val picHeight: Int = 0, + @SerialId(12) val picType: Int = 0, + @SerialId(13) val buildVer: String = "", + @SerialId(14) val innerIp: Int = 0, + @SerialId(15) val appPicType: Int = 0, + @SerialId(16) val originalPic: Int = 0, + @SerialId(17) val fileIndex: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(18) val dstUin: Long = 0L, + @SerialId(19) val srvUpload: Int = 0, + @SerialId(20) val transferUrl: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class TryUpImgRsp( + @SerialId(1) val fileId: Long = 0L, + @SerialId(2) val result: Int = 0, + @SerialId(3) val failMsg: String = "", + @SerialId(4) val boolFileExit: Boolean = false, + @SerialId(5) val msgImgInfo: ImgInfo? = null, + @SerialId(6) val uint32UpIp: List? = null, + @SerialId(7) val uint32UpPort: List? = null, + @SerialId(8) val upUkey: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(9) val fileid: Long = 0L, + @SerialId(10) val upOffset: Long = 0L, + @SerialId(11) val blockSize: Long = 0L, + @SerialId(12) val boolNewBigChan: Boolean = false, + @SerialId(26) val msgUpIp6: List? = null, + @SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(1001) val msgInfo4busi: TryUpInfo4Busi? = null + ) : ProtoBuf + + @Serializable + class TryUpInfo4Busi( + @SerialId(1) val downDomain: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(4) val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(5) val fileResid: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class TryUpPttReq( + @SerialId(1) val groupCode: Long = 0L, + @SerialId(2) val srcUin: Long = 0L, + @SerialId(3) val fileId: Long = 0L, + @SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(5) val fileSize: Long = 0L, + @SerialId(6) val fileName: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(7) val srcTerm: Int = 0, + @SerialId(8) val platformType: Int = 0, + @SerialId(9) val buType: Int = 0, + @SerialId(10) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(11) val innerIp: Int = 0, + @SerialId(12) val voiceLength: Int = 0, + @SerialId(13) val boolNewUpChan: Boolean = false, + @SerialId(14) val codec: Int = 0, + @SerialId(15) val voiceType: Int = 0, + @SerialId(16) val buId: Int = 0 + ) : ProtoBuf + + @Serializable + class TryUpPttRsp( + @SerialId(1) val fileId: Long = 0L, + @SerialId(2) val result: Int = 0, + @SerialId(3) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(4) val boolFileExit: Boolean = false, + @SerialId(5) val uint32UpIp: List? = null, + @SerialId(6) val uint32UpPort: List? = null, + @SerialId(7) val upUkey: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(8) val fileid: Long = 0L, + @SerialId(9) val upOffset: Long = 0L, + @SerialId(10) val blockSize: Long = 0L, + @SerialId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(12) val channelType: Int = 0, + @SerialId(26) val msgUpIp6: List? = null, + @SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf +} \ No newline at end of file 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 new file mode 100644 index 000000000..c75f8a867 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt @@ -0,0 +1,454 @@ +package net.mamoe.mirai.qqandroid.network.protocol.data.proto + +import kotlinx.serialization.SerialId +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumberType +import kotlinx.serialization.protobuf.ProtoType +import net.mamoe.mirai.qqandroid.io.ProtoBuf +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY + +@Serializable +class BdhExtinfo : ProtoBuf { + @Serializable + class CommFileExtReq( + @SerialId(1) val actionType: Int = 0, + @SerialId(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class CommFileExtRsp( + @SerialId(1) val int32Retcode: Int = 0, + @SerialId(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class PicInfo( + @SerialId(1) val idx: Int = 0, + @SerialId(2) val size: Int = 0, + @SerialId(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(4) val type: Int = 0 + ) : ProtoBuf + + @Serializable + class QQVoiceExtReq( + @SerialId(1) val qid: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val fmt: Int = 0, + @SerialId(3) val rate: Int = 0, + @SerialId(4) val bits: Int = 0, + @SerialId(5) val channel: Int = 0, + @SerialId(6) val pinyin: Int = 0 + ) : ProtoBuf + + @Serializable + class QQVoiceExtRsp( + @SerialId(1) val qid: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val int32Retcode: Int = 0, + @SerialId(3) val msgResult: List? = null + ) : ProtoBuf + + @Serializable + class QQVoiceResult( + @SerialId(1) val text: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val pinyin: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val source: Int = 0 + ) : ProtoBuf + + @Serializable + class ShortVideoReqExtInfo( + @SerialId(1) val cmd: Int = 0, + @SerialId(2) val sessionId: Long = 0L, + @SerialId(3) val msgThumbinfo: PicInfo? = null, + @SerialId(4) val msgVideoinfo: VideoInfo? = null, + @SerialId(5) val msgShortvideoSureReq: ShortVideoSureReqInfo? = null, + @SerialId(6) val boolIsMergeCmdBeforeData: Boolean = false + ) : ProtoBuf + + @Serializable + class ShortVideoRspExtInfo( + @SerialId(1) val cmd: Int = 0, + @SerialId(2) val sessionId: Long = 0L, + @SerialId(3) val int32Retcode: Int = 0, + @SerialId(4) val errinfo: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(5) val msgThumbinfo: PicInfo? = null, + @SerialId(6) val msgVideoinfo: VideoInfo? = null, + @SerialId(7) val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null, + @SerialId(8) val retryFlag: Int = 0 + ) : ProtoBuf + + @Serializable + class ShortVideoSureReqInfo( + @SerialId(1) val fromuin: Long = 0L, + @SerialId(2) val chatType: Int = 0, + @SerialId(3) val touin: Long = 0L, + @SerialId(4) val groupCode: Long = 0L, + @SerialId(5) val clientType: Int = 0, + @SerialId(6) val msgThumbinfo: PicInfo? = null, + @SerialId(7) val msgMergeVideoinfo: List? = null, + @SerialId(8) val msgDropVideoinfo: List? = null, + @SerialId(9) val businessType: Int = 0, + @SerialId(10) val subBusinessType: Int = 0 + ) : ProtoBuf + + @Serializable + class ShortVideoSureRspInfo( + @SerialId(1) val fileid: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val msgVideoinfo: VideoInfo? = null, + @SerialId(4) val mergeCost: Int = 0 + ) : ProtoBuf + + @Serializable + class StoryVideoExtReq : ProtoBuf + + @Serializable + class StoryVideoExtRsp( + @SerialId(1) val int32Retcode: Int = 0, + @SerialId(2) val msg: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(4) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(5) val fileId: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class UploadPicExtInfo( + @SerialId(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class VideoInfo( + @SerialId(1) val idx: Int = 0, + @SerialId(2) val size: Int = 0, + @SerialId(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(4) val format: Int = 0, + @SerialId(5) val resLen: Int = 0, + @SerialId(6) val resWidth: Int = 0, + @SerialId(7) val time: Int = 0, + @SerialId(8) val starttime: Long = 0L, + @SerialId(9) val isAudio: Int = 0 + ) : ProtoBuf +} + +@Serializable +class CSDataHighwayHead : ProtoBuf { + @Serializable + class C2CCommonExtendinfo( + @SerialId(1) val infoId: Int = 0, + @SerialId(2) val msgFilterExtendinfo: FilterExtendinfo? = null + ) : ProtoBuf + + @Serializable + class DataHighwayHead( + @SerialId(1) val version: Int = 0, + @SerialId(2) val uin: String = "", + @SerialId(3) val command: String = "", + @SerialId(4) val seq: Int = 0, + @SerialId(5) val retryTimes: Int = 0, + @SerialId(6) val appid: Int = 0, + @SerialId(7) val dataflag: Int = 0, + @SerialId(8) val commandId: Int = 0, + @SerialId(9) val buildVer: String = "", + @SerialId(10) val localeId: Int = 0 + ) : ProtoBuf + + @Serializable + class DataHole( + @SerialId(1) val begin: Long = 0L, + @SerialId(2) val end: Long = 0L + ) : ProtoBuf + + @Serializable + class FilterExtendinfo( + @SerialId(1) val filterFlag: Int = 0, + @SerialId(2) val msgImageFilterRequest: ImageFilterRequest? = null + ) : ProtoBuf + + @Serializable + class FilterStyle( + @SerialId(1) val styleId: Int = 0, + @SerialId(2) val styleName: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class ImageFilterRequest( + @SerialId(1) val sessionId: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val clientIp: Int = 0, + @SerialId(3) val uin: Long = 0L, + @SerialId(4) val style: FilterStyle? = null, + @SerialId(5) val width: Int = 0, + @SerialId(6) val height: Int = 0, + @SerialId(7) val imageData: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class ImageFilterResponse( + @SerialId(1) val retCode: Int = 0, + @SerialId(2) val imageData: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val costTime: Int = 0 + ) : ProtoBuf + + @Serializable + class LoginSigHead( + @SerialId(1) val loginsigType: Int = 0, + @SerialId(2) val loginsig: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class NewServiceTicket( + @SerialId(1) val signature: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class PicInfoExt( + @SerialId(1) val picWidth: Int = 0, + @SerialId(2) val picHeight: Int = 0, + @SerialId(3) val picFlag: Int = 0, + @SerialId(4) val busiType: Int = 0, + @SerialId(5) val srcTerm: Int = 0, + @SerialId(6) val platType: Int = 0, + @SerialId(7) val netType: Int = 0, + @SerialId(8) val imgType: Int = 0, + @SerialId(9) val appPicType: Int = 0 + ) : ProtoBuf + + @Serializable + class PicRspExtInfo( + @SerialId(1) val skey: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val clientIp: Int = 0, + @SerialId(3) val upOffset: Long = 0L, + @SerialId(4) val blockSize: Long = 0L + ) : ProtoBuf + + @Serializable + class QueryHoleRsp( + @SerialId(1) val result: Int = 0, + @SerialId(2) val dataHole: List? = null, + @SerialId(3) val boolCompFlag: Boolean = false + ) : ProtoBuf + + @Serializable + class ReqDataHighwayHead( + @SerialId(1) val msgBasehead: DataHighwayHead? = null, + @SerialId(2) val msgSeghead: SegHead? = null, + @SerialId(3) val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(4) val timestamp: Long = 0L, + @SerialId(5) val msgLoginSigHead: LoginSigHead? = null + ) : ProtoBuf + + @Serializable + class RspBody( + @SerialId(1) val msgQueryHoleRsp: QueryHoleRsp? = null + ) : ProtoBuf + + @Serializable + class RspDataHighwayHead( + @SerialId(1) val msgBasehead: DataHighwayHead? = null, + @SerialId(2) val msgSeghead: SegHead? = null, + @SerialId(3) val errorCode: Int = 0, + @SerialId(4) val allowRetry: Int = 0, + @SerialId(5) val cachecost: Int = 0, + @SerialId(6) val htcost: Int = 0, + @SerialId(7) val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(8) val timestamp: Long = 0L, + @SerialId(9) val range: Long = 0L, + @SerialId(10) val isReset: Int = 0 + ) : ProtoBuf + + @Serializable + class SegHead( + @SerialId(1) val serviceid: Int = 0, + @SerialId(2) val filesize: Long = 0L, + @SerialId(3) val dataoffset: Long = 0L, + @SerialId(4) val datalength: Int = 0, + @SerialId(5) val rtcode: Int = 0, + @SerialId(6) val serviceticket: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(7) val flag: Int = 0, + @SerialId(8) val md5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(9) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(10) val cacheAddr: Int = 0, + @SerialId(11) val queryTimes: Int = 0, + @SerialId(12) val updateCacheip: Int = 0 + ) : ProtoBuf +} + +@Serializable +class HwConfigPersistentPB : ProtoBuf { + @Serializable + class HwConfigItemPB( + @SerialId(1) val ingKey: String = "", + @SerialId(2) val endPointList: List? = null + ) : ProtoBuf + + @Serializable + class HwConfigPB( + @SerialId(1) val configItemList: List? = null, + @SerialId(2) val netSegConfList: List? = null, + @SerialId(3) val shortVideoNetConf: List? = null, + @SerialId(4) val configItemListIp6: List? = null + ) : ProtoBuf + + @Serializable + class HwEndPointPB( + @SerialId(1) val ingHost: String = "", + @SerialId(2) val int32Port: Int = 0, + @SerialId(3) val int64Timestampe: Long = 0L + ) : ProtoBuf + + @Serializable + class HwNetSegConfPB( + @SerialId(1) val int64NetType: Long = 0L, + @SerialId(2) val int64SegSize: Long = 0L, + @SerialId(3) val int64SegNum: Long = 0L, + @SerialId(4) val int64CurConnNum: Long = 0L + ) : ProtoBuf +} + +@Serializable +class HwSessionInfoPersistentPB : ProtoBuf { + @Serializable + class HwSessionInfoPB( + @SerialId(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf +} + +@Serializable +class Subcmd0x501 : ProtoBuf { + @Serializable + class ReqBody( + @SerialId(1281) val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null + ) : ProtoBuf + + @Serializable + class RspBody( + @SerialId(1281) val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null + ) : ProtoBuf + + @Serializable + class SubCmd0x501ReqBody( + @SerialId(1) val uin: Long = 0L, + @SerialId(2) val idcId: Int = 0, + @SerialId(3) val appid: Int = 0, + @SerialId(4) val loginSigType: Int = 0, + @SerialId(5) val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(6) val requestFlag: Int = 0, + @SerialId(7) val uint32ServiceTypes: List? = null, + @SerialId(8) val bid: Int = 0, + @SerialId(9) val term: Int = 0, + @SerialId(10) val plat: Int = 0, + @SerialId(11) val net: Int = 0, + @SerialId(12) val caller: Int = 0 + ) : ProtoBuf + + @Serializable + class SubCmd0x501Rspbody( + @SerialId(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val msgHttpconnAddrs: List? = null, + @SerialId(4) val preConnection: Int = 0, + @SerialId(5) val csConn: Int = 0, + @SerialId(6) val msgIpLearnConf: IpLearnConf? = null, + @SerialId(7) val msgDynTimeoutConf: DynTimeOutConf? = null, + @SerialId(8) val msgOpenUpConf: OpenUpConf? = null, + @SerialId(9) val msgDownloadEncryptConf: DownloadEncryptConf? = null, + @SerialId(10) val msgShortVideoConf: ShortVideoConf? = null, + @SerialId(11) val msgPtvConf: PTVConf? = null + ) : ProtoBuf { + @Serializable + class DownloadEncryptConf( + @SerialId(1) val boolEnableEncryptRequest: Boolean = false, + @SerialId(2) val boolEnableEncryptedPic: Boolean = false, + @SerialId(3) val ctrlFlag: Int = 0 + ) : ProtoBuf + + @Serializable + class DynTimeOutConf( + @SerialId(1) val tbase2g: Int = 0, + @SerialId(2) val tbase3g: Int = 0, + @SerialId(3) val tbase4g: Int = 0, + @SerialId(4) val tbaseWifi: Int = 0, + @SerialId(5) val torg2g: Int = 0, + @SerialId(6) val torg3g: Int = 0, + @SerialId(7) val torg4g: Int = 0, + @SerialId(8) val torgWifi: Int = 0, + @SerialId(9) val maxTimeout: Int = 0, + @SerialId(10) val enableDynTimeout: Int = 0, + @SerialId(11) val maxTimeout2g: Int = 0, + @SerialId(12) val maxTimeout3g: Int = 0, + @SerialId(13) val maxTimeout4g: Int = 0, + @SerialId(14) val maxTimeoutWifi: Int = 0, + @SerialId(15) val hbTimeout2g: Int = 0, + @SerialId(16) val hbTimeout3g: Int = 0, + @SerialId(17) val hbTimeout4g: Int = 0, + @SerialId(18) val hbTimeoutWifi: Int = 0, + @SerialId(19) val hbTimeoutDefault: Int = 0 + ) : ProtoBuf + + @Serializable + class Ip6Addr( + @SerialId(1) val type: Int = 0, + @SerialId(2) val ip6: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val port: Int = 0, + @SerialId(4) val area: Int = 0, + @SerialId(5) val sameIsp: Int = 0 + ) : ProtoBuf + + @Serializable + class IpAddr( + @SerialId(1) val type: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @SerialId(2) val ip: Int = 0, + @SerialId(3) val port: Int = 0, + @SerialId(4) val area: Int = 0, + @SerialId(5) val sameIsp: Int = 0 + ) : ProtoBuf + + @Serializable + class IpLearnConf( + @SerialId(1) val refreshCachedIp: Int = 0, + @SerialId(2) val enableIpLearn: Int = 0 + ) : ProtoBuf + + @Serializable + class NetSegConf( + @SerialId(1) val netType: Int = 0, + @SerialId(2) val segsize: Int = 0, + @SerialId(3) val segnum: Int = 0, + @SerialId(4) val curconnnum: Int = 0 + ) : ProtoBuf + + @Serializable + class OpenUpConf( + @SerialId(1) val boolEnableOpenup: Boolean = false, + @SerialId(2) val preSendSegnum: Int = 0, + @SerialId(3) val preSendSegnum3g: Int = 0, + @SerialId(4) val preSendSegnum4g: Int = 0, + @SerialId(5) val preSendSegnumWifi: Int = 0 + ) : ProtoBuf + + @Serializable + class PTVConf( + @SerialId(1) val channelType: Int = 0, + @SerialId(2) val msgNetsegconf: List? = null, + @SerialId(3) val boolOpenHardwareCodec: Boolean = false + ) : ProtoBuf + + @Serializable + class ShortVideoConf( + @SerialId(1) val channelType: Int = 0, + @SerialId(2) val msgNetsegconf: List? = null, + @SerialId(3) val boolOpenHardwareCodec: Boolean = false, + @SerialId(4) val boolSendAheadSignal: Boolean = false + ) : ProtoBuf + + @Serializable + class SrvAddrs( + @SerialId(1) val serviceType: Int = 0, + @SerialId(2) val msgAddrs: List? = null, + @SerialId(3) val fragmentSize: Int = 0, + @SerialId(4) val msgNetsegconf: List? = null, + @SerialId(5) val msgAddrsV6: 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 4f80d1114..a72854159 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 @@ -5,6 +5,9 @@ import kotlinx.io.pool.useInstance import net.mamoe.mirai.data.Packet import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImageUpPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList @@ -117,7 +120,10 @@ internal object KnownPacketFactories { MessageSvc.PbSendMsg, FriendList.GetFriendGroupList, FriendList.GetTroopListSimplify, - FriendList.GetTroopMemberList + FriendList.GetTroopMemberList, + ImgStore.GroupPicUp, + ImageUpPacket, + LongConn.OffPicDown ) object IncomingFactories : List> by mutableListOf( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageDownPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageDownPacket.kt deleted file mode 100644 index 118c27470..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageDownPacket.kt +++ /dev/null @@ -1,42 +0,0 @@ -package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image - -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.writeFully -import net.mamoe.mirai.data.Packet -import net.mamoe.mirai.qqandroid.QQAndroidBot -import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport -import net.mamoe.mirai.qqandroid.network.QQAndroidClient -import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352Packet -import net.mamoe.mirai.qqandroid.network.protocol.data.proto.GetImgUrlReq -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.buildLoginOutgoingPacket -import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket - -internal object ImageDownPacket : OutgoingPacketFactory("LongConn.OffPicDown") { - - operator fun invoke(client: QQAndroidClient, req: GetImgUrlReq): OutgoingPacket { - // TODO: 2020/1/24 测试: bodyType, subAppId - return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) { - writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) { - val data = ProtoBufWithNullableSupport.dump( - Cmd0x352Packet.serializer(), - Cmd0x352Packet.createByImageRequest(req) - ) - writeInt(data.size + 4) - writeFully(data) - } - } - } - - override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ImageDownPacketResponse { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - - sealed class ImageDownPacketResponse : Packet { - object Success : ImageDownPacketResponse() - } - - -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageUpPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageUpPacket.kt index 6172e3915..803ee9c34 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageUpPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageUpPacket.kt @@ -10,22 +10,19 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352Packet import net.mamoe.mirai.qqandroid.network.protocol.data.proto.UploadImgReq 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.buildLoginOutgoingPacket -import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket internal object ImageUpPacket : OutgoingPacketFactory("LongConn.OffPicUp") { operator fun invoke(client: QQAndroidClient, req: UploadImgReq): OutgoingPacket { // TODO: 2020/1/24 测试: bodyType, subAppId - return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) { - writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) { - val data = ProtoBufWithNullableSupport.dump( - Cmd0x352Packet.serializer(), - Cmd0x352Packet.createByImageRequest(req) - ) - writeInt(data.size + 4) - writeFully(data) - } + return buildOutgoingUniPacket(client) { + val data = ProtoBufWithNullableSupport.dump( + Cmd0x352Packet.serializer(), + Cmd0x352Packet.createByImageRequest(req) + ) + writeInt(data.size + 4) + writeFully(data) } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt new file mode 100644 index 000000000..fc6e85e99 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt @@ -0,0 +1,101 @@ +package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image + +import io.ktor.client.HttpClient +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.data.Packet +import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf +import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf +import net.mamoe.mirai.qqandroid.network.QQAndroidClient +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x388 +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 + +internal class ImgStore { + object GroupPicUp : OutgoingPacketFactory("ImgStore.GroupPicUp") { + + operator fun invoke( + client: QQAndroidClient, + uin: Long, + groupCode: Long, + md5: ByteArray, + size: Long, + picWidth: Int, + picHeight: Int, + picType: Int = 1000, + fileId: Long = 0, + filename: String, + srcTerm: Int = 5, + platformType: Int = 9, + buType: Int = 1, + appPicType: Int = 1006, + originalPic: Int = 0 + ): OutgoingPacket = buildOutgoingUniPacket(client) { + writeProtoBuf( + Cmd0x388.ReqBody.serializer(), + Cmd0x388.ReqBody( + netType = 3, // wifi + subcmd = 1, + msgTryupImgReq = listOf( + Cmd0x388.TryUpImgReq( + groupCode = groupCode, + srcUin = uin, + fileMd5 = md5, + fileSize = size, + fileId = fileId, + fileName = filename, + picWidth = picWidth, + picHeight = picHeight, + picType = picType, + appPicType = appPicType, + buildVer = client.buildVer, + srcTerm = srcTerm, + platformType = platformType, + originalPic = originalPic, + buType = buType + ) + ) + ) + ) + } + + sealed class Response : Packet { + class FileExists( + val fileId: Long, + val fileInfo: Cmd0x388.ImgInfo + ) : Response() { + override fun toString(): String { + return "FileExists(fileId=$fileId, fileInfo=$fileInfo)" + } + } + + class RequireUpload( + val fileId: Long, + val uKey: ByteArray, + val uploadIpList: List, + val uploadPortList: List + ) : Response() { + override fun toString(): String { + return "RequireUpload(fileId=$fileId, uKey=${uKey.contentToString()})" + } + } + + data class Failed( + val resultCode: Int, + val message: String + ) : Response() + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val resp0 = readProtoBuf(Cmd0x388.RspBody.serializer()) + resp0.msgTryupImgRsp ?: error("cannot find `msgTryupImgRsp` from `Cmd0x388.RspBody`") + val resp = resp0.msgTryupImgRsp.first() + return when { + resp.result != 0 -> Response.Failed(resultCode = resp.result, message = resp.failMsg) + resp.boolFileExit -> Response.FileExists(fileId = resp.fileid, fileInfo = resp.msgImgInfo!!) + else -> Response.RequireUpload(fileId = resp.fileid, uKey = resp.upUkey, uploadIpList = resp.uint32UpIp!!, uploadPortList = resp.uint32UpPort!!) + } + } + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/LongConn.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/LongConn.kt new file mode 100644 index 000000000..69d1ace68 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/LongConn.kt @@ -0,0 +1,42 @@ +package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image + +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.writeFully +import net.mamoe.mirai.data.Packet +import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport +import net.mamoe.mirai.qqandroid.network.QQAndroidClient +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352Packet +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.GetImgUrlReq +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.buildLoginOutgoingPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket + +internal class LongConn { + + object OffPicDown : OutgoingPacketFactory("LongConn.OffPicDown"){ + operator fun invoke(client: QQAndroidClient, req: GetImgUrlReq): OutgoingPacket { + // TODO: 2020/1/24 测试: bodyType, subAppId + return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) { + writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) { + val data = ProtoBufWithNullableSupport.dump( + Cmd0x352Packet.serializer(), + Cmd0x352Packet.createByImageRequest(req) + ) + writeInt(data.size + 4) + writeFully(data) + } + } + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ImageDownPacketResponse { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + + sealed class ImageDownPacketResponse : Packet { + object Success : ImageDownPacketResponse() + } + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt index fa20540c9..2b87c2b3b 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt @@ -6,7 +6,9 @@ import java.io.File fun main() { println( - File("""/Users/jiahua.liu/Desktop/QQAndroid-F/app/src/main/java/tencent/im/s2c/msgtype0x210/submsgtype0xc7/bussinfo/mutualmark""") + File(""" +E:\Projects\QQAndroidFF\app\src\main\java\com\tencent\mobileqq\highway\protocol + """.trimIndent()) .generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n") ) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt index 89976370a..1e524b609 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -31,8 +31,6 @@ interface Group : Contact, CoroutineScope { /** * 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新 - * - * **注意**: 获得的列表仅为这一时刻的成员列表的镜像. 它将不会被更新 */ val members: ContactList diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt index a0d5d2617..8824c85c5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt @@ -7,18 +7,10 @@ import kotlinx.io.core.Input import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.sendTo import net.mamoe.mirai.utils.io.toUHexString -@Suppress("FunctionName") -fun ExternalImage( - width: Int, - height: Int, - md5: ByteArray, - format: String, - data: ByteReadPacket -): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining) - /** * 外部图片. 图片数据还没有读取到内存. * @@ -33,19 +25,53 @@ class ExternalImage( val md5: ByteArray, imageFormat: String, val input: Input, - val inputSize: Long + val inputSize: Long, + val filename: String ) { - private val format: String - - init { - if (imageFormat == "JPEG" || imageFormat == "jpeg") {//必须转换 - this.format = "jpg" - } else { - this.format = imageFormat - } + companion object { + operator fun invoke( + width: Int, + height: Int, + md5: ByteArray, + format: String, + data: ByteReadPacket, + filename: String + ): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining, filename) } + private val format: String = when (val it =imageFormat.toLowerCase()) { + "jpeg" -> "jpg" //必须转换 + else -> it + } + + /** + * + * ImgType: + * JPG: 1000 + * PNG: 1001 + * WEBP: 1002 + * BMP: 1005 + * GIG: 2000 + * APNG: 2001 + * SHARPP: 1004 + */ + val imageType: Int + get() = when (format){ + "jpg" -> 1000 + "png" -> 1001 + "webp" -> 1002 + "bmp" -> 1005 + "gig" -> 2000 + "apng" -> 2001 + "sharpp" -> 1004 + else -> 1000 // unsupported, just make it jpg + } + override fun toString(): String = "[ExternalImage(${width}x$height $format)]" + + fun calculateImageResourceId(): String { + return "{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format" + } } /** diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt index ddeccb0d1..d254520e9 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt @@ -12,6 +12,7 @@ import kotlinx.io.core.copyTo import kotlinx.io.errors.IOException import kotlinx.io.streams.asInput import kotlinx.io.streams.asOutput +import net.mamoe.mirai.utils.io.getRandomString import java.awt.image.BufferedImage import java.io.File import java.io.InputStream @@ -44,7 +45,7 @@ fun BufferedImage.toExternalImage(formatName: String = "gif"): ExternalImage { }) } - return ExternalImage(width, height, digest.digest(), formatName, buffer) + return ExternalImage(width, height, digest.digest(), formatName, buffer, getRandomString(10) + "." + formatName) } suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } @@ -66,7 +67,8 @@ fun File.toExternalImage(): ExternalImage { md5 = this.inputStream().use { it.md5() }, imageFormat = image.formatName, input = this.inputStream().asInput(IoBuffer.Pool), - inputSize = this.length() + inputSize = this.length(), + filename = this.name ) }