From 28149dfed2f42e0d1f41edaefe74b209192d0fa7 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Mon, 28 Oct 2019 22:01:49 +0800 Subject: [PATCH] Combine image packets into the same file --- .../tim/packet/action/UploadFriendImage.kt | 350 --------------- .../tim/packet/action/UploadGroupImage.kt | 331 -------------- .../protocol/tim/packet/action/UploadImage.kt | 424 ++++++++++++++++++ 3 files changed, 424 insertions(+), 681 deletions(-) delete mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadFriendImage.kt delete mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadGroupImage.kt create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadImage.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadFriendImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadFriendImage.kt deleted file mode 100644 index ce2e8817a..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadFriendImage.kt +++ /dev/null @@ -1,350 +0,0 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused") - -package net.mamoe.mirai.network.protocol.tim.packet.action - -import kotlinx.io.core.* -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.message.ImageId -import net.mamoe.mirai.network.protocol.tim.TIMProtocol -import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket -import net.mamoe.mirai.network.protocol.tim.packet.PacketId -import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion -import net.mamoe.mirai.network.protocol.tim.packet.ResponsePacket -import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket.Response.State.* -import net.mamoe.mirai.network.qqAccount -import net.mamoe.mirai.qqAccount -import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.httpPostFriendImage -import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.readUnsignedVarInt -import net.mamoe.mirai.utils.writeUVarInt -import net.mamoe.mirai.withSession - -/** - * 上传图片 - * 挂起直到上传完成或失败 - * - * 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数 - * - * @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时 - */ -suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession { - FriendImageIdRequestPacket(qqAccount, sessionKey, id, image) - .sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> { - when (it.state) { - REQUIRE_UPLOAD -> { - require( - httpPostFriendImage( - botAccount = bot.qqAccount, - uKeyHex = it.uKey!!.toUHexString(""), - imageInput = image.input, - inputSize = image.inputSize - ) - ) - } - - ALREADY_EXISTS -> { - - } - - OVER_FILE_SIZE_MAX -> { - throw OverFileSizeMaxException() - } - } - - it.imageId!! - }.await() -} - -//fixVer2=00 00 00 01 2E 01 00 00 69 35 -//01 [3E 03 3F A2] [76 E4 B8 DD] 00 00 50 7A 00 0A 00 01 00 01 00 2D 55 73 65 72 44 61 74 61 49 6D 61 67 65 3A 43 32 43 5C 48 31 30 50 60 35 29 24 52 7D 57 45 56 54 41 4B 52 24 45 4E 54 45 58 2E 70 6E 67 -// 00 00 00 F2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01 - -//01 3E 03 3F A2 76 E4 B8 DD 00 00 50 7B 00 0A 00 01 00 01 00 5E 4F 53 52 6F 6F 74 3A 43 3A 5C 55 73 65 72 73 5C 48 69 6D 31 38 5C 44 6F 63 75 6D 65 6E 74 73 5C 54 65 6E 63 65 6E 74 20 46 69 6C 65 73 5C 31 30 34 30 34 30 30 32 39 30 5C 49 6D 61 67 65 5C 43 32 43 5C 4E 41 4B 60 52 52 4E 24 49 24 24 4B 44 24 34 5B 5B 45 4E 24 4D 4A 30 2E 6A 70 67 -// 00 00 06 99 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01 - -//01 3E 03 3F A2 76 E4 B8 DD 00 00 50 7C 00 0A 00 01 00 01 00 2D 55 73 65 72 44 61 74 61 49 6D 61 67 65 3A 43 32 43 5C 40 53 51 25 4F 46 43 50 36 4C 48 30 47 34 43 47 57 53 49 52 46 37 32 2E 70 6E 67 -// 00 01 61 A7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01 -/** - * 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00 - */ -@PacketId(0X01_BDu) -@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") -class SubmitImageFilenamePacket( - private val bot: UInt, - private val target: UInt, - private val filename: String, - private val sessionKey: ByteArray -) : OutgoingPacket() { - override fun encode(builder: BytePacketBuilder) = with(builder) { - writeQQ(bot) - writeHex(TIMProtocol.fixVer2)//? - //writeHex("04 00 00 00 01 2E 01 00 00 69 35") - - encryptAndWrite(sessionKey) { - writeByte(0x01) - writeQQ(bot) - writeQQ(target) - writeZero(2) - writeUByte(0x02u) - writeRandom(1) - writeHex("00 0A 00 01 00 01") - val name = "UserDataImage:$filename" - writeShort(name.length.toShort()) - writeStringUtf8(name) - writeHex("00 00") - writeRandom(2)//这个也与是哪个好友有关? - writeHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01")//35 02? 最后这个值是与是哪个好友有关 - - //this.debugPrintThis("SubmitImageFilenamePacket") - } - - //解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1A 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 28 5A 53 41 58 40 57 4B 52 4A 5A 31 7E 33 59 4F 53 53 4C 4D 32 4B 49 2E 6A 70 67 00 00 06 E2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02 - //解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1B 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 28 5A 53 41 58 40 57 4B 52 4A 5A 31 7E 33 59 4F 53 53 4C 4D 32 4B 49 2E 6A 70 67 00 00 06 E2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02 - //解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1C 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 29 37 42 53 4B 48 32 44 35 54 51 28 5A 35 7D 35 24 56 5D 32 35 49 4E 2E 6A 70 67 00 00 03 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02 - } - - @PacketId(0x01_BDu) - @PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173") - class Response(input: ByteReadPacket) : ResponsePacket(input) { - override fun decode() = with(input) { - require(readBytes().contentEquals(expecting)) - } - - companion object { - private val expecting = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00) - } - } -} - - -/** - * 请求上传图片. 将发送图片的 md5, size, width, height. - * 服务器返回以下之一: - * - 服务器已经存有这个图片 - * - 服务器未存有, 返回一个 key 用于客户端上传 - */ -@PacketId(0x03_52u) -@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") -class FriendImageIdRequestPacket( - private val bot: UInt, - private val sessionKey: ByteArray, - private val target: UInt, - private val image: ExternalImage -) : OutgoingPacket() { - - //00 00 00 07 00 00 00 4B 08 01 12 03 98 01 01 08 01 12 47 08 A2 FF 8C F0 03 10 89 FC A6 8C 0B 18 00 22 10 2B 23 D7 05 CA D1 F2 CF 37 10 FE 58 26 92 FC C4 28 FD 08 32 1A 7B 00 47 00 47 00 42 00 7E 00 49 00 31 00 5A 00 4D 00 43 00 28 00 25 00 49 00 38 01 48 00 70 42 78 42 - - override fun encode(builder: BytePacketBuilder) = with(builder) { - writeQQ(bot) - //04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00 - writeHex("04 00 00 00 01 2E 01 00 00 69 35 00 00 00 00 00 00 00 00") - - encryptAndWrite(sessionKey) { - //好友图片 - // 00 00 00 - // 07 00 - // 00 00 - - // proto - - // [4D 08]后文长度 - // 01 12 - // 03 98 - // 01 01 - // 08 01 - // 12 49 - // 08 [A2 FF 8C F0 03](1040400290 varint) - // 10 [DD F1 92 B7 07](1994701021 varint) - // 18 00 - // 22 [10](=16) [E9 BA 47 2E 36 ED D4 BF 8C 4F E5 6A CB A0 2D 5E](md5) - // 28 [CE 0E](1870 varint) - // 32 1A - // 39 00 - // 51 00 - // 24 00 - // 32 00 - // 4A 00 - // 53 00 - // 25 00 - // 4C 00 - // 56 00 - // 42 00 - // 33 00 - // 44 00 - // 44 00 - // 38 01 - // 48 00 - // 70 [92 03](402 varint) - // 78 [E3 01](227 varint) - - //好友图片 - /* - * 00 00 00 07 00 00 00 - * [4E 08]后文长度 - * 01 12 - * 03 98 - * 01 01 - * 08 01 - * 12 4A - * 08 [A2 FF 8C F0 03](varint) - * 10 [DD F1 92 B7 07](varint) - * 18 00//24 - * 22 10 72 02 57 44 84 1D 83 FC C0 85 A1 E9 10 AA 9C 2C - * 28 [BD D9 19](421053 varint) - * 32 1A//48 - * 49 00 - * 49 00 - * 25 00 - * 45 00 - * 5D 00 - * 50 00 - * 41 00 - * 7D 00 - * 4F 00 - * 56 00 - * 46 00 - * 4B 00 - * 5D 00 - * 38 01 - * 48 00//78 - * - * - * 70 [80 14] - * 78 [A0 0B]//84 - */ - writeHex("00 00 00 07 00 00 00") - - //proto - writeUVarintLVPacket(lengthOffset = { it - 7 }) { - writeUByte(0x08u) - writeUShort(0x01_12u) - writeUShort(0x03_98u) - writeUShort(0x01_01u) - writeUShort(0x08_01u) - - - writeUVarintLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) { - writeUByte(0x08u) - writeUVarInt(bot) - - writeUByte(0x10u) - writeUVarInt(target) - - writeUShort(0x18_00u) - - writeUByte(0x22u) - writeUByte(0x10u) - writeFully(image.md5) - - writeUByte(0x28u) - writeUVarInt(image.inputSize.toUInt()) - - - writeUByte(0x32u) - //长度应为1A - writeUVarintLVPacket { - writeUShort(0x28_00u) - writeUShort(0x46_00u) - writeUShort(0x51_00u) - writeUShort(0x56_00u) - writeUShort(0x4B_00u) - writeUShort(0x41_00u) - writeUShort(0x49_00u) - writeUShort(0x25_00u) - writeUShort(0x4B_00u) - writeUShort(0x24_00u) - writeUShort(0x55_00u) - writeUShort(0x30_00u) - writeUShort(0x24_00u) - } - - writeUShort(0x38_01u) - writeUShort(0x48_00u) - - writeUByte(0x70u) - writeUVarInt(image.width.toUInt()) - writeUByte(0x78u) - writeUVarInt(image.height.toUInt()) - } - } - - - //println(this.build().readBytes().toUHexString()) - } - } - - @PacketId(0x0352u) - @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") - class Response(input: ByteReadPacket) : ResponsePacket(input) { - /** - * 访问 HTTP API 时需要使用的一个 key. 128 位 - */ - var uKey: ByteArray? = null - - /** - * 发送消息时使用的 id - */ - var imageId: ImageId? = null - - lateinit var state: State - - enum class State { - /** - * 需要上传. 此时 [uKey], [imageId] 均不为 `null` - */ - REQUIRE_UPLOAD, - /** - * 服务器已有这个图片. 此时 [uKey] 为 `null`, [imageId] 不为 `null` - */ - ALREADY_EXISTS, - /** - * 图片过大. 此时 [uKey], [imageId] 均为 `null` - */ - OVER_FILE_SIZE_MAX, - } - - override fun decode() = with(input) { - //00 00 00 08 00 00 - //01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 B3 90 73 32 C0 5D 72 4B 18 AC 16 8A 23 92 21 3C E6 FD 51 33 CE C4 84 1C 4C 7B A2 E5 27 65 1C 99 EE D6 D4 D2 0D B8 10 2D 88 7E 13 71 75 09 36 46 0F BA 87 B3 EA 54 B2 2B 18 8F F3 5A 9D 55 C6 3B E4 CB 9E B3 69 79 E2 51 61 98 1B 04 49 76 58 29 75 E3 73 56 4B 89 A4 54 A2 E1 0C 17 72 8D 77 EA CD CF 9E 68 B7 01 65 7B F1 E3 B7 FC 04 0C F4 D8 8D B3 51 1B B2 4C 14 59 DE FA 0D 64 BD 50 2E ED 52 25 2F 62 63 66 38 63 39 39 65 2D 30 65 63 35 2D 34 33 33 31 2D 62 37 30 61 2D 31 36 33 35 32 66 66 64 38 33 33 33 5A 25 2F 62 63 66 38 63 39 39 65 2D 30 65 63 35 2D 34 33 33 31 2D 62 37 30 61 2D 31 36 33 35 32 66 66 64 38 33 33 33 60 00 68 80 80 08 20 01 - - //00 00 00 08 00 00 01 0C 12 06 98 01 01 A0 01 00 08 01 12 85 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 B7 87 AC E7 0B 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 50 40 90 3F 40 BB 03 40 50 4A 80 01 F2 65 BC F3 E8 C6 F3 30 B1 85 72 86 C0 95 C0 A7 09 E3 84 AC A6 68 C3 AF BB A8 96 64 AA 18 92 96 F7 3C 7B F8 EA 03 C6 6A AD B7 94 BC 76 D4 36 84 25 76 CB DF 5B 7C E7 40 DF 5D FD DF 3D 93 23 96 5D 23 A8 B2 93 FA 21 BF 68 3E 0B 71 D2 9C FF F2 55 45 11 E2 23 2E D0 49 6E 4F 1F DB 18 28 22 68 45 C9 9E A7 F4 AD EF 20 93 55 EB 0E A3 33 7B 18 E8 7C 15 6F 19 26 2C 41 E9 E4 51 61 48 AA 2F EE 52 25 2F 65 39 61 63 62 63 65 39 2D 61 62 39 36 2D 34 30 30 66 2D 38 61 66 30 2D 32 63 34 64 39 37 31 31 32 33 36 62 5A 25 2F 65 39 61 63 62 63 65 39 2D 61 62 39 36 2D 34 30 30 66 2D 38 61 66 30 2D 32 63 34 64 39 37 31 31 32 33 36 62 60 00 68 80 80 08 20 01 - //00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 BB C8 E4 E2 0F 38 FB AE FA 9D 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 0E 26 8D 39 E7 88 22 74 EC 88 2B 04 C5 D1 3D D2 09 A4 2E 48 22 F5 91 51 D5 82 7A 43 9F 45 70 77 79 83 21 87 4E AA 63 6E 73 D5 D3 DA 5F FC 36 BA 97 31 74 49 D9 97 83 58 74 06 BE F2 00 83 CC B9 50 D0 C4 D1 63 33 5F AE EA 1C 99 2D 0D E7 A2 94 97 6E 18 92 86 2C C0 36 E9 D9 E3 82 01 A3 B9 AC F1 90 67 73 F3 3C 0B 26 4C C4 DE 20 AF 3D B3 20 F8 50 B4 0E 78 0E 0E 1E 8C 56 02 21 10 5B 61 39 52 25 2F 31 38 37 31 34 66 66 39 2D 61 30 39 39 2D 34 61 38 64 2D 38 34 39 62 2D 38 37 35 65 65 30 36 65 34 64 32 36 5A 25 2F 31 38 37 31 34 66 66 39 2D 61 30 39 39 2D 34 61 38 64 2D 38 34 39 62 2D 38 37 35 65 65 30 36 65 34 64 32 36 60 00 68 80 80 08 20 01 - discardExact(6) - if (readUByte() != UByte.MIN_VALUE) { - //服务器还没有这个图片 - - //00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 - // 4A [80 01] B5 29 1A 1B 0E 63 79 8B 34 B1 4E 2A 2A 9E 69 09 A7 69 F5 C6 4F 95 DA 96 A9 1B E3 CD 6F 3D 30 EE 59 C0 30 22 BF F0 2D 88 2D A7 6C B2 09 AD D6 CE E1 46 84 FC 7D 19 AF 1A 37 91 98 AD 2C 45 25 AA 17 2F 81 DC 5A 7F 30 F4 2D 73 E5 1C 8B 8A 23 85 42 9D 8D 5C 18 15 32 D1 CA A3 4D 01 7C 59 11 73 DA B6 09 C2 6D 58 35 EF 48 88 44 0F 2D 17 09 52 DF D4 EA A7 85 2F 27 CE DF A8 F5 9B CD C9 84 C2 // 52 [25] 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 5A 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 60 00 68 80 80 08 20 01 - - discardExact(60) - - discardExact(1)//4A, id - uKey = readBytes(readUnsignedVarInt().toInt())//128 - - discardExact(1)//52, id - imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37 - state = State.REQUIRE_UPLOAD - - //DebugLogger.logPurple("获得 uKey(${uKey!!.size})=${uKey!!.toUHexString()}") - //DebugLogger.logPurple("获得 imageId(${imageId!!.value.length})=${imageId}") - } else { - //服务器已经有这个图片了 - //DebugLogger.logPurple("服务器已有好友图片 ") - // 89 - // 12 06 98 01 01 A0 01 00 08 01 12 82 01 08 00 10 AB A7 89 D8 02 18 00 28 01 32 20 0A 10 5A 39 37 10 EA D5 B5 57 A8 04 14 70 CE 90 67 14 10 67 18 8A 94 17 20 ED 03 28 97 04 30 0A 52 25 2F 39 38 31 65 61 31 64 65 2D 62 32 31 33 2D 34 31 61 39 2D 38 38 37 65 2D 32 38 37 39 39 66 31 39 36 37 35 65 5A 25 2F 39 38 31 65 61 31 64 65 2D 62 32 31 33 2D 34 31 61 39 2D 38 38 37 65 2D 32 38 37 39 39 66 31 39 36 37 35 65 60 00 68 80 80 08 20 01 - - - //83 12 06 98 01 01 A0 01 00 08 01 12 7D 08 00 10 9B A4 DC 92 06 18 00 28 01 32 1B 0A 10 8E C4 9D 72 26 AE 20 C0 5D A2 B6 78 4D 12 B7 3A 10 00 18 86 1F 20 30 28 30 52 25 2F 30 31 62 - val toDiscard = readUByte().toInt() - 37 - if (toDiscard < 0) { - state = OVER_FILE_SIZE_MAX - } else { - discardExact(toDiscard) - imageId = ImageId(readString(37)) - state = ALREADY_EXISTS - } - } - } - } -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadGroupImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadGroupImage.kt deleted file mode 100644 index dd266b43b..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadGroupImage.kt +++ /dev/null @@ -1,331 +0,0 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") - -package net.mamoe.mirai.network.protocol.tim.packet.action - -import kotlinx.coroutines.withContext -import kotlinx.io.core.* -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.GroupId -import net.mamoe.mirai.contact.GroupInternalId -import net.mamoe.mirai.contact.withSession -import net.mamoe.mirai.message.ImageId -import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket -import net.mamoe.mirai.network.protocol.tim.packet.PacketId -import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion -import net.mamoe.mirai.network.protocol.tim.packet.ResponsePacket -import net.mamoe.mirai.qqAccount -import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.httpPostGroupImage -import net.mamoe.mirai.utils.io.* -import kotlin.coroutines.coroutineContext - - -/** - * 图片文件过大 - */ -class OverFileSizeMaxException : IllegalStateException() - -/** - * 上传群图片 - * 挂起直到上传完成或失败 - * - * 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数 - * - * @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时 - */ -suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession { - val userContext = coroutineContext - GroupImageIdRequestPacket(bot.qqAccount, internalId, image, sessionKey) - .sendAndExpect<GroupImageIdRequestPacket.Response, Unit> { - withContext(userContext) { - when (it.state) { - GroupImageIdRequestPacket.Response.State.REQUIRE_UPLOAD -> { - httpPostGroupImage( - botAccount = bot.qqAccount, - groupId = GroupId(id), - imageInput = image.input, - inputSize = image.inputSize, - uKeyHex = it.uKey!!.toUHexString("") - ) - } - - GroupImageIdRequestPacket.Response.State.ALREADY_EXISTS -> { - - } - - GroupImageIdRequestPacket.Response.State.OVER_FILE_SIZE_MAX -> throw OverFileSizeMaxException() - } - } - }.join() - image.groupImageId -} - -/** - * 获取 Image Id 和上传用的一个 uKey - */ -@PacketId(0x0388u) -@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") -class GroupImageIdRequestPacket( - private val bot: UInt, - private val groupInternalId: GroupInternalId, - private val image: ExternalImage, - private val sessionKey: ByteArray -) : OutgoingPacket() { - - override fun encode(builder: BytePacketBuilder) = with(builder) { - //未知图片A - // 00 00 00 07 00 00 00 - // 53 08 =后文长度-6 - // 01 12 03 98 01 02 10 02 22 4F 08 F3 DB F3 E3 01 10 A2 FF 8C F0 03 18 B1 C7 B1 BB 0A 22 10 77 FB 3D 6F 97 BD 7B F0 C4 1F DC 60 1F 22 D2 7C 28 04 30 02 38 20 40 FF 01 48 00 50 01 5A 05 32 36 39 33 33 60 00 68 00 70 00 78 00 80 01 A4 05 88 01 D8 03 90 01 EB 07 A0 01 01 - - //小图B - // 00 00 00 07 00 00 00 - // 5B =后文长度-7 - // 08 01 12 03 98 01 01 10 01 1A - // 57长度 - // 08 FB D2 D8 94 02 - // 10 A2 FF 8C F0 03 - // 18 00 - // 22 [10] 7A A4 B3 AA 8C 3C 0F 45 2D 9B 7F 30 2A 0A CE AA - // 28 F3 06//size - // 32 1A - // 29 00 - // 37 00 - // 42 00 - // 53 00 - // 4B 00 - // 48 00 - // 32 00 - // 44 00 - // 35 00 - // 54 00 - // 51 00 - // 28 00 - // 5A 00 - // 38 01 - // 48 01 - // 50 41 //宽度 - // 58 34 //高度 - // 60 04 - // 6A [05] 32 36 39 33 33 - // 70 00 - // 78 03 - // 80 01 00 - - //450*298 - //00 00 00 07 00 00 00 - // 5D=后文-7 varint - // 08 01 12 03 98 01 01 10 01 1A - // 59 =后文长度 varint - // 08 A0 89 F7 B6 03 - // 10 A2 FF 8C F0 03 - // 18 00 - // 22 10 01 FC 9D 6B E9 B2 D9 CD AC 25 66 73 F9 AF 6A 67 - // 28 [C9 10] varint size - // 32 1A - // 58 00 51 00 56 00 51 00 58 00 47 00 55 00 47 00 38 00 57 00 5F 00 4A 00 43 00 - // 38 01 48 01 - // 50 [C2 03] - // 58 [AA 02] - // 60 02 - // 6A 05 32 36 39 33 33 - // 70 00 - // 78 03 - // 80 01 - // 00 - - //大图C - // 00 00 00 07 00 00 00 - // 5E 08 =后文长度-6 - // 01 12 03 98 01 01 10 01 1A - // 5A长度 - // 08 A0 89 F7 B6 03 - // 10 A2 FF 8C F0 03 - // 18 00 - // 22 [10] F1 DD 65 4D A1 AB 66 B4 0F B5 27 B5 14 8E 73 B5 - // 28 96 83 08//size - // 32 1A - // 31 00 - // 35 00 - // 4C 00 - // 24 00 - // 40 00 - // 5B 00 - // 4D 00 - // 5B 00 - // 39 00 - // 39 00 - // 40 00 - // 57 00 - // 5D 00 - // 38 01 - // 48 01 - // 50 80 14 //宽度 - // 58 A0 0B //高度 - // 60 02 - // 6A [05] 32 36 39 33 33 - // 70 00 - // 78 03 - // 80 01 00 - - - //00 00 00 07 00 00 00 - // 5B 08 01 12 03 98 01 01 10 01 1A - // 57 - // 08 A0 89 F7 B6 03 - // 10 A2 FF 8C F0 03 - // 18 00 - // 22 10 39 F7 65 32 E1 AB 5C A7 86 D7 A5 13 89 22 53 85 - // 28 90 23 - // 32 1A - // 28 00 52 00 49 00 5F 00 36 00 31 00 28 00 32 00 52 00 59 00 4B 00 59 00 43 00 - // 38 01 - // 48 01 - // 50 2D - // 58 2D - // 60 03 - // 6A 05 32 36 39 33 33 - // 70 00 - // 78 03 - // 80 01 00 - writeQQ(bot) - writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00") - //writeHex(TIMProtocol.version0x02) - - encryptAndWrite(sessionKey) { - writeHex("00 00 00 07 00 00 00") - - writeUVarintLVPacket(lengthOffset = { it - 7 }) { - writeByte(0x08) - writeHex("01 12 03 98 01 01 10 01 1A") - - writeUVarintLVPacket(lengthOffset = { it }) { - writeTUVarint(0x08u, groupInternalId.value) - writeTUVarint(0x10u, bot) - writeTV(0x1800u) - - writeUByte(0x22u) - writeUByte(0x10u) - writeFully(image.md5) - - writeTUVarint(0x28u, image.inputSize.toUInt()) - writeUVarintLVPacket(tag = 0x32u) { - writeTV(0x5B_00u) - writeTV(0x40_00u) - writeTV(0x33_00u) - writeTV(0x48_00u) - writeTV(0x5F_00u) - writeTV(0x58_00u) - writeTV(0x46_00u) - writeTV(0x51_00u) - writeTV(0x45_00u) - writeTV(0x51_00u) - writeTV(0x40_00u) - writeTV(0x24_00u) - writeTV(0x4F_00u) - } - writeTV(0x38_01u) - writeTV(0x48_01u) - writeTUVarint(0x50u, image.width.toUInt()) - writeTUVarint(0x58u, image.height.toUInt()) - writeTV(0x60_04u)//这个似乎会变 有时候是02, 有时候是03 - writeTByteArray(0x6Au, value0x6A) - - writeTV(0x70_00u) - writeTV(0x78_03u) - writeTV(0x80_01u) - writeUByte(0u) - } - } - - /* - this.debugColorizedPrintThis(compareTo = buildPacket { - writeHex("00 00 00 07 00 00 00 5E 08 01 12 03 98 01 01 10 01 1A") - writeHex("5A 08") - writeUVarInt(groupId) - writeUByte(0x10u) - writeUVarInt(bot) - writeHex("18 00 22 10") - writeFully(image.md5) - writeUByte(0x28u) - writeUVarInt(image.fileSize.toUInt()) - writeHex("32 1A 37 00 4D 00 32 00 25 00 4C 00 31 00 56 00 32 00 7B 00 39 00 30 00 29 00 52 00") - writeHex("38 01 48 01 50") - writeUVarInt(image.width.toUInt()) - writeUByte(0x58u) - writeUVarInt(image.height.toUInt()) - writeHex("60 04 6A 05 32 36 36 35 36 70 00 78 03 80 01 00") - }.readBytes().toUHexString()) - */ - } - } - - companion object { - private val value0x6A: UByteArray = ubyteArrayOf(0x05u, 0x32u, 0x36u, 0x36u, 0x35u, 0x36u) - } - - @PacketId(0x0388u) - @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") - class Response(input: ByteReadPacket) : ResponsePacket(input) { - lateinit var state: State - - /** - * 访问 HTTP API 时需要使用的一个 key. 128 位 - */ - var uKey: ByteArray? = null - - enum class State { - /** - * 需要上传. 此时 [uKey] 不为 `null` - */ - REQUIRE_UPLOAD, - /** - * 服务器已有这个图片. 此时 [uKey] 为 `null` - */ - ALREADY_EXISTS, - /** - * 图片过大. 此时 [uKey] 为 `null` - */ - OVER_FILE_SIZE_MAX, - } - - override fun decode(): Unit = with(input) { - discardExact(6)//00 00 00 05 00 00 - - val length = remaining - 128 - 14 - if (length < 0) { - state = if (readUShort().toUInt() == 0x0025u) { - State.OVER_FILE_SIZE_MAX - } else { - State.ALREADY_EXISTS - } - - //图片过大 00 25 12 03 98 01 01 08 9B A4 DC 92 06 10 01 1A 1B 08 00 10 C5 01 1A 12 6F 76 65 72 20 66 69 6C 65 20 73 69 7A 65 20 6D 61 78 20 00 - //图片过大 00 25 12 03 98 01 01 08 9B A4 DC 92 06 10 01 1A 1B 08 00 10 C5 01 1A 12 6F 76 65 72 20 66 69 6C 65 20 73 69 7A 65 20 6D 61 78 20 00 - //图片已有 00 3F 12 03 98 01 01 08 9B A4 DC 92 06 10 01 1A 35 08 00 10 00 20 01 2A 1F 0A 10 24 66 B9 6B E8 58 FE C0 12 BD 1E EC CB 74 A8 8E 10 04 18 83 E2 AF 01 20 80 3C 28 E0 21 30 EF 9A 88 B9 0B 38 50 48 90 D7 DA B0 08 - //debugPrint("后文") - return@with - } - - discardExact(length) - uKey = readBytes(128) - state = State.REQUIRE_UPLOAD - //} else { - // println("服务器已经有了这个图片") - //println("后文 = ${readRemainingBytes().toUHexString()}") - //} - - - // 已经有了的一张图片 - // 00 3B 12 03 98 01 01 - // 08 AB A7 89 D8 02 //群ID - // 10 01 1A 31 08 00 10 00 20 01 2A 1B 0A 10 7A A4 B3 AA 8C 3C 0F 45 2D 9B 7F 30 2A 0A CE AA 10 04 18 F3 06 20 41 28 34 30 DF CF A2 93 02 38 50 48 D0 A9 E5 C8 0B - - // 服务器还没有的一张图片 - // 02 4E 12 03 98 01 02 - // 08 AB A7 89 D8 02 //群ID - // 10 02 22 C3 04 08 F8 9D D0 F5 09 12 10 2F CA 6B E7 B7 95 B7 27 06 35 27 54 0E 43 B4 30 18 00 48 BD EE 92 8D 05 48 BD EE 92 E5 01 48 BB CA 80 A3 02 48 BA F6 D7 5C 48 EF BC 90 F5 0A 50 50 50 50 50 50 50 50 50 50 5A 0D 67 63 68 61 74 2E 71 70 69 63 2E 63 6E 62 79 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 33 39 36 37 39 34 39 34 32 37 2F 33 39 36 37 39 34 39 34 32 37 2D 32 36 36 32 36 30 30 34 34 30 2D 32 46 43 41 36 42 45 37 42 37 39 35 42 37 32 37 30 36 33 35 32 37 35 34 30 45 34 33 42 34 33 30 2F 31 39 38 3F 76 75 69 6E 3D 31 30 34 30 34 30 30 32 39 30 26 74 65 72 6D 3D 32 35 35 26 73 72 76 76 65 72 3D 32 36 39 33 33 6A 77 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 33 39 36 37 39 34 39 34 32 37 2F 33 39 36 37 39 34 39 34 32 37 2D 32 36 36 32 36 30 30 34 34 30 2D 32 46 43 41 36 42 45 37 42 37 39 35 42 37 32 37 30 36 33 35 32 37 35 34 30 45 34 33 42 34 33 30 2F 30 3F 76 75 69 6E 3D 31 30 34 30 34 30 30 32 39 30 26 74 65 72 6D 3D 32 35 35 26 73 72 76 76 65 72 3D 32 36 39 33 33 72 79 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 33 39 36 37 39 34 39 34 32 37 2F 33 39 36 37 39 34 39 34 32 37 2D 32 36 36 32 36 30 30 34 34 30 2D 32 46 43 41 36 42 45 37 42 37 39 35 42 37 32 37 30 36 33 35 32 37 35 34 30 45 34 33 42 34 33 30 2F 37 32 30 3F 76 75 69 6E 3D 31 30 34 30 34 30 30 32 39 30 26 74 65 72 6D 3D 32 35 35 26 73 72 76 76 65 72 3D 32 36 39 33 33 78 00 - // [80 01] 04 9A 01 79 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 33 39 36 37 39 34 39 34 32 37 2F 33 39 36 37 39 34 39 34 32 37 2D 32 36 36 32 36 30 30 34 34 30 2D 32 46 43 41 36 42 45 37 42 37 39 35 42 37 32 37 30 36 33 35 32 37 35 34 30 45 34 33 42 34 33 30 2F 34 30 30 3F 76 75 69 6E 3D 31 30 34 30 34 30 30 32 39 30 26 74 65 72 6D 3D 32 35 35 26 73 72 76 76 65 72 3D 32 36 39 33 33 A0 01 00 - } - } -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadImage.kt new file mode 100644 index 000000000..879b68915 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadImage.kt @@ -0,0 +1,424 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused") + +package net.mamoe.mirai.network.protocol.tim.packet.action + +import io.ktor.client.HttpClient +import io.ktor.client.request.post +import io.ktor.http.HttpStatusCode +import io.ktor.http.URLProtocol +import io.ktor.http.userAgent +import kotlinx.coroutines.withContext +import kotlinx.io.core.* +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.message.ImageId +import net.mamoe.mirai.network.protocol.tim.TIMProtocol +import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket +import net.mamoe.mirai.network.protocol.tim.packet.PacketId +import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion +import net.mamoe.mirai.network.protocol.tim.packet.ResponsePacket +import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket.Response.State.* +import net.mamoe.mirai.network.qqAccount +import net.mamoe.mirai.qqAccount +import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.io.* +import net.mamoe.mirai.withSession +import kotlin.coroutines.coroutineContext + +/** + * 图片文件过大 + */ +class OverFileSizeMaxException : IllegalStateException() + +/** + * 上传群图片 + * 挂起直到上传完成或失败 + * + * 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数 + * + * @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时 + */ +suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession { + val userContext = coroutineContext + GroupImageIdRequestPacket(bot.qqAccount, internalId, image, sessionKey) + .sendAndExpect<GroupImageIdRequestPacket.Response, Unit> { + withContext(userContext) { + when (it.state) { + GroupImageIdRequestPacket.Response.State.REQUIRE_UPLOAD -> httpClient.postImage( + htcmd = "0x6ff0071", + uin = bot.qqAccount, + groupId = GroupId(id), + imageInput = image.input, + inputSize = image.inputSize, + uKeyHex = it.uKey!!.toUHexString("") + ) + + GroupImageIdRequestPacket.Response.State.ALREADY_EXISTS -> { + } + + GroupImageIdRequestPacket.Response.State.OVER_FILE_SIZE_MAX -> throw OverFileSizeMaxException() + } + } + }.join() + image.groupImageId +} + +/** + * 上传图片 + * 挂起直到上传完成或失败 + * + * 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数 + * + * @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时 + */ +suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession { + FriendImageIdRequestPacket(qqAccount, sessionKey, id, image) + .sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> { + when (it.state) { + REQUIRE_UPLOAD -> httpClient.postImage( + htcmd = "0x6ff0070", + uin = bot.qqAccount, + groupId = null, + uKeyHex = it.uKey!!.toUHexString(""), + imageInput = image.input, + inputSize = image.inputSize + ) + + ALREADY_EXISTS -> { + } + + OVER_FILE_SIZE_MAX -> throw OverFileSizeMaxException() + } + + it.imageId!! + }.await() +} + +@Suppress("SpellCheckingInspection") +internal suspend inline fun HttpClient.postImage( + htcmd: String, + uin: UInt, + groupId: GroupId?, + imageInput: Input, + inputSize: Long, + uKeyHex: String +): Boolean = try { + post<HttpStatusCode> { + url { + protocol = URLProtocol.HTTP + host = "htdata2.qq.com" + path("cgi-bin/httpconn") + + parameters["htcmd"] = htcmd + parameters["uin"] = uin.toLong().toString() + + if (groupId != null) parameters["groupcode"] = groupId.value.toLong().toString() + + parameters["term"] = "pc" + parameters["ver"] = "5603" + parameters["filesize"] = inputSize.toString() + parameters["range"] = 0.toString() + parameters["ukey"] = uKeyHex + + userAgent("QQClient") + } + + configureBody(inputSize, imageInput) + } == HttpStatusCode.OK +} finally { + imageInput.close() +} + +/** + * 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00 + */ +@Deprecated("Useless packet") +@PacketId(0X01_BDu) +@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") +class SubmitImageFilenamePacket( + private val bot: UInt, + private val target: UInt, + private val filename: String, + private val sessionKey: ByteArray +) : OutgoingPacket() { + override fun encode(builder: BytePacketBuilder) = with(builder) { + writeQQ(bot) + writeHex(TIMProtocol.fixVer2)//? + //writeHex("04 00 00 00 01 2E 01 00 00 69 35") + + encryptAndWrite(sessionKey) { + writeByte(0x01) + writeQQ(bot) + writeQQ(target) + writeZero(2) + writeUByte(0x02u) + writeRandom(1) + writeHex("00 0A 00 01 00 01") + val name = "UserDataImage:$filename" + writeShort(name.length.toShort()) + writeStringUtf8(name) + writeHex("00 00") + writeRandom(2)//这个也与是哪个好友有关? + writeHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01")//35 02? 最后这个值是与是哪个好友有关 + + //this.debugPrintThis("SubmitImageFilenamePacket") + } + + //解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1A 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 28 5A 53 41 58 40 57 4B 52 4A 5A 31 7E 33 59 4F 53 53 4C 4D 32 4B 49 2E 6A 70 67 00 00 06 E2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02 + //解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1B 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 28 5A 53 41 58 40 57 4B 52 4A 5A 31 7E 33 59 4F 53 53 4C 4D 32 4B 49 2E 6A 70 67 00 00 06 E2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02 + //解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1C 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 29 37 42 53 4B 48 32 44 35 54 51 28 5A 35 7D 35 24 56 5D 32 35 49 4E 2E 6A 70 67 00 00 03 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02 + } + + @PacketId(0x01_BDu) + @PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173") + class Response(input: ByteReadPacket) : ResponsePacket(input) { + override fun decode() = with(input) { + require(readBytes().contentEquals(expecting)) + } + + companion object { + private val expecting = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00) + } + } +} + + +/** + * 请求上传图片. 将发送图片的 md5, size, width, height. + * 服务器返回以下之一: + * - 服务器已经存有这个图片 + * - 服务器未存有, 返回一个 key 用于客户端上传 + */ +@PacketId(0x03_52u) +@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") +class FriendImageIdRequestPacket( + private val bot: UInt, + private val sessionKey: ByteArray, + private val target: UInt, + private val image: ExternalImage +) : OutgoingPacket() { + + override fun encode(builder: BytePacketBuilder) = with(builder) { + writeQQ(bot) + writeHex("04 00 00 00 01 2E 01 00 00 69 35 00 00 00 00 00 00 00 00") + + encryptAndWrite(sessionKey) { + writeHex("00 00 00 07 00 00 00") + + writeUVarintLVPacket(lengthOffset = { it - 7 }) { + writeUByte(0x08u) + writeUShort(0x01_12u) + writeUShort(0x03_98u) + writeUShort(0x01_01u) + writeUShort(0x08_01u) + + writeUVarintLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) { + writeUByte(0x08u) + writeUVarInt(bot) + + writeUByte(0x10u) + writeUVarInt(target) + + writeUShort(0x18_00u) + + writeUByte(0x22u) + writeUByte(0x10u) + writeFully(image.md5) + + writeUByte(0x28u) + writeUVarInt(image.inputSize.toUInt()) + + + writeUByte(0x32u) + //长度应为1A + writeUVarintLVPacket { + writeUShort(0x28_00u) + writeUShort(0x46_00u) + writeUShort(0x51_00u) + writeUShort(0x56_00u) + writeUShort(0x4B_00u) + writeUShort(0x41_00u) + writeUShort(0x49_00u) + writeUShort(0x25_00u) + writeUShort(0x4B_00u) + writeUShort(0x24_00u) + writeUShort(0x55_00u) + writeUShort(0x30_00u) + writeUShort(0x24_00u) + } + + writeUShort(0x38_01u) + writeUShort(0x48_00u) + + writeUByte(0x70u) + writeUVarInt(image.width.toUInt()) + writeUByte(0x78u) + writeUVarInt(image.height.toUInt()) + } + } + } + } + + @PacketId(0x0352u) + @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") + class Response(input: ByteReadPacket) : ResponsePacket(input) { + /** + * 访问 HTTP API 时需要使用的一个 key. 128 位 + */ + var uKey: ByteArray? = null + + /** + * 发送消息时使用的 id + */ + var imageId: ImageId? = null + + lateinit var state: State + + enum class State { + /** + * 需要上传. 此时 [uKey], [imageId] 均不为 `null` + */ + REQUIRE_UPLOAD, + /** + * 服务器已有这个图片. 此时 [uKey] 为 `null`, [imageId] 不为 `null` + */ + ALREADY_EXISTS, + /** + * 图片过大. 此时 [uKey], [imageId] 均为 `null` + */ + OVER_FILE_SIZE_MAX, + } + + override fun decode() = with(input) { + discardExact(6) + if (readUByte() != UByte.MIN_VALUE) { + discardExact(60) + + discardExact(1)//4A, id + uKey = readBytes(readUnsignedVarInt().toInt())//128 + + discardExact(1)//52, id + imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37 + state = REQUIRE_UPLOAD + } else { + val toDiscard = readUByte().toInt() - 37 + if (toDiscard < 0) { + state = OVER_FILE_SIZE_MAX + } else { + discardExact(toDiscard) + imageId = ImageId(readString(37)) + state = ALREADY_EXISTS + } + } + } + } +} + + +/** + * 获取 Image Id 和上传用的一个 uKey + */ +@PacketId(0x0388u) +@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") +class GroupImageIdRequestPacket( + private val bot: UInt, + private val groupInternalId: GroupInternalId, + private val image: ExternalImage, + private val sessionKey: ByteArray +) : OutgoingPacket() { + + override fun encode(builder: BytePacketBuilder) = with(builder) { + writeQQ(bot) + writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00") + + encryptAndWrite(sessionKey) { + writeHex("00 00 00 07 00 00 00") + + writeUVarintLVPacket(lengthOffset = { it - 7 }) { + writeByte(0x08) + writeHex("01 12 03 98 01 01 10 01 1A") + + writeUVarintLVPacket(lengthOffset = { it }) { + writeTUVarint(0x08u, groupInternalId.value) + writeTUVarint(0x10u, bot) + writeTV(0x1800u) + + writeUByte(0x22u) + writeUByte(0x10u) + writeFully(image.md5) + + writeTUVarint(0x28u, image.inputSize.toUInt()) + writeUVarintLVPacket(tag = 0x32u) { + writeTV(0x5B_00u) + writeTV(0x40_00u) + writeTV(0x33_00u) + writeTV(0x48_00u) + writeTV(0x5F_00u) + writeTV(0x58_00u) + writeTV(0x46_00u) + writeTV(0x51_00u) + writeTV(0x45_00u) + writeTV(0x51_00u) + writeTV(0x40_00u) + writeTV(0x24_00u) + writeTV(0x4F_00u) + } + writeTV(0x38_01u) + writeTV(0x48_01u) + writeTUVarint(0x50u, image.width.toUInt()) + writeTUVarint(0x58u, image.height.toUInt()) + writeTV(0x60_04u)//这个似乎会变 有时候是02, 有时候是03 + writeTByteArray(0x6Au, value0x6A) + + writeTV(0x70_00u) + writeTV(0x78_03u) + writeTV(0x80_01u) + writeUByte(0u) + } + } + } + } + + companion object { + private val value0x6A: UByteArray = ubyteArrayOf(0x05u, 0x32u, 0x36u, 0x36u, 0x35u, 0x36u) + } + + @PacketId(0x0388u) + @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") + class Response(input: ByteReadPacket) : ResponsePacket(input) { + lateinit var state: State + + /** + * 访问 HTTP API 时需要使用的一个 key. 128 位 + */ + var uKey: ByteArray? = null + + enum class State { + /** + * 需要上传. 此时 [uKey] 不为 `null` + */ + REQUIRE_UPLOAD, + /** + * 服务器已有这个图片. 此时 [uKey] 为 `null` + */ + ALREADY_EXISTS, + /** + * 图片过大. 此时 [uKey] 为 `null` + */ + OVER_FILE_SIZE_MAX, + } + + override fun decode(): Unit = with(input) { + discardExact(6)//00 00 00 05 00 00 + + val length = remaining - 128 - 14 + if (length < 0) { + state = if (readUShort().toUInt() == 0x0025u) State.OVER_FILE_SIZE_MAX else State.ALREADY_EXISTS + return@with + } + + discardExact(length) + uKey = readBytes(128) + state = State.REQUIRE_UPLOAD + } + } +} \ No newline at end of file