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:
parent
8308e68467
commit
28149dfed2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user