Improve image uploading

This commit is contained in:
Him188 2019-10-26 15:54:02 +08:00
parent a2f35f5d2d
commit 28859056a1
20 changed files with 395 additions and 211 deletions

View File

@ -50,7 +50,7 @@ subscribeAlways<FriendMessageEvent>{
- 成员权限, 昵称(10/18)
- 好友在线状态改变(10/14)
- Android客户端上线/下线(10/18)
- 上传并发送图片(10/21)
- 上传并发送好友/群图片(10/26)
## 使用方法
### 要求

View File

@ -147,7 +147,7 @@ object MiraiServer {
Bot bot = new Bot(section);
var state = bot.network.login$mirai_core().of();
//bot.network.login$mirai_core().whenComplete((state, e) -> {
if (state == LoginState.SUCCESS) {
if (state == LoginState.REQUIRE_UPLOAD) {
Bot.instances.add(bot);
getLogger().logGreen(" Login Succeed");
} else {

View File

@ -3,8 +3,10 @@
package net.mamoe.mirai
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.session
import net.mamoe.mirai.utils.BotNetworkConfiguration
/*
@ -22,6 +24,8 @@ suspend fun Bot.getGroup(internalId: GroupInternalId): Group = this.contacts.get
val Bot.groups: ContactList<Group> get() = this.contacts.groups
val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
inline fun <T> Bot.withSession(block: BotSession.() -> T): T = with(this.network.session) { block() }
//NetworkHandler
suspend fun Bot.sendPacket(packet: OutgoingPacket) = this.network.sendPacket(packet)

View File

@ -4,7 +4,7 @@ package net.mamoe.mirai.message
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.protocol.tim.packet.FriendImageIdRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket
import net.mamoe.mirai.utils.ExternalImage
/**

View File

@ -97,7 +97,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
override suspend fun close(cause: Throwable?) {
super.close(cause)
this.heartbeatJob?.cancel(CancellationException("handler closed"))
this.heartbeatJob?.cancelChildren(CancellationException("handler closed"))
this.heartbeatJob?.join()//等待 cancel 完成
this.heartbeatJob = null

View File

@ -20,7 +20,7 @@ object TIMProtocol {
).forEach { list.add(solveIpAddress(it)) }
list.toList()
}()//不使用lazy是为了在启动时就加载.
}()//不使用lazy, 在初始化时就加载.
const val head = "02"
const val ver = "37 13"

View File

@ -1,16 +1,23 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused")
package net.mamoe.mirai.network.protocol.tim.packet
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.qqAccount
import net.mamoe.mirai.network.session
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.*
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
/**
* 上传图片
@ -18,14 +25,16 @@ import net.mamoe.mirai.utils.io.*
suspend fun QQ.uploadImage(image: ExternalImage): ImageId = with(bot.network.session) {
//SubmitImageFilenamePacket(account, account, "sdiovaoidsa.png", sessionKey).sendAndExpect<ServerSubmitImageFilenameResponsePacket>().join()
DebugLogger.logPurple("正在上传好友图片, md5=${image.md5.toUHexString()}")
return FriendImageIdRequestPacket(this.qqAccount, sessionKey, this.qqAccount, image).sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> {
return FriendImageIdRequestPacket(this.qqAccount, sessionKey, id, image).sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> {
if (it.uKey != null)
require(httpPostFriendImage(
botAccount = bot.qqAccount,
require(
httpPostFriendImage(
botAccount = bot.qqAccount,
uKeyHex = it.uKey!!.toUHexString(""),
imageInput = image.input,
inputSize = image.inputSize
))
imageInput = image.input,
inputSize = image.inputSize
)
)
it.imageId!!
}.await()
}
@ -43,12 +52,12 @@ suspend fun QQ.uploadImage(image: ExternalImage): ImageId = with(bot.network.ses
* 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00
*/
@PacketId(0X01_BDu)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
@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
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)
@ -99,7 +108,7 @@ class SubmitImageFilenamePacket(
* - 服务器未存有, 返回一个 key 用于客户端上传
*/
@PacketId(0x03_52u)
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
class FriendImageIdRequestPacket(
private val botNumber: UInt,
private val sessionKey: ByteArray,
@ -187,13 +196,10 @@ class FriendImageIdRequestPacket(
* 70 [80 14]
* 78 [A0 0B]//84
*/
writeZero(3)
writeUShort(0x07_00u)
writeZero(1)
writeHex("00 00 00 07 00 00 00")
//proto
val packet = buildPacket {
writeUVarintLVPacket(lengthOffset = { it - 7 }) {
writeUByte(0x08u)
writeUShort(0x01_12u)
writeUShort(0x03_98u)
@ -201,62 +207,85 @@ class FriendImageIdRequestPacket(
writeUShort(0x08_01u)
writeUShort(0x12_47u)//?似乎会变
writeUVarintLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) {
writeUByte(0x08u)
writeUVarInt(botNumber)
writeUByte(0x08u)
writeUVarInt(botNumber)
writeUByte(0x10u)
writeUVarInt(target)
writeUByte(0x10u)
writeUVarInt(target)
writeUShort(0x18_00u)
writeUShort(0x18_00u)
writeUByte(0x22u)
writeUByte(0x10u)
writeFully(image.md5)
writeUByte(0x22u)
writeUByte(0x10u)
writeFully(image.md5)
writeUByte(0x28u)
writeUVarInt(image.inputSize.toUInt())
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)
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())
}
writeUShort(0x38_01u)
writeUShort(0x48_00u)
writeUByte(0x70u)
writeUVarInt(image.width.toUInt())
writeUByte(0x78u)
writeUVarInt(image.height.toUInt())
}
writeShort((packet.remaining - 7).toShort())//why?
writePacket(packet)
//println(this.build().readBytes().toUHexString())
}
}
@PacketId(0x0352u)
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
class Response(input: ByteReadPacket) : ResponsePacket(input) {
var uKey: ByteArray? = null//最终可能为null
var imageId: ImageId? = null//最终不会为null
/**
* 访问 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
@ -278,41 +307,27 @@ class FriendImageIdRequestPacket(
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
// 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
discardExact(60)
discardExact(1)//52, id
imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
//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 = State.OVER_FILE_SIZE_MAX
} else {
discardExact(toDiscard)
imageId = ImageId(readString(37))
state = State.ALREADY_EXISTS
}
}
}
}
}
fun main() {
//GlobalSysTemp:II%E]PA}OVFK]61EGGF$356.jpg
//实际文件名为 II%E]PA}OVFK]61EGGF$356.jpg
println(SubmitImageFilenamePacket(
1994701021u,
1040400290u,
"testfilename.png",
"99 82 67 D4 62 20 CA 5D 81 F8 6F 83 EE 8A F7 68".hexToBytes()
).packet.readBytes().toUHexString())
println("01ee6426-5ff1-4cf0-8278-e8634d2909e".toByteArray().toUHexString())
"5A 25 2F 36 61 38 35 32 66 64 65 2D 38 32 38 35 2D 34 33 35 31 2D 61 65 65 38 2D 35 34 65 37 35 65 65 32 65 61 37 63 60 00 68 80 80 08 20 01"
.printStringFromHex()
"25 2F ".hexToBytes().read {
println(readUnsignedVarInt())
}
}

View File

@ -1,41 +1,62 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
package net.mamoe.mirai.network.protocol.tim.packet.action
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.network.session
import net.mamoe.mirai.contact.withSession
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.hexToBytes
import net.mamoe.mirai.utils.httpPostGroupImage
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.readUnsignedVarInt
/**
* 图片文件过大
*/
class OverFileSizeMaxException : IllegalStateException()
/**
* 上传群图片
* 挂起直到上传完成或失败
* 失败后抛出 [OverFileSizeMaxException]
*/
suspend fun Group.uploadImage(
image: ExternalImage
) = with(bot.network.session) {
) = withSession {
GroupImageIdRequestPacket(bot.qqAccount, internalId, image, sessionKey)
.sendAndExpect<GroupImageIdRequestPacket.Response, Unit> {
if (it.uKey != null) {
httpPostGroupImage(
botAccount = bot.qqAccount,
groupInternalId = internalId,
imageInput = image.input,
inputSize = image.inputSize,
uKeyHex = it.uKey!!.toUHexString("")
)
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()
}
}.await()
}.join()
}
/**
* 获取 Image Id 和上传用的一个 uKey
*/
@PacketId(0x0388u)
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
class GroupImageIdRequestPacket(
private val bot: UInt,
private val groupInternalId: GroupInternalId,
@ -51,8 +72,8 @@ class GroupImageIdRequestPacket(
//小图B
// 00 00 00 07 00 00 00
// 5B 08 =后文长度-6
// 01 12 03 98 01 01 10 01 1A
// 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
@ -83,6 +104,28 @@ class GroupImageIdRequestPacket(
// 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
@ -144,11 +187,11 @@ class GroupImageIdRequestPacket(
encryptAndWrite(sessionKey) {
writeHex("00 00 00 07 00 00 00")
writeUVarintLVPacket(lengthOffset = { it - 6 }) {
writeUVarintLVPacket(lengthOffset = { it - 7 }) {
writeByte(0x08)
writeHex("01 12 03 98 01 01 10 01 1A")
writeUVarintLVPacket(lengthOffset = { it + 1 }) {
writeUVarintLVPacket(lengthOffset = { it }) {
writeTUVarint(0x08u, groupInternalId.value)
writeTUVarint(0x10u, bot)
writeTV(0x1800u)
@ -207,31 +250,6 @@ class GroupImageIdRequestPacket(
}.readBytes().toUHexString())
*/
}
//以下仅支持中等大小图片
/*
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 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")
}
*/
}
companion object {
@ -239,21 +257,51 @@ class GroupImageIdRequestPacket(
}
@PacketId(0x0388u)
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173")
@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()}")
@ -272,12 +320,4 @@ class GroupImageIdRequestPacket(
// [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
}
}
}
fun main() {
("A2 FF 8C F0 03").hexToBytes().read {
println(readUnsignedVarInt())
}
println(0x40)
}

View File

@ -20,7 +20,7 @@ data class EventPacketIdentity(
val to: UInt,//对于好友消息, 这个是bot
internal val uniqueId: IoBuffer//8
) {
override fun toString(): String = "(from=$from, to=$to)"
override fun toString(): String = "($from->$to)"
}
fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {

View File

@ -18,17 +18,26 @@ class ExternalImage(
val width: Int,
val height: Int,
val md5: ByteArray,
val format: String,
imageFormat: String,
val input: Input,
val inputSize: Long
) {
private val format: String
init {
if (imageFormat == "JPEG" || imageFormat == "jpeg") {//必须转换
this.format = "jpg"
} else {
this.format = imageFormat
}
}
/**
* 用于发送消息的 [ImageId]
*/
val groupImageId: ImageId by lazy { ImageId("{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format") }
override fun toString(): String = "[ExternalImage(${width}x${height} $format)]"
override fun toString(): String = "[ExternalImage(${width}x$height $format)]"
}
private operator fun ByteArray.get(range: IntRange): String = buildString {

View File

@ -10,7 +10,7 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol
import io.ktor.http.userAgent
import kotlinx.io.core.Input
import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.contact.GroupId
/**
* 时间戳
@ -59,52 +59,95 @@ suspend fun httpPostFriendImage(
uKeyHex: String,
imageInput: Input,
inputSize: Long
): Boolean = (httpClient.postImage(imageInput, inputSize, uKeyHex) {
url {
parameters["htcmd"] = "0x6ff0070"
parameters["uin"] = botAccount.toLong().toString()
}
): Boolean = (httpClient.postImage(
htcmd = "0x6ff0070",
uin = botAccount,
groupcode = null,
imageInput = imageInput,
inputSize = inputSize,
uKeyHex = uKeyHex
) as HttpStatusCode).value.also { println(it) } == 200
} as HttpStatusCode).value.also { println(it) } == 200
/*
httpPostFriendImageOld(uKeyHex, botAccount, imageInput.readBytes().toReadPacket())
expect suspend fun httpPostFriendImageOld(
uKeyHex: String,
botNumber: UInt,
imageData: ByteReadPacket
): Boolean
*/
/**
* 上传群图片
*/
@Suppress("DuplicatedCode")
suspend fun httpPostGroupImage(
botAccount: UInt,
groupInternalId: GroupInternalId,
groupId: GroupId,
uKeyHex: String,
imageInput: Input,
inputSize: Long
): Boolean = (httpClient.postImage(imageInput, inputSize, uKeyHex) {
url {
parameters["htcmd"] = "0x6ff0071"
parameters["uin"] = botAccount.toLong().toString()
parameters["groupcode"] = groupInternalId.value.toLong().toString()
}
} as HttpStatusCode).value.also { println(it) } == 200
): Boolean = (httpClient.postImage(
htcmd = "0x6ff0071",
uin = botAccount,
groupcode = groupId,
imageInput = imageInput,
inputSize = inputSize,
uKeyHex = uKeyHex
) as HttpStatusCode).value.also { println(it) } == 200
/* = (httpClient.post {
url {
protocol = URLProtocol.HTTP
host = "htdata2.qq.com"
path("cgi-bin/httpconn")
parameters["htcmd"] = "0x6ff0071"
parameters["ver"] = "5603"
parameters["term"] = "pc"
parameters["ukey"] = uKeyHex
parameters["filesize"] = inputSize.toString()
parameters["range"] = 0.toString()
parameters["uin"] = botAccount.toLong().toString()
parameters["groupcode"] = groupId.value.toLong().toString()
// userAgent("QQClient")
}
println(url.buildString())
body = ByteArrayContent(imageInput.readBytes())
//configureBody(inputSize, imageInput)
} as HttpStatusCode).value.also { println(it) } == 200*/
@Suppress("SpellCheckingInspection")
private suspend inline fun <reified T> HttpClient.postImage(
htcmd: String,
uin: UInt,
groupcode: GroupId?,
imageInput: Input,
inputSize: Long,
uKeyHex: String,
block: HttpRequestBuilder.() -> Unit = {}
uKeyHex: String
): T = post {
url {
protocol = URLProtocol.HTTP
host = "htdata2.qq.com"
path("cgi-bin/httpconn")
parameters["htcmd"] = htcmd
parameters["uin"] = uin.toLong().toString()
if (groupcode != null) {
parameters["groupcode"] = groupcode.value.toLong().toString()
}
parameters["term"] = "pc"
parameters["ver"] = "5603"
parameters["filezise"] = inputSize.toString()
parameters["filesize"] = inputSize.toString()
parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex
userAgent("QQClient")
}
block()
println(url.buildString())
configureBody(inputSize, imageInput)
}

View File

@ -6,6 +6,7 @@ import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String
import kotlinx.io.core.use
import kotlin.jvm.JvmOverloads
@JvmOverloads
@ -34,6 +35,6 @@ fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString
fun ByteArray.toReadPacket() = ByteReadPacket(this)
fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().run(t)
fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().use(t)
fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)

View File

@ -5,9 +5,7 @@ package net.mamoe.mirai.utils.io
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.action.CanAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.*
import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.utils.MiraiLogger

View File

@ -6,6 +6,8 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.utils.io.toUHexString
import java.lang.reflect.Field
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.jvm.kotlinProperty
internal object PacketNameFormatter {
@JvmStatic
@ -22,7 +24,7 @@ internal object PacketNameFormatter {
}
}
private object IgnoreIdList : List<String> by listOf(
private object IgnoreIdListEquals : List<String> by listOf(
"idHex",
"id",
"packetId",
@ -32,18 +34,32 @@ private object IgnoreIdList : List<String> by listOf(
"idByteArray",
"encoded",
"packet",
"Companion",
"EMPTY_ID_HEX",
"input",
"sequenceId",
"output",
"this\$0",
"\$\$delegatedProperties",
"bot",
"UninitializedByteReadPacket",
"sessionKey"
)
private object IgnoreIdListInclude : List<String> by listOf(
"Companion",
"EMPTY_ID_HEX",
"input",
"output",
"this\$",
"\$\$delegatedProperties",
"UninitializedByteReadPacket",
"\$FU",
"RefVolatile"
)
@Suppress("UNCHECKED_CAST")
internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.idHexString})") + this::class.java.allDeclaredFields
.filterNot { it.name in IgnoreIdList /*|| "delegate" in it.name|| "$" in it.name */ }
.filterNot { field ->
IgnoreIdListEquals.any { field.name.replace("\$delegate", "") == it } || IgnoreIdListInclude.any { it in field.name }
}
.joinToString(", ", "{", "}") {
it.isAccessible = true
it.name.replace("\$delegate", "") + "=" + it.get(this).let { value ->
@ -55,6 +71,7 @@ internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjust
//is ByteReadPacket -> value.copy().readBytes().toUHexString()
is IoBuffer -> "[IoBuffer(${value.readRemaining})]"
is Lazy<*> -> "[Lazy]"
is ReadWriteProperty<*, *> -> (value as ReadWriteProperty<Packet, *>).getValue(this, it.kotlinProperty!!)
else -> value.toString()
}
}

View File

@ -2,18 +2,18 @@
import net.mamoe.mirai.contact.groupId
import net.mamoe.mirai.contact.toInternalId
import net.mamoe.mirai.network.protocol.tim.packet.GroupImageIdRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.GroupImageIdRequestPacket
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.readRemainingBytes
import net.mamoe.mirai.utils.io.toUHexString
import net.mamoe.mirai.utils.toMiraiImage
import net.mamoe.mirai.utils.toExternalImage
import java.io.File
import javax.imageio.ImageIO
val sessionKey: ByteArray = "F1 ED F2 BC 55 17 7B FE CC CC F3 08 D1 8D A7 0E".hexToBytes()
fun main() = println({
val image = ImageIO.read(File("C:\\Users\\Him18\\Desktop\\test2.gif").readBytes().inputStream()).toMiraiImage("png")
val image = ImageIO.read(File("C:\\Users\\Him18\\Desktop\\test2.gif").readBytes().inputStream()).toExternalImage("png")
// File("C:\\Users\\Him18\\Desktop\\test2.jpg").writeBytes(image.fileData.readBytes())
GroupImageIdRequestPacket(

View File

@ -0,0 +1,9 @@
import java.io.File
fun main() {
val file = File("C:\\Users\\Him18\\Desktop\\lemon.png")
println(file.inputStream().readAllBytes().size)
println(file.length())
}

View File

@ -1,16 +1,36 @@
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
apply plugin: "kotlin"
apply plugin: "java"
javafx {
version = "11"
modules = [ 'javafx.controls' ]
}
dependencies {
implementation project(':mirai-core')
compile files('./lib/jpcap.jar')
implementation files('./lib/jpcap.jar')
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io', version: kotlinxio_version
api group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version
// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-javafx
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-javafx', version: '1.3.2'
implementation 'org.pcap4j:pcap4j-distribution:1.8.2'
implementation 'no.tornado:tornadofx:1.7.17'
}
mainClassName = 'Application'
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}

View File

@ -1,17 +1,15 @@
@file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant")
import net.mamoe.mirai.utils.io.printCompareHex
import java.util.*
fun main() {
// println(HexComparator.printColorize("00 00 00 25 00 08 00 02 00 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD E7 86 74 F2 64 55 AD 9A EB 2F B9 DF F1 7F 8C 28 00 0B 78 14 5D A2 F5 CB 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A2 F5 CA 9D 26 CB 5E 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E4 BD A0 E5 A5 BD 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00"))
val scanner = Scanner(System.`in`)
while (true) {
println("Hex1: ")
val hex1 = scanner.nextLine()
val hex1 = readLine()!!
println("Hex2: ")
val hex2 = scanner.nextLine()
val hex2 = readLine()!!
println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
println(printCompareHex(hex1, hex2))
println()

View File

@ -3,9 +3,7 @@
import Main.localIp
import Main.qq
import Main.sessionKey
import jpcap.JpcapCaptor
import jpcap.packet.IPPacket
import jpcap.packet.UDPPacket
import com.sun.jna.Platform
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt
@ -20,6 +18,12 @@ import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.toUHexString
import org.pcap4j.core.BpfProgram.BpfCompileMode
import org.pcap4j.core.PacketListener
import org.pcap4j.core.PcapNetworkInterface
import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode
import org.pcap4j.core.Pcaps
/**
* 抓包分析器.
@ -30,11 +34,31 @@ import net.mamoe.mirai.utils.toUHexString
object Main {
@JvmStatic
fun main(args: Array<String>) {
val devices = JpcapCaptor.getDeviceList()
val jpcap: JpcapCaptor?
val caplen = 4096
val promiscCheck = true
jpcap = JpcapCaptor.openDevice(devices[0], caplen, promiscCheck, 50)
val nif: PcapNetworkInterface = Pcaps.findAllDevs()[0]
println(nif.name + "(" + nif.description + ")")
val handle = nif.openLive(65536, PromiscuousMode.PROMISCUOUS, 3000)
handle.setFilter("src $localIp && udp port 8000", BpfCompileMode.OPTIMIZE)
val listener = PacketListener {
println(it.rawData.toUHexString())
println()
}
handle.loop(Int.MAX_VALUE, listener)
val ps = handle.stats
println("ps_recv: " + ps.numPacketsReceived)
println("ps_drop: " + ps.numPacketsDropped)
println("ps_ifdrop: " + ps.numPacketsDroppedByIf)
if (Platform.isWindows()) {
println("bs_capt: " + ps.numPacketsCaptured)
}
handle.close()
/*
while (true) {
assert(jpcap != null)
val pk = jpcap!!.packet ?: continue
@ -65,6 +89,8 @@ object Main {
//pk.dst_ip
}
}
*/
}
/**
@ -79,9 +105,9 @@ object Main {
* 6. 运行到 `mov eax,dword ptr ss:[ebp+10]`
* 7. 查看内存, `eax` 开始的 16 bytes 便是 `sessionKey`
*/
val sessionKey: ByteArray = "1D 1E 71 68 B9 41 FD 5B F3 5A 3F 71 87 B5 86 CB".hexToBytes()
val sessionKey: ByteArray = "0D D7 C8 06 C6 C1 40 FE A8 3B CF 81 EE DF 69 83".hexToBytes()
const val qq: UInt = 1040400290u
const val localIp = "192.168.3."
const val localIp = "192.168.3.10"
fun dataReceived(data: ByteArray) {
//println("raw = " + data.toUHexString())
@ -159,12 +185,14 @@ object Main {
return@read
}
println("fixVer2=" + when (val flag = readByte().toInt()) {
2 -> byteArrayOf(2) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1)
4 -> byteArrayOf(4) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1 + 8)//8个0
0 -> byteArrayOf(0) + readBytes(2)
else -> error("unknown fixVer2 flag=$flag. Remaining =${readBytes().toUHexString()}")
}.toUHexString())
println(
"fixVer2=" + when (val flag = readByte().toInt()) {
2 -> byteArrayOf(2) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1)
4 -> byteArrayOf(4) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1 + 8)//8个0
0 -> byteArrayOf(0) + readBytes(2)
else -> error("unknown fixVer2 flag=$flag. Remaining =${readBytes().toUHexString()}")
}.toUHexString()
)
//39 27 DC E2 04 00 00 00 00 00 00 00 1E 0E 89 00 00 01 05 0F 05 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 3E 03 3F A2 00 00 00 00 00 00 00 00 00 00 00
@ -191,8 +219,8 @@ object Main {
try {
messageData.read {
discardExact(
4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2
+ 1
4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2
+ 1
)
val chain = readMessageChain()
println(chain)

View File

@ -17,8 +17,8 @@ import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.PlainText
import net.mamoe.mirai.message.firstOrNull
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingRawPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.uploadImage
import net.mamoe.mirai.network.session
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.*
@ -99,25 +99,27 @@ suspend fun main() {
}
"上传好友图片" in it.message -> withTimeoutOrNull(5000) {
val filename = it.message.toString().substringAfter("上传好友图片")
val id = 1040400290u.qq()
.uploadImage(File("C:\\Users\\Him18\\Desktop\\${it.message.toString().substringAfter("上传好友图片")}").toMiraiImage())
.uploadImage(File("C:\\Users\\Him18\\Desktop\\$filename").toExternalImage())
it.reply(id.value)
delay(1000)
delay(100)
it.reply(Image(id))
}
"上传群图片" in it.message -> withTimeoutOrNull(5000) {
val filename = it.message.toString().substringAfter("上传群图片")
val image = File(
"C:\\Users\\Him18\\Desktop\\${it.message.toString().substringAfter("上传群图片")}"
).toMiraiImage()
580266363u.group().uploadImage(image)
"C:\\Users\\Him18\\Desktop\\$filename"
).toExternalImage()
920503456u.group().uploadImage(image)
it.reply(image.groupImageId.value)
delay(1000)
580266363u.group().sendMessage(Image(image.groupImageId))
delay(100)
920503456u.group().sendMessage(Image(image.groupImageId))
}
"发群图片" in it.message -> {
580266363u.group().sendMessage(Image(ImageId(it.message.toString().substringAfter("发群图片"))))
920503456u.group().sendMessage(Image(ImageId(it.message.toString().substringAfter("发群图片"))))
}
"发好友图片" in it.message -> {