1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-03-25 06:50:09 +08:00

Combine image packets into the same file

This commit is contained in:
Him188 2019-10-28 22:01:49 +08:00
parent 8308e68467
commit 28149dfed2
3 changed files with 424 additions and 681 deletions
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action

View File

@ -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
}
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}