Commit 867c0d62 authored by Him188's avatar Him188

Downloading image is now available

parent 8b3d710e
...@@ -30,7 +30,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -30,7 +30,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
override suspend fun onPacketReceived(packet: Packet): Unit = with(session) { override suspend fun onPacketReceived(packet: Packet): Unit = with(session) {
when (packet) { when (packet) {
is SKey -> { is SKey -> {
_sKey = packet.delegate _sKey = packet.value
cookies = "uin=o$qqAccount;skey=$sKey;" cookies = "uin=o$qqAccount;skey=$sKey;"
......
package net.mamoe.mirai.network.protocol.tim.packet.action
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.withSession
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.ImageLink
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdDownloadLinkRequestPacket.ImageLinkResponse
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.qqAccount
suspend fun QQ.getLink(image: Image): ImageLink = withSession {
FriendImageIdDownloadLinkRequestPacket(bot.qqAccount, bot.sessionKey, id, image.id).sendAndExpect<ImageLinkResponse>().link
}
suspend inline fun QQ.downloadAsByteArray(image: Image): ByteArray = this.getLink(image).downloadAsByteArray()
\ No newline at end of file
...@@ -5,16 +5,14 @@ package net.mamoe.mirai.network.protocol.tim.packet.action ...@@ -5,16 +5,14 @@ package net.mamoe.mirai.network.protocol.tim.packet.action
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.response.HttpResponse
import io.ktor.client.response.readBytes
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol import io.ktor.http.URLProtocol
import io.ktor.http.userAgent import io.ktor.http.userAgent
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.ImageLink
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
...@@ -76,10 +74,10 @@ suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession { ...@@ -76,10 +74,10 @@ suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession {
* @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时 * @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时
*/ */
suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession { suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession {
FriendImageIdRequestPacket(qqAccount, sessionKey, id, image) FriendImagePacket.RequestImageId(qqAccount, sessionKey, id, image)
.sendAndExpectAsync<FriendImageIdRequestPacket.Response, ImageId> { .sendAndExpectAsync<ImageResponse, ImageId> {
return@sendAndExpectAsync when (it) { return@sendAndExpectAsync when (it) {
is FriendImageIdRequestPacket.Response.RequireUpload -> { is ImageUKey -> {
Http.postImage( Http.postImage(
htcmd = "0x6ff0070", htcmd = "0x6ff0070",
uin = bot.qqAccount, uin = bot.qqAccount,
...@@ -90,10 +88,9 @@ suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession { ...@@ -90,10 +88,9 @@ suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession {
) )
it.imageId it.imageId
} }
is ImageAlreadyExists -> it.imageId
is FriendImageIdRequestPacket.Response.AlreadyExists -> it.imageId is ImageOverFileSizeMax -> throw OverFileSizeMaxException()
else -> error("This shouldn't happen")
is FriendImageIdRequestPacket.Response.OverFileSizeMax -> throw OverFileSizeMaxException()
} }
}.await() }.await()
} }
...@@ -133,11 +130,6 @@ internal suspend inline fun HttpClient.postImage( ...@@ -133,11 +130,6 @@ internal suspend inline fun HttpClient.postImage(
imageInput.close() imageInput.close()
} }
internal suspend inline fun HttpClient.download(
url: String
): ByteArray = get<HttpResponse>(url).readBytes() // TODO 不知道为什么找不到 `ByteReadChannel`.
/* /*
/** /**
* 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00 * 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00
...@@ -191,100 +183,39 @@ object SubmitImageFilenamePacket : PacketFactory { ...@@ -191,100 +183,39 @@ object SubmitImageFilenamePacket : PacketFactory {
} }
}*/ }*/
interface ImageResponse : EventPacket
/** /**
* 通过 [ImageId] 请求下载链接. * 图片数据地址.
*/ */
@AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID) // TODO: 2019/11/15 应该为 inline class, 但 kotlin 有 bug
@PacketVersion(date = "2019.11.14", timVersion = "2.3.2 (21173)") data class ImageLink(inline val value: String) : ImageResponse {
object FriendImageIdDownloadLinkRequestPacket : SessionPacketFactory<FriendImageIdDownloadLinkRequestPacket.ImageLinkResponse>() { suspend fun downloadAsByteArray(): ByteArray = download().readBytes()
operator fun invoke( suspend fun download(): ByteReadPacket = Http.get(value)
bot: UInt,
sessionKey: SessionKey,
target: UInt,
imageId: ImageId
): OutgoingPacket {
imageId.requireLength()
// 00 00 00 07 00 00 00 override fun toString(): String = "ImageLink($value)"
// [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
return buildSessionPacket(bot, sessionKey, version = TIMProtocol.version0x04) { /**
writeHex("00 00 00 07 00 00") * 访问 HTTP API 时使用的 uKey
*/
class ImageUKey(inline val imageId: ImageId, inline val uKey: ByteArray) : ImageResponse {
override fun toString(): String = "ImageUKey(imageId=${imageId.value}, uKey=${uKey.toUHexString()})"
}
writeShortLVPacket(lengthOffset = { it - 7 }) { /**
writeUByte(0x08u) * 图片 ID 已存在
writeTV(0x01_12u) * 发送消息时使用的 id
writeTV(0x03_98u) */
writeTV(0x01_02u) inline class ImageAlreadyExists(inline val imageId: ImageId) : ImageResponse {
writeTV(0x08_02u) override fun toString(): String = "FriendImageAlreadyExists(imageId=${imageId.value})"
writeTV(0x1A_47u) }
writeTUVarint(0x08u, bot)
writeTUVarint(0x10u, target)
writeTLV(0x1Au, imageId.value.toByteArray())
writeHex("20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01")
}
}
}
/** /**
* 图片下载链接. 直接 'get' 请求即可 * 超过文件大小上限
*/ */
data class ImageLinkResponse( object ImageOverFileSizeMax : ImageResponse {
val imageId: ImageId, override fun toString(): String = "FriendImageOverFileSizeMax"
val link: ImageLink
) : Packet
// TODO: 2019/11/14 需要跟 RequestId packet 合并. 因为现行结构无法分别处理; 或者考虑修改结构(不推荐)
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): ImageLinkResponse {
//00 00 00 08 00 00
// [02 2B]
// 12 [06] 98 01 02 A0 01 00
// 08 02 1A
// [A6 04]
// 0A [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
// 18 00 32
// [7B] 68 74 74 70 3A 2F 2F 36 31 2E 31 35 31 2E 32 33 34 2E 35 34 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 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 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 32 7C 68 74 74 70 3A 2F 2F 31 30 31 2E 32 32 37 2E 31 33 31 2E 36 37 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 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 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 32 7D 68 74 74 70 3A 2F 2F 31 35 37 2E 32 35 35 2E 31 39 32 2E 31 30 35 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 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 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 32 7C 68 74 74 70 3A 2F 2F 31 32 30 2E 32 34 31 2E 31 39 30 2E 34 31 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 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 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
// 3A 00 80 01 00
//00 00 00 08 00 00
// [02 29]
// 12 [06] 98 01 02 A0 01 00
// 08 02 1A
// [A4 04]
// 0A [25] 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64
// 18 00 32
// [7A] 68 74 74 70 3A 2F 2F 31 30 31 2E 38 39 2E 33 39 2E 32 31 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64 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
// 32 7B 68 74 74 70 3A 2F 2F 36 31 2E 31 35 31 2E 31 38 33 2E 32 31 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64 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 32 7D 68 74 74 70 3A 2F 2F 31 35 37 2E 32 35 35 2E 31 39 32 2E 31 30 35 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64 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 32 7C 68 74 74 70 3A 2F 2F 31 32 30 2E 32 34 31 2E 31 39 30 2E 34 31 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64 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 3A 00 80 01 00
discardExact(6)//00 00 00 08 00 00
discardExact(2)// [02 29]
discardExact(1 + 1 + 6)// 12 [06] 98 01 02 A0 01 00
discardExact(3)// 08 02 1A
discardExact(2)// [A4 04] 后文长度
check(readUByte().toUInt() == 0x0Au) { "Illegal identity. Required 0x0Au" }
val imageId = ImageId(readString(readUByte().toInt()))
check(readUByte().toUInt() == 0x18u) { "Illegal identity. Required 0x18u" }
check(readUShort().toUInt() == 0x0032u) { "Illegal identity. Required 0x0032u" }
val link = readUVarIntLVString()
discard()
return ImageLinkResponse(imageId, ImageLink(link))
}
} }
/** /**
...@@ -294,9 +225,9 @@ object FriendImageIdDownloadLinkRequestPacket : SessionPacketFactory<FriendImage ...@@ -294,9 +225,9 @@ object FriendImageIdDownloadLinkRequestPacket : SessionPacketFactory<FriendImage
* - 服务器未存有, 返回一个 key 用于客户端上传 * - 服务器未存有, 返回一个 key 用于客户端上传
*/ */
@AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID) @AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID)
@PacketVersion(date = "2019.11.1", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.16", timVersion = "2.3.2 (21173)")
object FriendImageIdRequestPacket : SessionPacketFactory<FriendImageIdRequestPacket.Response>() { object FriendImagePacket : SessionPacketFactory<ImageResponse>() {
operator fun invoke( fun RequestImageId(
bot: UInt, bot: UInt,
sessionKey: SessionKey, sessionKey: SessionKey,
target: UInt, target: UInt,
...@@ -341,76 +272,162 @@ object FriendImageIdRequestPacket : SessionPacketFactory<FriendImageIdRequestPac ...@@ -341,76 +272,162 @@ object FriendImageIdRequestPacket : SessionPacketFactory<FriendImageIdRequestPac
} }
fun RequestImageLink(
bot: UInt,
sessionKey: SessionKey,
imageId: ImageId
): OutgoingPacket {
imageId.requireLength()
require(imageId.value.length == 37) { "ImageId.value.length must == 37" }
sealed class Response : EventPacket { // 00 00 00 07 00 00 00
data class RequireUpload( // [4B]
/** // 08
* 访问 HTTP API 时需要使用的一个 key. 128 位 // 01 12
*/ // 03 98
val uKey: ByteArray, // 01 02
/** // 08 02
* 发送消息时使用的 id //
*/ // 1A [47]
val imageId: ImageId // 08 [A2 FF 8C F0 03] UVarInt
) : Response() { // 10 [DD F1 92 B7 07] UVarInt
override fun equals(other: Any?): Boolean { // 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
if (this === other) return true // 20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01
if (other !is RequireUpload) return false
if (!uKey.contentEquals(other.uKey)) return false
if (imageId != other.imageId) return false
return true // 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
override fun hashCode(): Int {
var result = uKey.contentHashCode()
result = 31 * result + imageId.hashCode()
return result
}
}
data class AlreadyExists( return buildSessionPacket(bot, sessionKey, version = TIMProtocol.version0x04) {
/** writeHex("00 00 00 07 00 00")
* 发送消息时使用的 id
*/
val imageId: ImageId
) : Response()
/** writeUShort(0x004Bu)
* 超过文件大小上限
*/ writeUByte(0x08u)
object OverFileSizeMax : Response() { writeTV(0x01_12u)
override fun toString(): String = this::class.simpleName!! 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")
} }
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): ImageResponse =
discardExact(6) with(this) {
if (readUByte() != UByte.MIN_VALUE) { // 上传图片, 成功获取ID
discardExact(60) //00 00 00 08 00 00
// [01 0D]
@Suppress("ControlFlowWithEmptyBody") // 12 [06] 98 01 01 A0 01 00
while (readUByte().toUInt() != 0x4Au); // 08 01 //packet type 01=上传图片; 02=下载图片
// 12 [86 02]
val uKey = readBytes(readUVarInt().toInt())//128 // 08 00
// 10 [9B A4 D4 9A 0A]
discardExact(1)//52, id // 18 00
val imageId = ImageId(readString(readUVarInt().toInt()))//37 // 28 00
return Response.RequireUpload(uKey, imageId) // 38 F1 C0 A1 BF 05
} else { // 38 BB C8 E4 E2 0F
val toDiscard = readUByte().toInt() - 37 // 38 FB AE FA 9D 0A
// 38 E5 C6 8B CD 06
return if (toDiscard < 0) { // 40 BB 03 // ports
Response.OverFileSizeMax // 40 90 3F
} else { // 40 50
discardExact(toDiscard) // 40 BB 03
val imageId = ImageId(readString(37)) // 4A [80 01] 76 B2 58 23 B8 F6 B1 E6 AE D4 76 EC 3C 08 79 B1 DF 05 D5 C2 4A E0 CC F1 2F 26 4F D4 DC 44 5A 9A 16 A9 E4 22 EB 92 96 05 C3 C9 8F C5 5F 84 00 A3 4E 63 BE 76 F7 B9 7B 09 43 A6 14 EE C8 6D 6A 48 02 E3 9D 62 CD 42 3E 15 93 64 8F FC F5 88 50 74 6A 6A 03 C9 FE F0 96 EA 76 02 DC 4F 09 D0 F5 60 73 B2 62 8F 8B 11 06 BF 06 1B 18 00 FE B4 5E F3 12 72 F2 66 9C F5 01 97 1C 0A 5B 68 5B 85 ED 9C
Response.AlreadyExists(imageId) // 52 [25] 2F 37 38 62 36 34 64 63 32 2D 31 66 32 31 2D 34 33 62 38 2D 39 32 62 31 2D 61 30 35 30 35 30 34 30 35 66 65 32
// 5A [25] 2F 37 38 62 36 34 64 63 32 2D 31 66 32 31 2D 34 33 62 38 2D 39 32 62 31 2D 61 30 35 30 35 30 34 30 35 66 65 32
// 60 00 68 80 80 08
// 20 01
// 上传图片, 图片过大
//00 00 00 09 00 00 00 1D 12 07 98 01 01 A0 01 C7 01 08 01 12 19 08 00 18 C7 01 22 12 66 69 6C 65 20 73 69 7A 65 20 6F 76 65 72 20 6D 61 78
discardExact(3) // 00 00 00
if (readUByte().toUInt() == 0x09u) {
return ImageOverFileSizeMax
}
discardExact(2) //00 00
discardExact(2) //全长 (有 offset)
discardExact(1); discardExact(readUVarInt().toInt()) // 12 06 98 01 01 A0 01 00
check(readUByte().toUInt() == 0x08u)
when (val flag = readUByte().toUInt()) {
0x01u -> {
try {
while (readUByte().toUInt() != 0x4Au) readUVarLong()
val uKey = readBytes(readUVarInt().toInt())//128
while (readUByte().toUInt() != 0x52u) readUVarLong()
val imageId = ImageId(readString(readUVarInt().toInt()))//37
return ImageUKey(imageId, uKey)
} catch (e: EOFException) {
val toDiscard = readUByte().toInt() - 37
return if (toDiscard < 0) {
ImageOverFileSizeMax
} else {
discardExact(toDiscard)
val imageId = ImageId(readString(37))
ImageAlreadyExists(imageId)
}
}
}
0x02u -> {
//00 00 00 08 00 00
// [02 2B]
// 12 [06] 98 01 02 A0 01 00
// 08 02
// 1A [A6 04]
// 0A [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
// 18 00
// 32 [7B] 68 74 74 70 3A 2F 2F 36 31 2E 31 35 31 2E 32 33 34 2E 35 34 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 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 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 32 7C 68 74 74 70 3A 2F 2F 31 30 31 2E 32 32 37 2E 31 33 31 2E 36 37 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 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 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 32 7D 68 74 74 70 3A 2F 2F 31 35 37 2E 32 35 35 2E 31 39 32 2E 31 30 35 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 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 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 32 7C 68 74 74 70 3A 2F 2F 31 32 30 2E 32 34 31 2E 31 39 30 2E 34 31 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 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 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
// 3A 00 80 01 00
//00 00 00 08 00 00
// [02 29]
// 12 [06] 98 01 02 A0 01 00
// 08 02
// 1A [A4 04]
// 0A [25] 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64
// 18 00
// 32 [7A] 68 74 74 70 3A 2F 2F 31 30 31 2E 38 39 2E 33 39 2E 32 31 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64 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
// 32 7B 68 74 74 70 3A 2F 2F 36 31 2E 31 35 31 2E 31 38 33 2E 32 31 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64 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 32 7D 68 74 74 70 3A 2F 2F 31 35 37 2E 32 35 35 2E 31 39 32 2E 31 30 35 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64 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 32 7C 68 74 74 70 3A 2F 2F 31 32 30 2E 32 34 31 2E 31 39 30 2E 34 31 3A 38 30 2F 6F 66 66 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 2F 62 61 65 30 63 64 66 66 2D 65 33 34 30 2D 34 38 39 34 2D 39 37 36 65 2D 30 66 62 35 38 61 61 31 36 35 66 64 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 3A 00 80 01 00
discardExact(1)
discardExact(2)// [A4 04] 后文长度
check(readUByte().toUInt() == 0x0Au) { "Illegal identity. Required 0x0Au" }
val imageId = ImageId(readString(readUByte().toInt()))
check(readUByte().toUInt() == 0x18u) { "Illegal identity. Required 0x18u" }
check(readUShort().toUInt() == 0x0032u) { "Illegal identity. Required 0x0032u" }
val link = readUVarIntLVString()
discard()
ImageLink(link)
}
else -> error("Unknown FriendImageIdRequestPacket flag $flag")
} }
} }
}
} }
......
...@@ -35,6 +35,7 @@ fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort(). ...@@ -35,6 +35,7 @@ fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().
private inline fun <R> inline(block: () -> R): R = block() private inline fun <R> inline(block: () -> R): R = block()
@Suppress("DuplicatedCode")
fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMap<UInt, ByteArray> { fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMap<UInt, ByteArray> {
val map = mutableMapOf<UInt, ByteArray>() val map = mutableMapOf<UInt, ByteArray>()
var type: UShort = 0u var type: UShort = 0u
...@@ -66,6 +67,42 @@ fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMa ...@@ -66,6 +67,42 @@ fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMa
return map return map
} }
/**
* 读扁平的 tag-UVarInt map. 重复的 tag 将不会只保留最后一个
*
* tag: UByte
* value: UVarint
*/
@Suppress("DuplicatedCode")
fun Input.readFlatTUVarIntMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMap<UInt, UInt> {
val map = mutableMapOf<UInt, UInt>()
var type: UShort = 0u
while (inline {
try {
type = when (tagSize) {
1 -> readUByte().toUShort()
2 -> readUShort()
else -> error("Unsupported tag size: $tagSize")
}
} catch (e: EOFException) {
if (expectingEOF) {
return map
}
throw e
}
type
}.toUByte() != UByte.MAX_VALUE) {
if (map.containsKey(type.toUInt())) {
map[type.toUInt()] = this.readUVarInt()
} else {
map[type.toUInt()] = this.readUVarInt()
}
}
return map
}
fun Map<UInt, ByteArray>.printTLVMap(name: String) = fun Map<UInt, ByteArray>.printTLVMap(name: String) =
debugPrintln("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() }.mapKeys { it.key.toInt().toUShort().toUHexString() }) debugPrintln("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() }.mapKeys { it.key.toInt().toUShort().toUHexString() })
......
...@@ -120,7 +120,7 @@ object PacketDebugger { ...@@ -120,7 +120,7 @@ object PacketDebugger {
* 7. 运行完 `mov eax,dword ptr ss:[ebp+10]` * 7. 运行完 `mov eax,dword ptr ss:[ebp+10]`
* 8. 查看内存, `eax` 到 `eax+10` 的 16 字节就是 `sessionKey` * 8. 查看内存, `eax` 到 `eax+10` 的 16 字节就是 `sessionKey`
*/ */
val sessionKey: SessionKey = SessionKey("52 A2 10 EC C7 A8 80 44 FF 65 6C 70 43 A6 E7 73".hexToBytes()) val sessionKey: SessionKey = SessionKey("4D 60 96 90 90 67 02 A2 4E 1C 78 32 2E 30 2C 99".hexToBytes())
const val qq: UInt = 1040400290u const val qq: UInt = 1040400290u
val IgnoredPacketIdList: List<PacketId> = listOf( val IgnoredPacketIdList: List<PacketId> = listOf(
......
...@@ -4,7 +4,7 @@ import com.alibaba.fastjson.JSON ...@@ -4,7 +4,7 @@ import com.alibaba.fastjson.JSON
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.Image import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.upload import net.mamoe.mirai.message.uploadAsImage
import org.jsoup.Jsoup import org.jsoup.Jsoup
class GentleImage { class GentleImage {
...@@ -58,7 +58,7 @@ class GentleImage { ...@@ -58,7 +58,7 @@ class GentleImage {
.execute() .execute()
.bodyStream() .bodyStream()
} }
}?.upload(contact) ?: error("Unable to download image") }?.uploadAsImage(contact) ?: error("Unable to download image")
} }
} }
......
...@@ -12,11 +12,15 @@ import net.mamoe.mirai.event.Subscribable ...@@ -12,11 +12,15 @@ import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeMessages import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.Image import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.network.protocol.tim.packet.action.download
import net.mamoe.mirai.network.protocol.tim.packet.action.downloadAsByteArray import net.mamoe.mirai.network.protocol.tim.packet.action.downloadAsByteArray
import net.mamoe.mirai.network.protocol.tim.packet.action.downloadTo
import net.mamoe.mirai.network.protocol.tim.packet.event.FriendMessage import net.mamoe.mirai.network.protocol.tim.packet.event.FriendMessage
import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess
import net.mamoe.mirai.utils.currentTime import net.mamoe.mirai.utils.currentTime
import java.io.File import java.io.File
import javax.swing.filechooser.FileSystemView
import kotlin.random.Random import kotlin.random.Random
private fun readTestAccount(): BotAccount? { private fun readTestAccount(): BotAccount? {
...@@ -65,13 +69,24 @@ suspend fun main() { ...@@ -65,13 +69,24 @@ suspend fun main() {
if (this is FriendMessage) { if (this is FriendMessage) {
withContext(IO) { withContext(IO) {
reply(message[Image] + " downloading") reply(message[Image] + " downloading")
sender.downloadAsByteArray(message[Image]).inputStream()
.transferTo(File(System.getProperty("user.dir", "testDownloadedImage${currentTime}.png")).outputStream()) sender.downloadTo(message[Image], File(System.getProperty("user.dir"), "testDownloadedImage${currentTime}.png").also { it.createNewFile() })
reply(message[Image] + " downloaded") reply(message[Image].id.value + " downloaded")
} }
} }
} }
startsWith("上传图片", removePrefix = true) handler@{
val file = File(FileSystemView.getFileSystemView().homeDirectory, it)
if (!file.exists()) {
reply("图片不存在")
return@handler
}
reply("sent")
file.sendAsImageTo(subject)
}
startsWith("随机图片", removePrefix = true) { startsWith("随机图片", removePrefix = true) {
repeat(it.toIntOrNull() ?: 1) { repeat(it.toIntOrNull() ?: 1) {
GlobalScope.launch { GlobalScope.launch {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment