Commit 28859056 authored by Him188's avatar Him188

Improve image uploading

parent a2f35f5d
...@@ -50,7 +50,7 @@ subscribeAlways<FriendMessageEvent>{ ...@@ -50,7 +50,7 @@ subscribeAlways<FriendMessageEvent>{
- 成员权限, 昵称(10/18) - 成员权限, 昵称(10/18)
- 好友在线状态改变(10/14) - 好友在线状态改变(10/14)
- Android客户端上线/下线(10/18) - Android客户端上线/下线(10/18)
- 上传并发送图片(10/21) - 上传并发送好友/群图片(10/26)
## 使用方法 ## 使用方法
### 要求 ### 要求
......
...@@ -147,7 +147,7 @@ object MiraiServer { ...@@ -147,7 +147,7 @@ object MiraiServer {
Bot bot = new Bot(section); Bot bot = new Bot(section);
var state = bot.network.login$mirai_core().of(); var state = bot.network.login$mirai_core().of();
//bot.network.login$mirai_core().whenComplete((state, e) -> { //bot.network.login$mirai_core().whenComplete((state, e) -> {
if (state == LoginState.SUCCESS) { if (state == LoginState.REQUIRE_UPLOAD) {
Bot.instances.add(bot); Bot.instances.add(bot);
getLogger().logGreen(" Login Succeed"); getLogger().logGreen(" Login Succeed");
} else { } else {
......
...@@ -3,8 +3,10 @@ ...@@ -3,8 +3,10 @@
package net.mamoe.mirai package net.mamoe.mirai
import net.mamoe.mirai.contact.* 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.OutgoingPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.session
import net.mamoe.mirai.utils.BotNetworkConfiguration import net.mamoe.mirai.utils.BotNetworkConfiguration
/* /*
...@@ -22,6 +24,8 @@ suspend fun Bot.getGroup(internalId: GroupInternalId): Group = this.contacts.get ...@@ -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.groups: ContactList<Group> get() = this.contacts.groups
val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
inline fun <T> Bot.withSession(block: BotSession.() -> T): T = with(this.network.session) { block() }
//NetworkHandler //NetworkHandler
suspend fun Bot.sendPacket(packet: OutgoingPacket) = this.network.sendPacket(packet) suspend fun Bot.sendPacket(packet: OutgoingPacket) = this.network.sendPacket(packet)
......
...@@ -4,7 +4,7 @@ package net.mamoe.mirai.message ...@@ -4,7 +4,7 @@ package net.mamoe.mirai.message
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ 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 import net.mamoe.mirai.utils.ExternalImage
/** /**
......
...@@ -97,7 +97,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -97,7 +97,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
override suspend fun close(cause: Throwable?) { override suspend fun close(cause: Throwable?) {
super.close(cause) super.close(cause)
this.heartbeatJob?.cancel(CancellationException("handler closed")) this.heartbeatJob?.cancelChildren(CancellationException("handler closed"))
this.heartbeatJob?.join()//等待 cancel 完成 this.heartbeatJob?.join()//等待 cancel 完成
this.heartbeatJob = null this.heartbeatJob = null
......
...@@ -20,7 +20,7 @@ object TIMProtocol { ...@@ -20,7 +20,7 @@ object TIMProtocol {
).forEach { list.add(solveIpAddress(it)) } ).forEach { list.add(solveIpAddress(it)) }
list.toList() list.toList()
}()//不使用lazy是为了在启动时就加载. }()//不使用lazy, 在初始化时就加载.
const val head = "02" const val head = "02"
const val ver = "37 13" const val ver = "37 13"
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused") @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 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.network.protocol.tim.TIMProtocol 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.qqAccount
import net.mamoe.mirai.network.session import net.mamoe.mirai.network.session
import net.mamoe.mirai.qqAccount 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.io.*
import net.mamoe.mirai.utils.readUnsignedVarInt
import net.mamoe.mirai.utils.writeUVarInt
/** /**
* 上传图片 * 上传图片
...@@ -18,14 +25,16 @@ import net.mamoe.mirai.utils.io.* ...@@ -18,14 +25,16 @@ import net.mamoe.mirai.utils.io.*
suspend fun QQ.uploadImage(image: ExternalImage): ImageId = with(bot.network.session) { suspend fun QQ.uploadImage(image: ExternalImage): ImageId = with(bot.network.session) {
//SubmitImageFilenamePacket(account, account, "sdiovaoidsa.png", sessionKey).sendAndExpect<ServerSubmitImageFilenameResponsePacket>().join() //SubmitImageFilenamePacket(account, account, "sdiovaoidsa.png", sessionKey).sendAndExpect<ServerSubmitImageFilenameResponsePacket>().join()
DebugLogger.logPurple("正在上传好友图片, md5=${image.md5.toUHexString()}") 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) if (it.uKey != null)
require(httpPostFriendImage( require(
botAccount = bot.qqAccount, httpPostFriendImage(
botAccount = bot.qqAccount,
uKeyHex = it.uKey!!.toUHexString(""), uKeyHex = it.uKey!!.toUHexString(""),
imageInput = image.input, imageInput = image.input,
inputSize = image.inputSize inputSize = image.inputSize
)) )
)
it.imageId!! it.imageId!!
}.await() }.await()
} }
...@@ -43,12 +52,12 @@ suspend fun QQ.uploadImage(image: ExternalImage): ImageId = with(bot.network.ses ...@@ -43,12 +52,12 @@ suspend fun QQ.uploadImage(image: ExternalImage): ImageId = with(bot.network.ses
* 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00 * 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00
*/ */
@PacketId(0X01_BDu) @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( class SubmitImageFilenamePacket(
private val bot: UInt, private val bot: UInt,
private val target: UInt, private val target: UInt,
private val filename: String, private val filename: String,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : OutgoingPacket() { ) : OutgoingPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(bot) writeQQ(bot)
...@@ -99,7 +108,7 @@ class SubmitImageFilenamePacket( ...@@ -99,7 +108,7 @@ class SubmitImageFilenamePacket(
* - 服务器未存有, 返回一个 key 用于客户端上传 * - 服务器未存有, 返回一个 key 用于客户端上传
*/ */
@PacketId(0x03_52u) @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( class FriendImageIdRequestPacket(
private val botNumber: UInt, private val botNumber: UInt,
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
...@@ -187,13 +196,10 @@ class FriendImageIdRequestPacket( ...@@ -187,13 +196,10 @@ class FriendImageIdRequestPacket(
* 70 [80 14] * 70 [80 14]
* 78 [A0 0B]//84 * 78 [A0 0B]//84
*/ */
writeHex("00 00 00 07 00 00 00")
writeZero(3)
writeUShort(0x07_00u)
writeZero(1)
//proto //proto
val packet = buildPacket { writeUVarintLVPacket(lengthOffset = { it - 7 }) {
writeUByte(0x08u) writeUByte(0x08u)
writeUShort(0x01_12u) writeUShort(0x01_12u)
writeUShort(0x03_98u) writeUShort(0x03_98u)
...@@ -201,62 +207,85 @@ class FriendImageIdRequestPacket( ...@@ -201,62 +207,85 @@ class FriendImageIdRequestPacket(
writeUShort(0x08_01u) writeUShort(0x08_01u)
writeUShort(0x12_47u)//?似乎会变 writeUVarintLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) {
writeUByte(0x08u)
writeUByte(0x08u) writeUVarInt(botNumber)
writeUVarInt(botNumber)
writeUByte(0x10u)
writeUByte(0x10u) writeUVarInt(target)
writeUVarInt(target)
writeUShort(0x18_00u)
writeUShort(0x18_00u)
writeUByte(0x22u)
writeUByte(0x22u) writeUByte(0x10u)
writeUByte(0x10u) writeFully(image.md5)
writeFully(image.md5)
writeUByte(0x28u)
writeUByte(0x28u) writeUVarInt(image.inputSize.toUInt())
writeUVarInt(image.inputSize.toUInt())
writeUByte(0x32u)
writeUByte(0x32u) //长度应为1A
//长度应为1A writeUVarintLVPacket {
writeUVarintLVPacket { writeUShort(0x28_00u)
writeUShort(0x28_00u) writeUShort(0x46_00u)
writeUShort(0x46_00u) writeUShort(0x51_00u)
writeUShort(0x51_00u) writeUShort(0x56_00u)
writeUShort(0x56_00u) writeUShort(0x4B_00u)
writeUShort(0x4B_00u) writeUShort(0x41_00u)
writeUShort(0x41_00u) writeUShort(0x49_00u)
writeUShort(0x49_00u) writeUShort(0x25_00u)
writeUShort(0x25_00u) writeUShort(0x4B_00u)
writeUShort(0x4B_00u) writeUShort(0x24_00u)
writeUShort(0x24_00u) writeUShort(0x55_00u)
writeUShort(0x55_00u) writeUShort(0x30_00u)
writeUShort(0x30_00u) writeUShort(0x24_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()) //println(this.build().readBytes().toUHexString())
} }
} }
@PacketId(0x0352u) @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) { 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) { override fun decode() = with(input) {
//00 00 00 08 00 00 //00 00 00 08 00 00
...@@ -278,41 +307,27 @@ class FriendImageIdRequestPacket( ...@@ -278,41 +307,27 @@ class FriendImageIdRequestPacket(
discardExact(1)//52, id discardExact(1)//52, id
imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37 imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
state = State.REQUIRE_UPLOAD
//DebugLogger.logPurple("获得 uKey(${uKey!!.size})=${uKey!!.toUHexString()}") //DebugLogger.logPurple("获得 uKey(${uKey!!.size})=${uKey!!.toUHexString()}")
//DebugLogger.logPurple("获得 imageId(${imageId!!.value.length})=${imageId}") //DebugLogger.logPurple("获得 imageId(${imageId!!.value.length})=${imageId}")
} else { } else {
//服务器已经有这个图片了 //服务器已经有这个图片了
//DebugLogger.logPurple("服务器已有好友图片 ") //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 //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
imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37 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())
}
} }
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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 kotlinx.io.core.*
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId 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.qqAccount
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.httpPostGroupImage import net.mamoe.mirai.utils.httpPostGroupImage
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.readUnsignedVarInt
/**
* 图片文件过大
*/
class OverFileSizeMaxException : IllegalStateException()
/**
* 上传群图片
* 挂起直到上传完成或失败
* 失败后抛出 [OverFileSizeMaxException]
*/
suspend fun Group.uploadImage( suspend fun Group.uploadImage(
image: ExternalImage image: ExternalImage
) = with(bot.network.session) { ) = withSession {
GroupImageIdRequestPacket(bot.qqAccount, internalId, image, sessionKey) GroupImageIdRequestPacket(bot.qqAccount, internalId, image, sessionKey)
.sendAndExpect<GroupImageIdRequestPacket.Response, Unit> { .sendAndExpect<GroupImageIdRequestPacket.Response, Unit> {
if (it.uKey != null) { when (it.state) {
httpPostGroupImage( GroupImageIdRequestPacket.Response.State.REQUIRE_UPLOAD -> {
botAccount = bot.qqAccount, httpPostGroupImage(
groupInternalId = internalId, botAccount = bot.qqAccount,
imageInput = image.input, groupId = GroupId(id),
inputSize = image.inputSize, imageInput = image.input,
uKeyHex = it.uKey!!.toUHexString("") 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 * 获取 Image Id 和上传用的一个 uKey
*/ */
@PacketId(0x0388u) @PacketId(0x0388u)
@PacketVersion(date = "2019.10.20", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
class GroupImageIdRequestPacket( class GroupImageIdRequestPacket(
private val bot: UInt, private val bot: UInt,
private val groupInternalId: GroupInternalId, private val groupInternalId: GroupInternalId,
...@@ -51,8 +72,8 @@ class GroupImageIdRequestPacket( ...@@ -51,8 +72,8 @@ class GroupImageIdRequestPacket(
//小图B //小图B
// 00 00 00 07 00 00 00 // 00 00 00 07 00 00 00
// 5B 08 =后文长度-6 // 5B =后文长度-7
// 01 12 03 98 01 01 10 01 1A // 08 01 12 03 98 01 01 10 01 1A
// 57长度 // 57长度
// 08 FB D2 D8 94 02 // 08 FB D2 D8 94 02
// 10 A2 FF 8C F0 03 // 10 A2 FF 8C F0 03
...@@ -83,6 +104,28 @@ class GroupImageIdRequestPacket( ...@@ -83,6 +104,28 @@ class GroupImageIdRequestPacket(
// 78 03 // 78 03
// 80 01 00 // 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 //大图C
// 00 00 00 07 00 00 00 // 00 00 00 07 00 00 00
// 5E 08 =后文长度-6 // 5E 08 =后文长度-6
...@@ -144,11 +187,11 @@ class GroupImageIdRequestPacket( ...@@ -144,11 +187,11 @@ class GroupImageIdRequestPacket(
encryptAndWrite(sessionKey) { encryptAndWrite(sessionKey) {
writeHex("00 00 00 07 00 00 00") writeHex("00 00 00 07 00 00 00")
writeUVarintLVPacket(lengthOffset = { it - 6 }) { writeUVarintLVPacket(lengthOffset = { it - 7 }) {
writeByte(0x08) writeByte(0x08)
writeHex("01 12 03 98 01 01 10 01 1A") writeHex("01 12 03 98 01 01 10 01 1A")
writeUVarintLVPacket(lengthOffset = { it + 1 }) { writeUVarintLVPacket(lengthOffset = { it }) {
writeTUVarint(0x08u, groupInternalId.value) writeTUVarint(0x08u, groupInternalId.value)
writeTUVarint(0x10u, bot) writeTUVarint(0x10u, bot)
writeTV(0x1800u) writeTV(0x1800u)
...@@ -207,31 +250,6 @@ class GroupImageIdRequestPacket( ...@@ -207,31 +250,6 @@ class GroupImageIdRequestPacket(
}.readBytes().toUHexString()) }.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 { companion object {
...@@ -239,21 +257,51 @@ class GroupImageIdRequestPacket( ...@@ -239,21 +257,51 @@ class GroupImageIdRequestPacket(
} }
@PacketId(0x0388u) @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) { class Response(input: ByteReadPacket) : ResponsePacket(input) {
lateinit var state: State
/**
* 访问 HTTP API 时需要使用的一个 key. 128 位
*/
var uKey: ByteArray? = null 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) { override fun decode(): Unit = with(input) {
discardExact(6)//00 00 00 05 00 00 discardExact(6)//00 00 00 05 00 00
val length = remaining - 128 - 14 val length = remaining - 128 - 14
if (length < 0) { 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 return@with
} }
discardExact(length) discardExact(length)
uKey = readBytes(128) uKey = readBytes(128)
state = State.REQUIRE_UPLOAD
//} else { //} else {
// println("服务器已经有了这个图片") // println("服务器已经有了这个图片")
//println("后文 = ${readRemainingBytes().toUHexString()}") //println("后文 = ${readRemainingBytes().toUHexString()}")
...@@ -272,12 +320,4 @@ class GroupImageIdRequestPacket( ...@@ -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 // [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)
} }
\ No newline at end of file
...@@ -20,7 +20,7 @@ data class EventPacketIdentity( ...@@ -20,7 +20,7 @@ data class EventPacketIdentity(
val to: UInt,//对于好友消息, 这个是bot val to: UInt,//对于好友消息, 这个是bot
internal val uniqueId: IoBuffer//8 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) { fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {
......
...@@ -18,17 +18,26 @@ class ExternalImage( ...@@ -18,17 +18,26 @@ class ExternalImage(
val width: Int, val width: Int,
val height: Int, val height: Int,
val md5: ByteArray, val md5: ByteArray,
val format: String, imageFormat: String,
val input: Input, val input: Input,
val inputSize: Long val inputSize: Long
) { ) {
private val format: String
init {
if (imageFormat == "JPEG" || imageFormat == "jpeg") {//必须转换
this.format = "jpg"
} else {
this.format = imageFormat
}
}
/** /**
* 用于发送消息的 [ImageId] * 用于发送消息的 [ImageId]
*/ */
val groupImageId: ImageId by lazy { ImageId("{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format") } 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 { private operator fun ByteArray.get(range: IntRange): String = buildString {
......
...@@ -10,7 +10,7 @@ import io.ktor.http.HttpStatusCode ...@@ -10,7 +10,7 @@ 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.io.core.Input import kotlinx.io.core.Input
import net.mamoe.mirai.contact.GroupInternalId import net.mamoe.mirai.contact.GroupId
/** /**
* 时间戳 * 时间戳
...@@ -59,52 +59,95 @@ suspend fun httpPostFriendImage( ...@@ -59,52 +59,95 @@ suspend fun httpPostFriendImage(
uKeyHex: String, uKeyHex: String,
imageInput: Input, imageInput: Input,
inputSize: Long inputSize: Long
): Boolean = (httpClient.postImage(imageInput, inputSize, uKeyHex) { ): Boolean = (httpClient.postImage(
url { htcmd = "0x6ff0070",
parameters["htcmd"] = "0x6ff0070" uin = botAccount,
parameters["uin"] = botAccount.toLong().toString() groupcode = null,
} imageInput = imageInput,
inputSize = inputSize,
} as HttpStatusCode).value.also { println(it) } == 200 uKeyHex = uKeyHex
) 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") @Suppress("DuplicatedCode")
suspend fun httpPostGroupImage( suspend fun httpPostGroupImage(
botAccount: UInt, botAccount: UInt,
groupInternalId: GroupInternalId, groupId: GroupId,
uKeyHex: String, uKeyHex: String,
imageInput: Input, imageInput: Input,
inputSize: Long inputSize: Long
): Boolean = (httpClient.postImage(imageInput, inputSize, uKeyHex) { ): Boolean = (httpClient.postImage(
url { htcmd = "0x6ff0071",
parameters["htcmd"] = "0x6ff0071" uin = botAccount,
parameters["uin"] = botAccount.toLong().toString() groupcode = groupId,
parameters["groupcode"] = groupInternalId.value.toLong().toString() imageInput = imageInput,
} inputSize = inputSize,
} as HttpStatusCode).value.also { println(it) } == 200 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( private suspend inline fun <reified T> HttpClient.postImage(
htcmd: String,
uin: UInt,
groupcode: GroupId?,
imageInput: Input, imageInput: Input,
inputSize: Long, inputSize: Long,
uKeyHex: String, uKeyHex: String
block: HttpRequestBuilder.() -> Unit = {}
): T = post { ): T = post {
url { url {
protocol = URLProtocol.HTTP protocol = URLProtocol.HTTP
host = "htdata2.qq.com" host = "htdata2.qq.com"
path("cgi-bin/httpconn") 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["ver"] = "5603"
parameters["filezise"] = inputSize.toString() parameters["filesize"] = inputSize.toString()
parameters["range"] = 0.toString() parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex parameters["ukey"] = uKeyHex
userAgent("QQClient") userAgent("QQClient")
} }
block()
println(url.buildString())
configureBody(inputSize, imageInput) configureBody(inputSize, imageInput)
} }
......
...@@ -6,6 +6,7 @@ import kotlinx.io.charsets.Charset ...@@ -6,6 +6,7 @@ import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets import kotlinx.io.charsets.Charsets
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String import kotlinx.io.core.String
import kotlinx.io.core.use
import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmOverloads
@JvmOverloads @JvmOverloads
...@@ -34,6 +35,6 @@ fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString ...@@ -34,6 +35,6 @@ fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString
fun ByteArray.toReadPacket() = ByteReadPacket(this) 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) fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)
\ No newline at end of file
...@@ -5,9 +5,7 @@ package net.mamoe.mirai.utils.io ...@@ -5,9 +5,7 @@ package net.mamoe.mirai.utils.io
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol 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.action.CanAddFriendPacket import net.mamoe.mirai.network.protocol.tim.packet.action.*
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.event.ServerEventPacket import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.* import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
......
...@@ -6,6 +6,8 @@ import kotlinx.io.core.ByteReadPacket ...@@ -6,6 +6,8 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
import java.lang.reflect.Field import java.lang.reflect.Field
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.jvm.kotlinProperty
internal object PacketNameFormatter { internal object PacketNameFormatter {
@JvmStatic @JvmStatic
...@@ -22,7 +24,7 @@ internal object PacketNameFormatter { ...@@ -22,7 +24,7 @@ internal object PacketNameFormatter {
} }
} }
private object IgnoreIdList : List<String> by listOf( private object IgnoreIdListEquals : List<String> by listOf(
"idHex", "idHex",
"id", "id",
"packetId", "packetId",
...@@ -32,18 +34,32 @@ private object IgnoreIdList : List<String> by listOf( ...@@ -32,18 +34,32 @@ private object IgnoreIdList : List<String> by listOf(
"idByteArray", "idByteArray",
"encoded", "encoded",
"packet", "packet",
"EMPTY_ID_HEX",
"input",
"sequenceId",
"output",
"bot",
"UninitializedByteReadPacket",
"sessionKey"
)
private object IgnoreIdListInclude : List<String> by listOf(
"Companion", "Companion",
"EMPTY_ID_HEX", "EMPTY_ID_HEX",
"input", "input",
"output", "output",
"this\$0", "this\$",
"\$\$delegatedProperties", "\$\$delegatedProperties",
"UninitializedByteReadPacket", "UninitializedByteReadPacket",
"sessionKey" "\$FU",
"RefVolatile"
) )
@Suppress("UNCHECKED_CAST")
internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.idHexString})") + this::class.java.allDeclaredFields 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(", ", "{", "}") { .joinToString(", ", "{", "}") {
it.isAccessible = true it.isAccessible = true
it.name.replace("\$delegate", "") + "=" + it.get(this).let { value -> it.name.replace("\$delegate", "") + "=" + it.get(this).let { value ->
...@@ -55,6 +71,7 @@ internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjust ...@@ -55,6 +71,7 @@ internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjust
//is ByteReadPacket -> value.copy().readBytes().toUHexString() //is ByteReadPacket -> value.copy().readBytes().toUHexString()
is IoBuffer -> "[IoBuffer(${value.readRemaining})]" is IoBuffer -> "[IoBuffer(${value.readRemaining})]"
is Lazy<*> -> "[Lazy]" is Lazy<*> -> "[Lazy]"
is ReadWriteProperty<*, *> -> (value as ReadWriteProperty<Packet, *>).getValue(this, it.kotlinProperty!!)
else -> value.toString() else -> value.toString()
} }
} }
......
...@@ -2,18 +2,18 @@ ...@@ -2,18 +2,18 @@
import net.mamoe.mirai.contact.groupId import net.mamoe.mirai.contact.groupId
import net.mamoe.mirai.contact.toInternalId 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.hexToBytes
import net.mamoe.mirai.utils.io.readRemainingBytes import net.mamoe.mirai.utils.io.readRemainingBytes
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
import net.mamoe.mirai.utils.toMiraiImage import net.mamoe.mirai.utils.toExternalImage
import java.io.File import java.io.File
import javax.imageio.ImageIO import javax.imageio.ImageIO
val sessionKey: ByteArray = "F1 ED F2 BC 55 17 7B FE CC CC F3 08 D1 8D A7 0E".hexToBytes() val sessionKey: ByteArray = "F1 ED F2 BC 55 17 7B FE CC CC F3 08 D1 8D A7 0E".hexToBytes()
fun main() = println({ 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()) // File("C:\\Users\\Him18\\Desktop\\test2.jpg").writeBytes(image.fileData.readBytes())
GroupImageIdRequestPacket( GroupImageIdRequestPacket(
......
import java.io.File
fun main() {
val file = File("C:\\Users\\Him18\\Desktop\\lemon.png")
println(file.inputStream().readAllBytes().size)
println(file.length())
}
\ No newline at end of file
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
apply plugin: "kotlin" apply plugin: "kotlin"
apply plugin: "java" apply plugin: "java"
javafx {
version = "11"
modules = [ 'javafx.controls' ]
}
dependencies { dependencies {
implementation project(':mirai-core') 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.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io', version: kotlinxio_version api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io', version: kotlinxio_version
api group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_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) { tasks.withType(JavaCompile) {
options.encoding = "UTF-8" options.encoding = "UTF-8"
} }
@file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant") @file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant")
import net.mamoe.mirai.utils.io.printCompareHex import net.mamoe.mirai.utils.io.printCompareHex
import java.util.*
fun main() { 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")) // 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) { while (true) {
println("Hex1: ") println("Hex1: ")
val hex1 = scanner.nextLine() val hex1 = readLine()!!
println("Hex2: ") 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("\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(printCompareHex(hex1, hex2))
println() println()
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
import Main.localIp import Main.localIp
import Main.qq import Main.qq
import Main.sessionKey import Main.sessionKey
import jpcap.JpcapCaptor import com.sun.jna.Platform
import jpcap.packet.IPPacket
import jpcap.packet.UDPPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
...@@ -20,6 +18,12 @@ import net.mamoe.mirai.utils.decryptBy ...@@ -20,6 +18,12 @@ import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.toUHexString 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 ...@@ -30,11 +34,31 @@ import net.mamoe.mirai.utils.toUHexString
object Main { object Main {
@JvmStatic @JvmStatic
fun main(args: Array<String>) { fun main(args: Array<String>) {
val devices = JpcapCaptor.getDeviceList() val nif: PcapNetworkInterface = Pcaps.findAllDevs()[0]
val jpcap: JpcapCaptor? println(nif.name + "(" + nif.description + ")")
val caplen = 4096
val promiscCheck = true val handle = nif.openLive(65536, PromiscuousMode.PROMISCUOUS, 3000)
jpcap = JpcapCaptor.openDevice(devices[0], caplen, promiscCheck, 50)
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) { while (true) {
assert(jpcap != null) assert(jpcap != null)
val pk = jpcap!!.packet ?: continue val pk = jpcap!!.packet ?: continue
...@@ -65,6 +89,8 @@ object Main { ...@@ -65,6 +89,8 @@ object Main {
//pk.dst_ip //pk.dst_ip
} }
} }
*/
} }
/** /**
...@@ -79,9 +105,9 @@ object Main { ...@@ -79,9 +105,9 @@ object Main {
* 6. 运行到 `mov eax,dword ptr ss:[ebp+10]` * 6. 运行到 `mov eax,dword ptr ss:[ebp+10]`
* 7. 查看内存, 从 `eax` 开始的 16 bytes 便是 `sessionKey` * 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 qq: UInt = 1040400290u
const val localIp = "192.168.3." const val localIp = "192.168.3.10"
fun dataReceived(data: ByteArray) { fun dataReceived(data: ByteArray) {
//println("raw = " + data.toUHexString()) //println("raw = " + data.toUHexString())
...@@ -159,12 +185,14 @@ object Main { ...@@ -159,12 +185,14 @@ object Main {
return@read return@read
} }
println("fixVer2=" + when (val flag = readByte().toInt()) { println(
2 -> byteArrayOf(2) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1) "fixVer2=" + when (val flag = readByte().toInt()) {
4 -> byteArrayOf(4) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1 + 8)//8个0 2 -> byteArrayOf(2) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1)
0 -> byteArrayOf(0) + readBytes(2) 4 -> byteArrayOf(4) + readBytes(TIMProtocol.fixVer2.hexToBytes().size - 1 + 8)//8个0
else -> error("unknown fixVer2 flag=$flag. Remaining =${readBytes().toUHexString()}") 0 -> byteArrayOf(0) + readBytes(2)
}.toUHexString()) 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 //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 { ...@@ -191,8 +219,8 @@ object Main {
try { try {
messageData.read { messageData.read {
discardExact( discardExact(
4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2 4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2
+ 1 + 1
) )
val chain = readMessageChain() val chain = readMessageChain()
println(chain) println(chain)
......
...@@ -17,8 +17,8 @@ import net.mamoe.mirai.message.ImageId ...@@ -17,8 +17,8 @@ import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.PlainText import net.mamoe.mirai.message.PlainText
import net.mamoe.mirai.message.firstOrNull import net.mamoe.mirai.message.firstOrNull
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingRawPacket 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.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.uploadImage
import net.mamoe.mirai.network.session import net.mamoe.mirai.network.session
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
...@@ -99,25 +99,27 @@ suspend fun main() { ...@@ -99,25 +99,27 @@ suspend fun main() {
} }
"上传好友图片" in it.message -> withTimeoutOrNull(5000) { "上传好友图片" in it.message -> withTimeoutOrNull(5000) {
val filename = it.message.toString().substringAfter("上传好友图片")
val id = 1040400290u.qq() 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) it.reply(id.value)
delay(1000) delay(100)
it.reply(Image(id)) it.reply(Image(id))
} }
"上传群图片" in it.message -> withTimeoutOrNull(5000) { "上传群图片" in it.message -> withTimeoutOrNull(5000) {
val filename = it.message.toString().substringAfter("上传群图片")
val image = File( val image = File(
"C:\\Users\\Him18\\Desktop\\${it.message.toString().substringAfter("上传群图片")}" "C:\\Users\\Him18\\Desktop\\$filename"
).toMiraiImage() ).toExternalImage()
580266363u.group().uploadImage(image) 920503456u.group().uploadImage(image)
it.reply(image.groupImageId.value) it.reply(image.groupImageId.value)
delay(1000) delay(100)
580266363u.group().sendMessage(Image(image.groupImageId)) 920503456u.group().sendMessage(Image(image.groupImageId))
} }
"发群图片" in it.message -> { "发群图片" in it.message -> {
580266363u.group().sendMessage(Image(ImageId(it.message.toString().substringAfter("发群图片")))) 920503456u.group().sendMessage(Image(ImageId(it.message.toString().substringAfter("发群图片"))))
} }
"发好友图片" in it.message -> { "发好友图片" in it.message -> {
......
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