GroupImageDownload supports

This commit is contained in:
Him188 2019-11-20 19:40:50 +08:00
parent 44a6ba160e
commit 1294441716
5 changed files with 267 additions and 134 deletions

View File

@ -2,11 +2,11 @@
package net.mamoe.mirai.network.protocol.tim.packet.action package net.mamoe.mirai.network.protocol.tim.packet.action
import io.ktor.client.request.get
import kotlinx.io.charsets.Charsets import kotlinx.io.charsets.Charsets
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.ImageId0x06
import net.mamoe.mirai.message.requireLength import net.mamoe.mirai.message.requireLength
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
@ -59,11 +59,8 @@ interface FriendImageResponse : EventPacket
* 图片数据地址. * 图片数据地址.
*/ */
// TODO: 2019/11/15 应该为 inline class, 但 kotlin 有 bug // TODO: 2019/11/15 应该为 inline class, 但 kotlin 有 bug
data class FriendImageLink(inline val value: String) : FriendImageResponse { data class FriendImageLink(override inline val original: String) : FriendImageResponse, ImageLink {
suspend fun downloadAsByteArray(): ByteArray = download().readBytes() override fun toString(): String = "FriendImageLink($original)"
suspend fun download(): ByteReadPacket = Http.get(value)
override fun toString(): String = "FriendImageLink($value)"
} }
/** /**
@ -199,7 +196,7 @@ object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
writeUByte(0x1Au) writeUByte(0x1Au)
writeUByte(0x47u) writeUByte(0x47u)
writeTUVarint(0x08u, bot) writeTUVarint(0x08u, bot)
writeTUVarint(0x10u, bot) writeTUVarint(0x10u, bot) // 这里实际上应该是这张图片来自哪个 QQ 号. 但传 bot 也没事.
writeTLV(0x1Au, imageId.value.toByteArray(Charsets.ISO_8859_1)) writeTLV(0x1Au, imageId.value.toByteArray(Charsets.ISO_8859_1))
writeHex("20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01") writeHex("20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01")
} }
@ -276,7 +273,7 @@ object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
while (readUByte().toUInt() != 0x4Au) readUVarLong() while (readUByte().toUInt() != 0x4Au) readUVarLong()
val uKey = readBytes(readUVarInt().toInt())//128 val uKey = readBytes(readUVarInt().toInt())//128
while (readUByte().toUInt() != 0x52u) readUVarLong() while (readUByte().toUInt() != 0x52u) readUVarLong()
val imageId = ImageId(readString(readUVarInt().toInt()))//37 val imageId = ImageId0x06(readString(readUVarInt().toInt()))//37
return FriendImageUKey(imageId, uKey) return FriendImageUKey(imageId, uKey)
} catch (e: EOFException) { } catch (e: EOFException) {
val toDiscard = readUByte().toInt() - 37 val toDiscard = readUByte().toInt() - 37
@ -285,7 +282,7 @@ object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
FriendImageOverFileSizeMax FriendImageOverFileSizeMax
} else { } else {
discardExact(toDiscard) discardExact(toDiscard)
val imageId = ImageId(readString(37)) val imageId = ImageId0x06(readString(37))
FriendImageAlreadyExists(imageId) FriendImageAlreadyExists(imageId)
} }
} }
@ -302,6 +299,7 @@ object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
// 3A 00 80 01 00 // 3A 00 80 01 00
//00 00 00 08 00 00 //00 00 00 08 00 00
// [02 29] // [02 29]
// 12 [06] 98 01 02 A0 01 00 // 12 [06] 98 01 02 A0 01 00
@ -315,7 +313,7 @@ object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
discardExact(1) discardExact(1)
discardExact(2)// [A4 04] 后文长度 discardExact(2)// [A4 04] 后文长度
check(readUByte().toUInt() == 0x0Au) { "Illegal identity. Required 0x0Au" } check(readUByte().toUInt() == 0x0Au) { "Illegal identity. Required 0x0Au" }
/* val imageId = */ImageId(readString(readUByte().toInt())) /* val imageId = */ImageId0x06(readString(readUByte().toInt()))
check(readUByte().toUInt() == 0x18u) { "Illegal identity. Required 0x18u" } check(readUByte().toUInt() == 0x18u) { "Illegal identity. Required 0x18u" }
check(readUShort().toUInt() == 0x0032u) { "Illegal identity. Required 0x0032u" } check(readUShort().toUInt() == 0x0032u) { "Illegal identity. Required 0x0032u" }

View File

@ -3,8 +3,9 @@
package net.mamoe.mirai.network.protocol.tim.packet.action package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.charsets.Charsets import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.* import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
@ -13,16 +14,17 @@ import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.contact.withSession import net.mamoe.mirai.contact.withSession
import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.ImageId0x03
import net.mamoe.mirai.message.requireLength import net.mamoe.mirai.message.requireLength
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.event.EventPacket import net.mamoe.mirai.network.protocol.tim.packet.event.EventPacket
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.Http import net.mamoe.mirai.utils.Http
import net.mamoe.mirai.utils.assertUnreachable import net.mamoe.mirai.utils.assertUnreachable
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.debugPrintln
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
@ -64,14 +66,32 @@ interface GroupImageResponse : EventPacket
// endregion // endregion
@Suppress("unused")
@Serializable @Serializable
data class ImageDownloadInfo( class ImageDownloadInfo(
@SerialId(11) val host: String, @SerialId(3) val errorCode: Int = 0, // 0 for success
@SerialId(4) val errorMessage: String? = null, // 感动中国
@SerialId(12) val thumbnail: String, @SerialId(10) private val _port: List<Byte>? = null,
@SerialId(13) val original: String, @SerialId(11) private val _host: String? = null,
@SerialId(14) val compressed: String
) : GroupImageResponse @SerialId(12) private val _thumbnail: String? = null,
@SerialId(13) private val _original: String? = null,
@SerialId(14) private val _compressed: String? = null
) : GroupImageResponse, ImageLink {
private val port: List<Byte> get() = _port!!
private val host: String get() = "http://" + _host!!
val thumbnail: String get() = host + ":" + port.first() + _thumbnail!!
override val original: String get() = host + ":" + port.first() + _original!!
val compressed: String get() = host + ":" + port.first() + _compressed!!
override fun toString(): String = "ImageDownloadInfo(${_original?.let { original } ?: errorMessage ?: "unknown"}"
}
fun ImageDownloadInfo.requireSuccess(): ImageDownloadInfo {
require(this.errorCode == 0) { this.errorMessage ?: "null" }
return this
}
@Serializable @Serializable
class ImageUploadInfo( class ImageUploadInfo(
@ -86,126 +106,162 @@ class ImageUploadInfo(
@AnnotatedId(KnownPacketId.GROUP_IMAGE_ID) @AnnotatedId(KnownPacketId.GROUP_IMAGE_ID)
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.10.26", timVersion = "2.3.2 (21173)")
object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() { object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
/*
请求上传图片
ProtoMap(
varint 0=0x5D(UVarInt(data=93))
varint 1=0x01(UVarInt(data=1))
delimi 2=98 01 01
varint 2=0x01(UVarInt(data=1))
delimi 3=08 A0 89 F7 B6 03 10 A2 FF 8C F0 03 18 00 22 10 2C F2 65 98 12 EA 9C 88 60 BD 7A 29 8E 6F 9B 4D 28 F0 0B 32 1A 43 00 4D 00 45 00 35 00 44 00 53 00 5A 00 43 00 4C 00 52 00 51 00 29 00 28 00 38 01 48 01 50 8B 02 58 BE 03 60 02 6A 05 32 36 39 33 33 70 00 78 03 80 01 00
)
*/
private val RequestImageIdUnknownByteArray = ubyteArrayOf(0x98u, 0x01u, 0x01u).toByteArray()
private val constValue3 = byteArrayOf(
0x28, 0x00, 0x5A, 0x00, 0x53, 0x00, 0x41, 0x00, 0x58, 0x00, 0x40, 0x00, 0x57,
0x00, 0x4B, 0x00, 0x52, 0x00, 0x4A, 0x00, 0x5A, 0x00, 0x31, 0x00, 0x7E, 0x00
)
@Suppress("unused")
@Serializable
private class RequestIdProto(
@SerialId(2) val unknown4: Byte = 1,
@SerialId(3) var body: Body
) {
@Serializable
class Body(
@SerialId(1) val group: Int,
@SerialId(2) val bot: Int,
@SerialId(3) val const1: Byte = 0,
@SerialId(4) val md5: ByteArray,
@SerialId(5) val const2: Short = 0x0E2D,
@SerialId(6) val const3: ByteArray = constValue3,
@SerialId(7) val const4: Byte = 1,
@SerialId(9) val const5: Byte = 1,
@SerialId(10) val width: Int,
@SerialId(11) val height: Int,
@SerialId(12) val const6: Byte = 4,
@SerialId(13) val const7: ByteArray = constValue7,
@SerialId(14) val const8: Byte = 0,
@SerialId(15) val const9: Byte = 3,
@SerialId(16) val const10: Byte = 0
)
}
@Suppress("unused")
@Serializable
private class RequestLinkProto(
@SerialId(2) val unknown4: Byte = 2,
@SerialId(4) var body: Body
) {
/*
ProtoMap(
varint 1=0x8E87C28403(UVarInt(data=814777230))
varint 2=0xA2FF8CF003(UVarInt(data=1040400290))
varint 3=0xFBECB9A9A(UVarInt(data=2771285627))
delimi 4=66 3C 60 FB 31 67 85 84 1A 18 00 52 2C D6 C8 7E
varint 5=0x04(UVarInt(data=4))
varint 6=0x02(UVarInt(data=2))
varint 7=0x20(UVarInt(data=32))
varint 8=0xFF01(UVarInt(data=255))
varint 9=0x00(UVarInt(data=0))
varint 10=0x01(UVarInt(data=1))
delimi 11=32 36 39 33 33
varint 12=0x00(UVarInt(data=0))
varint 13=0x00(UVarInt(data=0))
varint 14=0x01(UVarInt(data=1))
varint 15=0x00(UVarInt(data=0))
varint 16=0xA412(UVarInt(data=2340))
varint 17=0xB808(UVarInt(data=1080))
varint 18=0xE807(UVarInt(data=1000))
varint 20=0x01(UVarInt(data=1))
)
*/
@Serializable
class Body(
@SerialId(1) val group: Int,
@SerialId(2) val bot: Int,
@SerialId(3) val uniqueId: Int,
@SerialId(4) val md5: ByteArray,
@SerialId(5) val const2: Byte = 4,
@SerialId(6) val const3: Byte = 2,
@SerialId(7) val const4: Byte = 32,
@SerialId(8) val const14: Int = 255,
@SerialId(9) val const5: Byte = 0,
@SerialId(10) val unknown5: Int = 1,
@SerialId(11) val const7: ByteArray = constValue7,
@SerialId(12) val unknown6: Byte = 0,
@SerialId(13) val const6: Byte = 0,
@SerialId(14) val const8: Byte = 0,
@SerialId(15) val const9: Byte = 0,
@SerialId(16) val height: Int,
@SerialId(17) val width: Int,
@SerialId(18) val const12: Int = 1003, //?? 有时候还是1000, 1004
@SerialId(20) val const13: Byte = 1
)
}
private val constValue7: ByteArray = byteArrayOf(0x32, 0x36, 0x39, 0x33, 0x33)
private val requestImageIdHead = ubyteArrayOf(0x12u, 0x03u, 0x98u, 0x01u, 0x01u)
@Suppress("FunctionName") @Suppress("FunctionName")
fun RequestImageId( fun RequestImageId(
bot: UInt, bot: UInt,
groupInternalId: GroupInternalId, groupInternalId: GroupInternalId,
image: ExternalImage, image: ExternalImage,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey, version = TIMProtocol.version0x04) { ): OutgoingPacket = buildSessionProtoPacket(
writeHex("00 00 00 07 00 00") bot, sessionKey, name = "GroupImagePacket.RequestImageId",
head = requestImageIdHead,
writeShortLVPacket(lengthOffset = { it - 7 }) { serializer = RequestIdProto.serializer(),
writeByte(0x08) protoObj = RequestIdProto(
writeHex("01 12 03 98 01 01 10 01 1A") body = RequestIdProto.Body(
// 02 10 02 22 bot = bot.toInt(),
group = groupInternalId.value.toInt(),
writeUVarIntLVPacket(lengthOffset = { it }) { md5 = image.md5,
writeTUVarint(0x08u, groupInternalId.value) height = image.height,
writeTUVarint(0x10u, bot) width = image.width
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)
}
}
}
private val requestImageLinkHead = ubyteArrayOf(0x08u, 0x01u, 0x12u, 0x03u, 0x98u, 0x01u, 0x2u)
@Suppress("FunctionName") @Suppress("FunctionName")
fun RequestImageLink( fun RequestImageLink(
bot: UInt, bot: UInt,
sessionKey: SessionKey, sessionKey: SessionKey,
imageId: ImageId imageId: ImageId0x03
): OutgoingPacket { ): OutgoingPacket {
imageId.requireLength() imageId.requireLength()
require(imageId.value.length == 37) { "ImageId.value.length must == 37" } //require(imageId.value.length == 37) { "ImageId.value.length must == 37" }
//[00 00 00 07] [00 00 00 52] (08 01 12 03 98 01 02) 10 02 22 4E 08 A0 89 F7 B6 03 10 A2 FF 8C F0 03 18 BB 92 94 BF 08 22 10 64 CF BB 65 00 13 8D B5 58 E2 45 1E EA 65 88 E1 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 97 04 88 01 ED 03 90 01 04 A0 01 01
// 00 00 00 07 00 00 00 // head 长度 proto 长度 head proto
// [4B] return buildSessionProtoPacket(
// 08 bot,
// 01 12 sessionKey,
// 03 98 name = "GroupImagePacket.RequestImageLink",
// 01 02 head = requestImageLinkHead,
// 08 02 serializer = RequestLinkProto.serializer(),
// protoObj = RequestLinkProto(
// 1A [47] body = RequestLinkProto.Body(
// 08 [A2 FF 8C F0 03] UVarInt bot = bot.toInt(), // same bin representation, so will be decoded correctly as a unsigned value in the server
// 10 [DD F1 92 B7 07] UVarInt group = bot.toInt(), // TODO 似乎是必须要填group ??
// 1A [25] 2F 38 65 32 63 32 38 62 64 2D 35 38 61 31 2D 34 66 37 30 2D 38 39 61 31 2D 65 37 31 39 66 63 33 30 37 65 65 66 uniqueId = imageId.uniqueId.toInt(),
// 20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01 md5 = imageId.md5,
height = imageId.height,
width = imageId.width
// 00 00 00 07 00 00 00 )
// [4B] )
// 08 01 )
// 12 03
// 98 01 02
// 08 02
//
// 1A
// [47]
// 08 [A2 FF 8C F0 03]
// 10 [A6 A7 F1 EA 02]
// 1A [25] 2F 39 61 31 66 37 31 36 32 2D 38 37 30 38 2D 34 39 30 38 2D 38 31 63 30 2D 66 34 63 64 66 33 35 63 38 64 37 65
// 20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01
return buildSessionPacket(bot, sessionKey, version = TIMProtocol.version0x04) {
writeHex("00 00 00 07 00 00")
writeUShort(0x004Bu)
writeUByte(0x08u)
writeTV(0x01_12u)
writeTV(0x03_98u)
writeTV(0x01_02u)
writeTV(0x08_02u)
writeUByte(0x1Au)
writeUByte(0x47u)
writeTUVarint(0x08u, bot)
writeTUVarint(0x10u, bot)
writeTLV(0x1Au, imageId.value.toByteArray(Charsets.ISO_8859_1))
writeHex("20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01")
}
} }
private val value0x6A: UByteArray = ubyteArrayOf(0x05u, 0x32u, 0x36u, 0x36u, 0x35u, 0x36u)
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): GroupImageResponse { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): GroupImageResponse {
discardExact(6)//00 00 00 05 00 00 val headLength = readInt()
val protoLength = readInt()
discardExact(2) // 是 protobuf 的长度, 但是是错的 discardExact(headLength)
val bytes = readBytes() val bytes = readBytes(protoLength)
// println(ByteReadPacket(bytes).readProtoMap()) // println(ByteReadPacket(bytes).readProtoMap())
@Serializable @Serializable
@ -214,6 +270,7 @@ object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
@SerialId(4) val imageDownloadInfo: ImageDownloadInfo? = null @SerialId(4) val imageDownloadInfo: ImageDownloadInfo? = null
) )
debugPrintln("收到返回=" + bytes.toUHexString())
val proto = ProtoBuf.load(GroupImageResponseProto.serializer(), bytes) val proto = ProtoBuf.load(GroupImageResponseProto.serializer(), bytes)
return when { return when {
proto.imageUploadInfoPacket != null -> proto.imageUploadInfoPacket proto.imageUploadInfoPacket != null -> proto.imageUploadInfoPacket

View File

@ -0,0 +1,78 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "NO_REFLECTION_IN_CLASS_PATH")
package net.mamoe.mirai.network.protocol.tim.packet.action
import io.ktor.client.request.get
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.Http
/**
* 图片文件过大
*/
class OverFileSizeMaxException : IllegalStateException()
interface ImageLink {
/**
* 原图
*/
val original: String
suspend fun downloadAsByteArray(): ByteArray = download().readBytes()
suspend fun download(): ByteReadPacket = Http.get(original)
}
/*
/**
* 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00
*/
@Deprecated("Useless packet")
@AnnotatedId(KnownPacketId.SUBMIT_IMAGE_FILE_NAME)
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2 (21173)")
object SubmitImageFilenamePacket : PacketFactory {
operator fun invoke(
bot: UInt,
target: UInt,
filename: String,
sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot)
writeFully(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
}
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
class Response {
override fun decode() = with(input) {
require(readBytes().contentEquals(expecting))
}
companion object {
private val expecting = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00)
}
}
}*/
// regiion GroupImageResponse

View File

@ -5,24 +5,25 @@ package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import net.mamoe.mirai.* import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.* import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.internal.readMessageChain import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageLink import net.mamoe.mirai.network.protocol.tim.packet.action.ImageLink
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImagePacket
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.printTLVMap import net.mamoe.mirai.utils.io.printTLVMap
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readTLVMap import net.mamoe.mirai.utils.io.readTLVMap
import net.mamoe.mirai.utils.io.readUShortLVByteArray import net.mamoe.mirai.utils.io.readUShortLVByteArray
import net.mamoe.mirai.withSession
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
/** /**
@ -87,8 +88,8 @@ abstract class MessagePacketBase<TSubject : Contact> : EventPacket, BotEvent() {
// endregion // endregion
// region Image download // region Image download
suspend inline fun Image.getLink(): ImageLink = bot.withSession { getLink() }
suspend fun Image.getLink(): FriendImageLink = bot.withSession { FriendImagePacket.RequestImageLink(bot.qqAccount, bot.sessionKey, id).sendAndExpect() }
suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray() suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray()
suspend inline fun Image.download(): ByteReadPacket = getLink().download() suspend inline fun Image.download(): ByteReadPacket = getLink().download()
// endregion // endregion
@ -106,6 +107,7 @@ data class GroupMessage(
override val sender: QQ, override val sender: QQ,
override val message: MessageChain = NullMessageChain override val message: MessageChain = NullMessageChain
) : MessagePacket<Group>() { ) : MessagePacket<Group>() {
override val subject: Group get() = group override val subject: Group get() = group
} }

View File

@ -7,10 +7,7 @@ import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Image import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.image
import net.mamoe.mirai.message.sendTo
import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
@ -52,7 +49,8 @@ class ExternalImage(
/** /**
* 用于发送消息的 [ImageId] * 用于发送消息的 [ImageId]
*/ */
val groupImageId: ImageId by lazy { ImageId("{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format") } @Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
val groupImageId: ImageId by lazy { ImageId0x03("{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format", 0u, height, width) }
override fun toString(): String = "[ExternalImage(${width}x$height $format)]" override fun toString(): String = "[ExternalImage(${width}x$height $format)]"
} }