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

View File

@ -3,8 +3,9 @@
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.coroutines.withContext
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.*
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
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.withSession
import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.ImageId0x03
import net.mamoe.mirai.message.requireLength
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.event.EventPacket
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.Http
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
@ -64,14 +66,32 @@ interface GroupImageResponse : EventPacket
// endregion
@Suppress("unused")
@Serializable
data class ImageDownloadInfo(
@SerialId(11) val host: String,
class ImageDownloadInfo(
@SerialId(3) val errorCode: Int = 0, // 0 for success
@SerialId(4) val errorMessage: String? = null, // 感动中国
@SerialId(12) val thumbnail: String,
@SerialId(13) val original: String,
@SerialId(14) val compressed: String
) : GroupImageResponse
@SerialId(10) private val _port: List<Byte>? = null,
@SerialId(11) private val _host: String? = null,
@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
class ImageUploadInfo(
@ -86,126 +106,162 @@ class ImageUploadInfo(
@AnnotatedId(KnownPacketId.GROUP_IMAGE_ID)
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2 (21173)")
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")
fun RequestImageId(
bot: UInt,
groupInternalId: GroupInternalId,
image: ExternalImage,
sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey, version = TIMProtocol.version0x04) {
writeHex("00 00 00 07 00 00")
writeShortLVPacket(lengthOffset = { it - 7 }) {
writeByte(0x08)
writeHex("01 12 03 98 01 01 10 01 1A")
// 02 10 02 22
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)
}
}
}
): OutgoingPacket = buildSessionProtoPacket(
bot, sessionKey, name = "GroupImagePacket.RequestImageId",
head = requestImageIdHead,
serializer = RequestIdProto.serializer(),
protoObj = RequestIdProto(
body = RequestIdProto.Body(
bot = bot.toInt(),
group = groupInternalId.value.toInt(),
md5 = image.md5,
height = image.height,
width = image.width
)
)
)
private val requestImageLinkHead = ubyteArrayOf(0x08u, 0x01u, 0x12u, 0x03u, 0x98u, 0x01u, 0x2u)
@Suppress("FunctionName")
fun RequestImageLink(
bot: UInt,
sessionKey: SessionKey,
imageId: ImageId
imageId: ImageId0x03
): OutgoingPacket {
imageId.requireLength()
require(imageId.value.length == 37) { "ImageId.value.length must == 37" }
// 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] UVarInt
// 10 [DD F1 92 B7 07] UVarInt
// 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
// 20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01
// 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")
}
//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
// head 长度 proto 长度 head proto
return buildSessionProtoPacket(
bot,
sessionKey,
name = "GroupImagePacket.RequestImageLink",
head = requestImageLinkHead,
serializer = RequestLinkProto.serializer(),
protoObj = RequestLinkProto(
body = RequestLinkProto.Body(
bot = bot.toInt(), // same bin representation, so will be decoded correctly as a unsigned value in the server
group = bot.toInt(), // TODO 似乎是必须要填group ??
uniqueId = imageId.uniqueId.toInt(),
md5 = imageId.md5,
height = imageId.height,
width = imageId.width
)
)
)
}
private val value0x6A: UByteArray = ubyteArrayOf(0x05u, 0x32u, 0x36u, 0x36u, 0x35u, 0x36u)
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): GroupImageResponse {
discardExact(6)//00 00 00 05 00 00
discardExact(2) // 是 protobuf 的长度, 但是是错的
val bytes = readBytes()
val headLength = readInt()
val protoLength = readInt()
discardExact(headLength)
val bytes = readBytes(protoLength)
// println(ByteReadPacket(bytes).readProtoMap())
@Serializable
@ -214,6 +270,7 @@ object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
@SerialId(4) val imageDownloadInfo: ImageDownloadInfo? = null
)
debugPrintln("收到返回=" + bytes.toUHexString())
val proto = ProtoBuf.load(GroupImageResponseProto.serializer(), bytes)
return when {
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.discardExact
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.Group
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.BroadcastControllable
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.internal.readMessageChain
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.FriendImagePacket
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.network.protocol.tim.packet.action.ImageLink
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.printTLVMap
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readTLVMap
import net.mamoe.mirai.utils.io.readUShortLVByteArray
import net.mamoe.mirai.withSession
import kotlin.jvm.JvmName
/**
@ -87,8 +88,8 @@ abstract class MessagePacketBase<TSubject : Contact> : EventPacket, BotEvent() {
// endregion
// 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.download(): ByteReadPacket = getLink().download()
// endregion
@ -106,6 +107,7 @@ data class GroupMessage(
override val sender: QQ,
override val message: MessageChain = NullMessageChain
) : MessagePacket<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.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.image
import net.mamoe.mirai.message.sendTo
import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage
import net.mamoe.mirai.utils.io.toUHexString
@ -52,7 +49,8 @@ class ExternalImage(
/**
* 用于发送消息的 [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)]"
}