Commit fdf50e4d authored by Him188's avatar Him188

Image uploading

parent 4d346a2c
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
...@@ -172,7 +174,7 @@ object MiraiServer { ...@@ -172,7 +174,7 @@ object MiraiServer {
get() { get() {
for (it in qqList.split("\n").dropLastWhile { it.isEmpty() }.toTypedArray()) { for (it in qqList.split("\n").dropLastWhile { it.isEmpty() }.toTypedArray()) {
val strings = it.split("----").dropLastWhile { it.isEmpty() }.toTypedArray() val strings = it.split("----").dropLastWhile { it.isEmpty() }.toTypedArray()
val bot = Bot(BotAccount(strings[0].toLong(), strings[1]), MiraiLogger) val bot = Bot(BotAccount(strings[0].toUInt(), strings[1]), MiraiLogger)
if (runBlocking { bot.login() } === LoginResult.SUCCESS) { if (runBlocking { bot.login() } === LoginResult.SUCCESS) {
bot.logGreen("Login succeed") bot.logGreen("Login succeed")
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
...@@ -86,16 +88,16 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) { ...@@ -86,16 +88,16 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
* 通过群号码获取群对象. * 通过群号码获取群对象.
* 注意: 在并发调用时, 这个方法并不是原子的. * 注意: 在并发调用时, 这个方法并不是原子的.
*/ */
fun getQQ(account: Long): QQ = qqs.getOrPut(account) { QQ(this@Bot, account) } fun getQQ(account: UInt): QQ = qqs.getOrPut(account) { QQ(this@Bot, account) }
/** /**
* 通过群号码获取群对象. * 通过群号码获取群对象.
* 注意: 在并发调用时, 这个方法并不是原子的. * 注意: 在并发调用时, 这个方法并不是原子的.
*/ */
fun getGroupByNumber(groupNumber: Long): Group = groups.getOrPut(groupNumber) { Group(this@Bot, groupNumber) } fun getGroupByNumber(groupNumber: UInt): Group = groups.getOrPut(groupNumber) { Group(this@Bot, groupNumber) }
fun getGroupById(groupId: Long): Group { fun getGroupById(groupId: UInt): Group {
return getGroupByNumber(Group.groupIdToNumber(groupId)) return getGroupByNumber(Group.groupIdToNumber(groupId))
} }
} }
......
...@@ -17,14 +17,14 @@ import net.mamoe.mirai.utils.toUHexString ...@@ -17,14 +17,14 @@ import net.mamoe.mirai.utils.toUHexString
*/ */
//Contacts //Contacts
fun Bot.getQQ(number: Long): QQ = this.contacts.getQQ(number) fun Bot.getQQ(number: Long): QQ = this.contacts.getQQ(number.toUInt())
fun Bot.getQQ(number: UInt): QQ = getQQ(number.toLong()) fun Bot.getQQ(number: UInt): QQ = this.contacts.getQQ(number)
fun Bot.getGroupByNumber(number: Long): Group = this.contacts.getGroupByNumber(number) fun Bot.getGroupByNumber(number: Long): Group = this.contacts.getGroupByNumber(number.toUInt())
fun Bot.getGroupByNumber(number: UInt): Group = getGroupByNumber(number.toLong()) fun Bot.getGroupByNumber(number: UInt): Group = this.contacts.getGroupByNumber(number)
fun Bot.getGroupById(number: Long): Group = this.contacts.getGroupById(number) fun Bot.getGroupById(number: UInt): Group = this.contacts.getGroupById(number)
val Bot.groups: ContactList<Group> get() = this.contacts.groups val Bot.groups: ContactList<Group> get() = this.contacts.groups
...@@ -39,7 +39,7 @@ suspend fun Bot.login(configuration: BotNetworkConfiguration.() -> Unit): LoginR ...@@ -39,7 +39,7 @@ suspend fun Bot.login(configuration: BotNetworkConfiguration.() -> Unit): LoginR
suspend fun Bot.login(): LoginResult = this.network.login(BotNetworkConfiguration.Default) suspend fun Bot.login(): LoginResult = this.network.login(BotNetworkConfiguration.Default)
//BotAccount //BotAccount
val Bot.qqAccount: Long get() = this.account.account val Bot.qqAccount: UInt get() = this.account.account
//logging //logging
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
...@@ -11,7 +13,7 @@ import net.mamoe.mirai.message.toChain ...@@ -11,7 +13,7 @@ import net.mamoe.mirai.message.toChain
* *
* @author Him188moe * @author Him188moe
*/ */
abstract class PlatformContactBase internal constructor(val bot: Bot, val number: Long) { abstract class PlatformContactBase internal constructor(val bot: Bot, val number: UInt) {
abstract suspend fun sendMessage(message: MessageChain) abstract suspend fun sendMessage(message: MessageChain)
...@@ -33,4 +35,4 @@ abstract class PlatformContactBase internal constructor(val bot: Bot, val number ...@@ -33,4 +35,4 @@ abstract class PlatformContactBase internal constructor(val bot: Bot, val number
* 在不同平台可能有不同的实现. * 在不同平台可能有不同的实现.
* 如在 JVM, suspend 调用不便, [Contact] 中有简化调用的 `blocking`() 和 `async` * 如在 JVM, suspend 调用不便, [Contact] 中有简化调用的 `blocking`() 和 `async`
*/ */
expect sealed class Contact(bot: Bot, number: Long) : PlatformContactBase expect sealed class Contact(bot: Bot, number: UInt) : PlatformContactBase
\ No newline at end of file \ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
...@@ -13,8 +15,8 @@ import net.mamoe.mirai.utils.ContactList ...@@ -13,8 +15,8 @@ import net.mamoe.mirai.utils.ContactList
* *
* @author Him188moe * @author Him188moe
*/ */
expect class Group(bot: Bot, number: Long) : Contact { expect class Group(bot: Bot, number: UInt) : Contact {
val groupId: Long val groupId: UInt
val members: ContactList<QQ> val members: ContactList<QQ>
override suspend fun sendMessage(message: MessageChain) override suspend fun sendMessage(message: MessageChain)
...@@ -23,7 +25,7 @@ expect class Group(bot: Bot, number: Long) : Contact { ...@@ -23,7 +25,7 @@ expect class Group(bot: Bot, number: Long) : Contact {
companion object companion object
} }
fun Group.Companion.groupNumberToId(number: Long): Long {//求你别出错 fun Group.Companion.groupNumberToId(number: UInt): UInt {//求你别出错
val left: Long = number.toString().let { val left: Long = number.toString().let {
if (it.length < 6) { if (it.length < 6) {
return@groupNumberToId number return@groupNumberToId number
...@@ -36,84 +38,84 @@ fun Group.Companion.groupNumberToId(number: Long): Long {//求你别出错 ...@@ -36,84 +38,84 @@ fun Group.Companion.groupNumberToId(number: Long): Long {//求你别出错
return when (left) { return when (left) {
in 1..10 -> { in 1..10 -> {
((left + 202).toString() + right.toString()).toLong() ((left + 202).toString() + right.toString()).toUInt()
} }
in 11..19 -> { in 11..19 -> {
((left + 469).toString() + right.toString()).toLong() ((left + 469).toString() + right.toString()).toUInt()
} }
in 20..66 -> { in 20..66 -> {
((left + 208).toString() + right.toString()).toLong() ((left + 208).toString() + right.toString()).toUInt()
} }
in 67..156 -> { in 67..156 -> {
((left + 1943).toString() + right.toString()).toLong() ((left + 1943).toString() + right.toString()).toUInt()
} }
in 157..209 -> { in 157..209 -> {
((left + 199).toString() + right.toString()).toLong() ((left + 199).toString() + right.toString()).toUInt()
} }
in 210..309 -> { in 210..309 -> {
((left + 389).toString() + right.toString()).toLong() ((left + 389).toString() + right.toString()).toUInt()
} }
in 310..499 -> { in 310..499 -> {
((left + 349).toString() + right.toString()).toLong() ((left + 349).toString() + right.toString()).toUInt()
} }
else -> number else -> number
} }
} }
fun Group.Companion.groupIdToNumber(id: Long): Long {//求你别出错 fun Group.Companion.groupIdToNumber(id: UInt): UInt {//求你别出错
var left: Long = id.toString().let { var left: UInt = id.toString().let {
if (it.length < 6) { if (it.length < 6) {
return@groupIdToNumber id return@groupIdToNumber id
} }
it.substring(0 until it.length - 6).toLong() it.substring(0 until it.length - 6).toUInt()
} }
return when (left) { return when (left.toInt()) {
in 203..212 -> { in 203..212 -> {
val right: Long = id.toString().let { val right: UInt = id.toString().let {
it.substring(it.length - 6).toLong() it.substring(it.length - 6).toUInt()
} }
((left - 202).toString() + right.toString()).toLong() ((left - 202u).toString() + right.toString()).toUInt()
} }
in 480..488 -> { in 480..488 -> {
val right: Long = id.toString().let { val right: UInt = id.toString().let {
it.substring(it.length - 6).toLong() it.substring(it.length - 6).toUInt()
} }
((left - 469).toString() + right.toString()).toLong() ((left - 469u).toString() + right.toString()).toUInt()
} }
in 2100..2146 -> { in 2100..2146 -> {
val right: Long = id.toString().let { val right: UInt = id.toString().let {
it.substring(it.length - 7).toLong() it.substring(it.length - 7).toUInt()
} }
left = left.toString().substring(0 until 3).toLong() left = left.toString().substring(0 until 3).toUInt()
((left - 208).toString() + right.toString()).toLong() ((left - 208u).toString() + right.toString()).toUInt()
} }
in 2010..2099 -> { in 2010..2099 -> {
val right: Long = id.toString().let { val right: UInt = id.toString().let {
it.substring(it.length - 6).toLong() it.substring(it.length - 6).toUInt()
} }
((left - 1943).toString() + right.toString()).toLong() ((left - 1943u).toString() + right.toString()).toUInt()
} }
in 2147..2199 -> { in 2147..2199 -> {
val right: Long = id.toString().let { val right: UInt = id.toString().let {
it.substring(it.length - 7).toLong() it.substring(it.length - 7).toUInt()
} }
left = left.toString().substring(0 until 3).toLong() left = left.toString().substring(0 until 3).toUInt()
((left - 199).toString() + right.toString()).toLong() ((left - 199u).toString() + right.toString()).toUInt()
} }
in 4100..4199 -> { in 4100..4199 -> {
val right: Long = id.toString().let { val right: UInt = id.toString().let {
it.substring(it.length - 7).toLong() it.substring(it.length - 7).toUInt()
} }
left = left.toString().substring(0 until 3).toLong() left = left.toString().substring(0 until 3).toUInt()
((left - 389).toString() + right.toString()).toLong() ((left - 389u).toString() + right.toString()).toUInt()
} }
in 3800..3989 -> { in 3800..3989 -> {
val right: Long = id.toString().let { val right: UInt = id.toString().let {
it.substring(it.length - 7).toLong() it.substring(it.length - 7).toUInt()
} }
left = left.toString().substring(0 until 3).toLong() left = left.toString().substring(0 until 3).toUInt()
((left - 349).toString() + right.toString()).toLong() ((left - 349u).toString() + right.toString()).toUInt()
} }
else -> id else -> id
} }
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
...@@ -14,7 +16,7 @@ import net.mamoe.mirai.message.MessageChain ...@@ -14,7 +16,7 @@ import net.mamoe.mirai.message.MessageChain
* *
* @author Him188moe * @author Him188moe
*/ */
expect class QQ(bot: Bot, number: Long) : Contact { expect class QQ(bot: Bot, number: UInt) : Contact {
override suspend fun sendMessage(message: MessageChain) override suspend fun sendMessage(message: MessageChain)
override suspend fun sendXMLMessage(message: String) override suspend fun sendXMLMessage(message: String)
......
@file:Suppress("MemberVisibilityCanBePrivate", "unused") @file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.message package net.mamoe.mirai.message
...@@ -87,21 +87,24 @@ data class PlainText(override val stringValue: String) : Message() { ...@@ -87,21 +87,24 @@ data class PlainText(override val stringValue: String) : Message() {
// ==================================== Image ==================================== // ==================================== Image ====================================
/** /**
* 图片消息. * 图片消息. 在发送时将会区分群图片和好友图片发送.
* 由接收消息时构建, 可直接发送 * 由接收消息时构建, 可直接发送
* *
* @param id 类似 `{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg`. 群的是大写id, 好友的是小写id * @param id 类似 `/01ee6426-5ff1-4cf0-8278-e8634d2909ef`. 群的是大写id, 好友的是小写id
* @param filename 文件名. 这将决定图片的显示
*/ */
data class Image(val id: String) : Message() { data class Image(val id: ImageId, val filename: String = "") : Message() {
override val stringValue: String = "[$id]" override val stringValue: String = "[${id.value}]"
} }
inline class ImageId(val value: String)
// ==================================== At ==================================== // ==================================== At ====================================
/** /**
* At 一个人 * At 一个人
*/ */
data class At(val targetQQ: Long) : Message() { data class At(val targetQQ: UInt) : Message() {
constructor(target: QQ) : this(target.number) constructor(target: QQ) : this(target.number)
override val stringValue: String = "[@$targetQQ]" override val stringValue: String = "[@$targetQQ]"
......
...@@ -8,7 +8,8 @@ enum class MessageType(val value: UByte) { ...@@ -8,7 +8,8 @@ enum class MessageType(val value: UByte) {
PLAIN_TEXT(0x01u), PLAIN_TEXT(0x01u),
AT(0x06u), AT(0x06u),
FACE(0x02u), FACE(0x02u),
IMAGE(0x03u),//may be 0x06? GROUP_IMAGE(0x03u),
FRIEND_IMAGE(0x06u),
; ;
......
...@@ -37,8 +37,8 @@ internal fun IoBuffer.parseMessageImage0x06(): Image { ...@@ -37,8 +37,8 @@ internal fun IoBuffer.parseMessageImage0x06(): Image {
val suffix = readString(filenameLength).substringAfter(".") val suffix = readString(filenameLength).substringAfter(".")
discardExact(8)//03 00 04 00 00 02 9C 04 discardExact(8)//03 00 04 00 00 02 9C 04
val length = readShort()//=27 val length = readShort()//=27
discardExact(1)//无意义符号? return Image(ImageId(readString(length)), "")//todo 文件名解析
return Image("{${readString(length - 2/*去掉首尾各一个无意义符号*/)}}.$suffix") //return Image("{${readString(length)}}.$suffix")
} }
...@@ -56,8 +56,8 @@ fun main() { ...@@ -56,8 +56,8 @@ fun main() {
} }
internal fun IoBuffer.parseMessageImage0x03(): Image { internal fun IoBuffer.parseMessageImage0x03(): Image {
discardExact(1) discardExact(1) //TODO 这里的文件名解析
return Image(String(readLVByteArray())) return Image(ImageId(String(readLVByteArray())), "")
} }
internal fun ByteReadPacket.readMessage(): Message? { internal fun ByteReadPacket.readMessage(): Message? {
...@@ -67,7 +67,7 @@ internal fun ByteReadPacket.readMessage(): Message? { ...@@ -67,7 +67,7 @@ internal fun ByteReadPacket.readMessage(): Message? {
return try { return try {
when (messageType) { when (messageType) {
//todo 在每个parse里面都 discard 了第一byte. //todo 在每个parse里面都 discard 了第一 byte.
0x01 -> sectionData.parsePlainText() 0x01 -> sectionData.parsePlainText()
0x02 -> sectionData.parseMessageFace() 0x02 -> sectionData.parseMessageFace()
0x03 -> sectionData.parseMessageImage0x03() 0x03 -> sectionData.parseMessageImage0x03()
...@@ -128,14 +128,14 @@ fun ByteReadPacket.readMessageChain(): MessageChain { ...@@ -128,14 +128,14 @@ fun ByteReadPacket.readMessageChain(): MessageChain {
return chain return chain
} }
fun MessageChain.toPacket(): ByteReadPacket = buildPacket { fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
this@toPacket.forEach { message -> this@toPacket.forEach { message ->
writePacket(with(message) { writePacket(with(message) {
when (this) { when (this) {
is Face -> buildPacket { is Face -> buildPacket {
writeUByte(MessageType.FACE.value) writeUByte(MessageType.FACE.value)
writeLVPacket { writeShortLVPacket {
writeShort(1) writeShort(1)
writeUByte(id.id) writeUByte(id.id)
...@@ -150,31 +150,88 @@ fun MessageChain.toPacket(): ByteReadPacket = buildPacket { ...@@ -150,31 +150,88 @@ fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
is At -> throw UnsupportedOperationException("At is not supported now but is expecting to be supported") is At -> throw UnsupportedOperationException("At is not supported now but is expecting to be supported")
is Image -> buildPacket { is Image -> buildPacket {
writeUByte(MessageType.IMAGE.value) if (forGroup) {
writeUByte(MessageType.GROUP_IMAGE.value)
writeLVPacket {
writeByte(0x02) /*
writeLVString(id) * 00 00 06 00 F3 02 00 1B 28 52 49 5F 36 31 28 32 52 59 4B 59 43 40 37 59 29 58 29 39 29 42 49 2E 67 69 66 03 00 04 00 00 11 90 04 00 25 2F 35 37 34 63 34 32 34 30 2D 30 31 33 66 2D 34 35 39 38 2D 61 37 32 34 2D 30 36 65 66 35 36 39 39 30 64 30 62 14 00 04 03 00 00 00 0B 00 00 18 00 25 2F 35 37 34 63 34 32 34 30 2D 30 31 33 66 2D 34 35 39 38 2D 61 37 32 34 2D 30 36 65 66 35 36 39 39 30 64 30 62 19 00 04 00 00 00 2D 1A 00 04 00 00 00 2D FF 00 63 16 20 20 39 39 31 30 20 38 38 31 44 42 20 20 20 20 20 20 34 34 39 36 65 33 39 46 37 36 35 33 32 45 31 41 42 35 43 41 37 38 36 44 37 41 35 31 33 38 39 32 32 35 33 38 35 2E 67 69 66 66 2F 35 37 34 63 34 32 34 30 2D 30 31 33 66 2D 34 35 39 38 2D 61 37 32 34 2D 30 36 65 66 35 36 39 39 30 64 30 62 41
writeHex("04 00 " + * 00 00 06 00 F3 02 00 1B 46 52 25 46 60 30 59 4F 4A 5A 51 48 31 46 4A 53 4C 51 4C 4A 33 46 31 2E 6A 70 67 03 00 04 00 00 02 A2 04 00 25 2F 37 33 38 33 35 38 36 37 2D 38 64 65 31 2D 34 65 30 66 2D 61 33 36 35 2D 34 39 62 30 33 39 63 34 61 39 31 66 14 00 04 03 00 00 00 0B 00 00 18 00 25 2F 37 33 38 33 35 38 36 37 2D 38 64 65 31 2D 34 65 30 66 2D 61 33 36 35 2D 34 39 62 30 33 39 63 34 61 39 31 66 19 00 04 00 00 00 4E 1A 00 04 00 00 00 23 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 36 37 34 65 31 46 42 34 43 32 35 45 42 34 46 45 31 32 45 34 46 33 42 42 38 31 39 31 33 37 42 44 39 39 30 39 2E 6A 70 67 66 2F 37 33 38 33 35 38 36 37 2D 38 64 65 31 2D 34 65 30 66 2D 61 33 36 35 2D 34 39 62 30 33 39 63 34 61 39 31 66 41
"04 9B 53 B0 08 " + */
"05 00 " +
"04 D9 8A 5A 70 " + writeShortLVPacket {
"06 00 " + //todo
"04 00 00 00 50 " + writeByte(0x02)
"07 00 " + writeShortLVString(id.value.substring(1..35))
"01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 11 00 00 00 15 00 04 00 00 02 BC 16 00 04 00 00 02 BC 18 00 04 00 00 7D 5E FF 00 5C 15 36 20 39 32 6B 41 31 43 39 62 35 33 62 30 30 38 64 39 38 61 35 61 37 30 20") writeHex("04 00 " +
writeHex("20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20") "04 9B 53 B0 08 " +
writeStringUtf8(id) "05 00 " +
writeByte(0x41) "04 D9 8A 5A 70 " +
"06 00 " +
"04 00 00 00 50 " +
"07 00 " +
"01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 11 00 00 00 15 00 04 00 00 02 BC 16 00 04 00 00 02 BC 18 00 04 00 00 7D 5E FF 00 5C 15 36 20 39 32 6B 41 31 43 39 62 35 33 62 30 30 38 64 39 38 61 35 61 37 30 20")
writeHex("20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20")
writeStringUtf8(id.value)
writeByte(0x41)
}
} else {
writeUByte(MessageType.FRIEND_IMAGE.value)
// 00 00 06 00 F3 02
// 00 1B 24 5B 56 54 4A 38 60 4C 5A 4E 46 7D 53 39 4F 52 36 25 45 60 42 55 53 2E 6A 70 67
// 03 00 04 00 01 41 1B 04
// 00 25 2F 65 61 37 30 30 61 38 33 2D 38 38 38 62 2D 34 66 37 31 2D 61 62 64 31 2D 63 33 38 64 63 62 64 31 61 65 36 31
// 14 00 04 00 00 00 00 0B 00 00 18
// 00 25 2F 65 61 37 30 30 61 38 33 2D 38 38 38 62 2D 34 66 37 31 2D 61 62 64 31 2D 63 33 38 64 63 62 64 31 61 65 36 31
// 19 00 04 00 00 03 74 1A 00 04 00 00 02 BE FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20
///38 32 32 30 33 65 39 36 30 46 42 35 44 37 46 42 33 39 46 34 39 39 31 37 46 34 37 33 44 41 45 31 37 30 32 46 44 31 2E 6A 70 67 66
// 2F 65 61 37 30 30 61 38 33 2D 38 38 38 62 2D 34 66 37 31 2D 61 62 64 31 2D 63 33 38 64 63 62 64 31 61 65 36 31 41
// 00 00 06 00 F3 02
// 00 1B 7B 48 29 47 52 53 31 29 50 24 5A 42 28 4F 35 43 44 4B 45 31 35 7B 37 2E 70 6E 67
// 03 00 04 00 00 6F 36 04
// 00 25 2F 35 65 63 32 34 63 37 62 2D 34 30 32 39 2D 34 61 39 33 2D 62 63 66 35 2D 34 31 38 34 35 32 65 39 32 33 31 64
// 14 00 04 00 00 00 00 0B 00 00 18
// 00 25 2F 35 65 63 32 34 63 37 62 2D 34 30 32 39 2D 34 61 39 33 2D 62 63 66 35 2D 34 31 38 34 35 32 65 39 32 33 31 64
// 19 00 04 00 00 04 14 1A 00 04 00 00 02 77 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 41 42 20 20 20 20 20
///32 38 34 37 30 65 35 42 37 44 45 37 36 41 34 41 44 44 31 37 43 46 39 32 39 38 33 43 46 30 41 43 45 35 42 34 33 39 2E 70 6E 67 66
// 2F 35 65 63 32 34 63 37 62 2D 34 30 32 39 2D 34 61 39 33 2D 62 63 66 35 2D 34 31 38 34 35 32 65 39 32 33 31 64 41
/*
* 00 00 06 00 F3 02
* 00 1B 46 52 25 46 60 30 59 4F 4A 5A 51 48 31 46 4A 53 4C 51 4C 4A 33 46 31 2E 6A 70 67
* 03 00 04 00 00 02 A2 04
* 00 25 2F 37 33 38 33 35 38 36 37 2D 38 64 65 31 2D 34 65 30 66 2D 61 33 36 35 2D 34 39 62 30 33 39 63 34 61 39 31 66
* 14 00 04 03 00 00 00 0B 00 00 18
* 00 25 2F 37 33 38 33 35 38 36 37 2D 38 64 65 31 2D 34 65 30 66 2D 61 33 36 35 2D 34 39 62 30 33 39 63 34 61 39 31 66
* 19 00 04 00 00 00 4E 1A 00 04 00 00 00 23 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20
**36 37 34 65 31 46 42 34 43 32 35 45 42 34 46 45 31 32 45 34 46 33 42 42 38 31 39 31 33 37 42 44 39 39 30 39 2E 6A 70 67 66
* 2F 37 33 38 33 35 38 36 37 2D 38 64 65 31 2D 34 65 30 66 2D 61 33 36 35 2D 34 39 62 30 33 39 63 34 61 39 31 66 41
*/
writeShortLVPacket {
//todo
writeByte(0x02)
//"46 52 25 46 60 30 59 4F 4A 5A 51 48 31 46 4A 53 4C 51 4C 4A 33 46 31 2E 6A 70 67".hexToBytes().stringOfWitch()
// writeShortLVString(filename)//图片文件名 FR%F`0YOJZQH1FJSLQLJ3F1.jpg
writeShortLVString(getRandomString(24, 'A'..'Z') + ".jpg")//图片文件名 FR%F`0YOJZQH1FJSLQLJ3F1.jpg
writeHex("03 00 04 00 00 02 A2 04")
writeShortLVString(id.value)
writeHex("14 00 04 03 00 00 00 0B 00 00 18")
writeShortLVString(id.value)
writeHex("19 00 04 00 00 00 4E 1A 00 04 00 00 00 23 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 " +
"36 37 34 65 31 46 42 34 43 32 35 45 42 34 46 45 31 32 45 34 46 33 42 42 38 31 39 31 33 37 42 44 39 39 30 39 2E 6A 70 67 66 ")
//36 37 34 65 31 46 42 34 43 32 35 45 42 34 46 45 31 32 45 34 46 33 42 42 38 31 39 31 33 37 42 44 39 39 30 39 2E 6A 70 67 66
writeStringUtf8(id.value)
writeUByte(0x41u)
}
} }
} }
is PlainText -> buildPacket { is PlainText -> buildPacket {
writeUByte(MessageType.PLAIN_TEXT.value) writeUByte(MessageType.PLAIN_TEXT.value)
writeLVPacket { writeShortLVPacket {
writeByte(0x01) writeByte(0x01)
writeLVString(stringValue) writeShortLVString(stringValue)
} }
} }
......
...@@ -70,7 +70,7 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> { ...@@ -70,7 +70,7 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> {
* @see [BotSession.sendAndExpect] 发送并期待一个包 * @see [BotSession.sendAndExpect] 发送并期待一个包
* @see [TemporaryPacketHandler] 临时包处理器 * @see [TemporaryPacketHandler] 临时包处理器
*/ */
suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*, *>)
/** /**
* 发送数据包 * 发送数据包
......
@file:Suppress("MemberVisibilityCanBePrivate", "unused") @file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network package net.mamoe.mirai.network
import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
...@@ -65,10 +65,10 @@ class BotSession( ...@@ -65,10 +65,10 @@ class BotSession(
* @param handlerTemporary 处理器. * @param handlerTemporary 处理器.
*/ */
//@JvmSynthetic //@JvmSynthetic
suspend inline fun <reified P : ServerPacket> sendAndExpect(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableJob { suspend inline fun <reified P : ServerPacket, R> sendAndExpect(handlerTemporary: TemporaryPacketHandler<P, R>.() -> Unit): CompletableDeferred<R> {
val job = coroutineContext[Job].takeIf { it != null }?.let { Job(it) } ?: Job() val deferred: CompletableDeferred<R> = coroutineContext[Job].takeIf { it != null }?.let { CompletableDeferred<R>(it) } ?: CompletableDeferred()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, job, this).also(handlerTemporary)) this.bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this).also(handlerTemporary))
return job return deferred
} }
/** /**
...@@ -86,13 +86,13 @@ class BotSession( ...@@ -86,13 +86,13 @@ class BotSession(
* @param P 期待的包 * @param P 期待的包
* @param handler 处理期待的包 * @param handler 处理期待的包
*/ */
suspend inline fun <reified P : ServerPacket> ClientPacket.sendAndExpect(noinline handler: suspend (P) -> Unit): CompletableJob { suspend inline fun <reified P : ServerPacket, R> ClientPacket.sendAndExpect(noinline handler: suspend (P) -> R): CompletableDeferred<R> {
val job = coroutineContext[Job].takeIf { it != null }?.let { Job(it) } ?: Job() val deferred: CompletableDeferred<R> = coroutineContext[Job].takeIf { it != null }?.let { CompletableDeferred<R>(it) } ?: CompletableDeferred()
bot.network.addHandler(TemporaryPacketHandler(P::class, job, this@BotSession).also { bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this@BotSession).also {
it.toSend(this) it.toSend(this)
it.onExpect(handler) it.onExpect(handler)
}) })
return job return deferred
} }
suspend inline fun ClientPacket.send() = socket.sendPacket(this) suspend inline fun ClientPacket.send() = socket.sendPacket(this)
...@@ -101,6 +101,6 @@ class BotSession( ...@@ -101,6 +101,6 @@ class BotSession(
suspend fun BotSession.distributePacket(packet: ServerPacket) = this.socket.distributePacket(packet) suspend fun BotSession.distributePacket(packet: ServerPacket) = this.socket.distributePacket(packet)
val BotSession.isOpen: Boolean get() = socket.isOpen val BotSession.isOpen: Boolean get() = socket.isOpen
val BotSession.account: Long get() = bot.account.account val BotSession.account: UInt get() = bot.account.account
val <T : BotNetworkHandler<*>> T.session get() = this[ActionPacketHandler].session val <T : BotNetworkHandler<*>> T.session get() = this[ActionPacketHandler].session
\ No newline at end of file
...@@ -33,13 +33,13 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -33,13 +33,13 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
override lateinit var socket: BotSocketAdapter override lateinit var socket: BotSocketAdapter
private set private set
internal val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*>>() internal val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*, *>>()
private val handlersLock = Mutex() private val handlersLock = Mutex()
private var heartbeatJob: Job? = null private var heartbeatJob: Job? = null
override suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) { override suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*, *>) {
handlersLock.withLock { handlersLock.withLock {
temporaryPacketHandlers.add(temporaryPacketHandler) temporaryPacketHandlers.add(temporaryPacketHandler)
} }
...@@ -408,7 +408,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -408,7 +408,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
class HeartbeatTimeoutException : CancellationException("heartbeat timeout") class HeartbeatTimeoutException : CancellationException("heartbeat timeout")
if (withTimeoutOrNull(configuration.heartbeatTimeout.millisecondsLong) { if (withTimeoutOrNull(configuration.heartbeatTimeout.millisecondsLong) {
ClientHeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<ServerHeartbeatResponsePacket> {} ClientHeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<ServerHeartbeatResponsePacket, Unit> {}
} == null) { } == null) {
bot.logPurple("Heartbeat timed out") bot.logPurple("Heartbeat timed out")
bot.reinitializeNetworkHandler(configuration, HeartbeatTimeoutException()) bot.reinitializeNetworkHandler(configuration, HeartbeatTimeoutException())
......
...@@ -50,7 +50,12 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -50,7 +50,12 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
} }
is ServerSubmitImageFilenameResponsePacket -> {
}
is ServerTryGetImageIDResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) is ServerTryGetImageIDResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerSubmitImageFilenameResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerAccountInfoResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) is ServerAccountInfoResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerAccountInfoResponsePacket -> { is ServerAccountInfoResponsePacket -> {
...@@ -84,7 +89,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -84,7 +89,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
} }
//@JvmSynthetic //@JvmSynthetic
suspend fun addFriend(account: Long, message: Lazy<String> = lazyOf("")): CompletableDeferred<AddFriendResult> { suspend fun addFriend(account: UInt, message: Lazy<String> = lazyOf("")): CompletableDeferred<AddFriendResult> {
val future = CompletableDeferred<AddFriendResult>() val future = CompletableDeferred<AddFriendResult>()
val session = AddFriendSession(account, future, message) val session = AddFriendSession(account, future, message)
// uploadImageSessions.add(session) // uploadImageSessions.add(session)
...@@ -160,7 +165,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -160,7 +165,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
} }
private inner class AddFriendSession( private inner class AddFriendSession(
private val qq: Long, private val qq: UInt,
private val future: CompletableDeferred<AddFriendResult>, private val future: CompletableDeferred<AddFriendResult>,
private val message: Lazy<String> private val message: Lazy<String>
) { ) {
......
...@@ -41,7 +41,7 @@ class EventPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -41,7 +41,7 @@ class EventPacketHandler(session: BotSession) : PacketHandler(session) {
} }
is ServerGroupMessageEventPacket -> { is ServerGroupMessageEventPacket -> {
if (packet.qq.toLong() == bot.account.account) return if (packet.qq == bot.account.account) return
GroupMessageEvent( GroupMessageEvent(
bot, bot,
......
package net.mamoe.mirai.network.protocol.tim.handler package net.mamoe.mirai.network.protocol.tim.handler
import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
...@@ -19,14 +19,14 @@ import kotlin.reflect.KClass ...@@ -19,14 +19,14 @@ import kotlin.reflect.KClass
* *
* @see BotSession.sendAndExpect * @see BotSession.sendAndExpect
*/ */
class TemporaryPacketHandler<P : ServerPacket>( class TemporaryPacketHandler<P : ServerPacket, R>(
private val expectationClass: KClass<P>, private val expectationClass: KClass<P>,
private val job: CompletableJob, private val deferred: CompletableDeferred<R>,
private val fromSession: BotSession private val fromSession: BotSession
) { ) {
private lateinit var toSend: ClientPacket private lateinit var toSend: ClientPacket
private lateinit var expect: suspend (P) -> Unit private lateinit var expect: suspend (P) -> R
lateinit var session: BotSession//无需覆盖 lateinit var session: BotSession//无需覆盖
...@@ -40,7 +40,7 @@ class TemporaryPacketHandler<P : ServerPacket>( ...@@ -40,7 +40,7 @@ class TemporaryPacketHandler<P : ServerPacket>(
} }
fun onExpect(handler: suspend (P) -> Unit) { fun onExpect(handler: suspend (P) -> R) {
this.expect = handler this.expect = handler
} }
...@@ -51,10 +51,15 @@ class TemporaryPacketHandler<P : ServerPacket>( ...@@ -51,10 +51,15 @@ class TemporaryPacketHandler<P : ServerPacket>(
suspend fun shouldRemove(session: BotSession, packet: ServerPacket): Boolean { suspend fun shouldRemove(session: BotSession, packet: ServerPacket): Boolean {
if (expectationClass.isInstance(packet) && session === this.fromSession) { if (expectationClass.isInstance(packet) && session === this.fromSession) {
kotlin.runCatching {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val ret = try {
expect(packet as P) expect(packet as P)
}.onFailure { job.completeExceptionally(it) }.onSuccess { job.complete() } } catch (e: Exception) {
deferred.completeExceptionally(e)
return true
}
deferred.complete(ret)
return true return true
} }
return false return false
......
...@@ -58,4 +58,9 @@ abstract class ClientPacket : Packet(), Closeable { ...@@ -58,4 +58,9 @@ abstract class ClientPacket : Packet(), Closeable {
} }
} }
@MustBeDocumented
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class PacketVersion(val date: String, val timVersion: String)
private val UninitializedByteReadPacket = ByteReadPacket(IoBuffer.Empty, IoBuffer.EmptyPool) private val UninitializedByteReadPacket = ByteReadPacket(IoBuffer.Empty, IoBuffer.EmptyPool)
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.encryptAndWrite
import net.mamoe.mirai.utils.writeQQ
class ClientRawPacket(
override val id: UShort,
private val bot: UInt,
private val version: ByteArray,
private val sessionKey: ByteArray,
private val data: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(bot)
writeFully(version)
encryptAndWrite(sessionKey) {
writeFully(data)
}
}
}
\ No newline at end of file
...@@ -9,7 +9,6 @@ import net.mamoe.mirai.network.protocol.tim.TIMProtocol ...@@ -9,7 +9,6 @@ import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.encryptAndWrite import net.mamoe.mirai.utils.encryptAndWrite
import net.mamoe.mirai.utils.writeHex import net.mamoe.mirai.utils.writeHex
import net.mamoe.mirai.utils.writeQQ import net.mamoe.mirai.utils.writeQQ
import net.mamoe.mirai.utils.writeRandom
/** /**
* 获取升级天数等. * 获取升级天数等.
...@@ -18,7 +17,7 @@ import net.mamoe.mirai.utils.writeRandom ...@@ -18,7 +17,7 @@ import net.mamoe.mirai.utils.writeRandom
*/ */
@PacketId(0x00_5Cu) @PacketId(0x00_5Cu)
class ClientAccountInfoRequestPacket( class ClientAccountInfoRequestPacket(
private val qq: Long, private val qq: UInt,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
......
...@@ -11,7 +11,7 @@ import net.mamoe.mirai.utils.writeQQ ...@@ -11,7 +11,7 @@ import net.mamoe.mirai.utils.writeQQ
@PacketId(0x00_58u) @PacketId(0x00_58u)
class ClientHeartbeatPacket( class ClientHeartbeatPacket(
private val bot: Long, private val bot: UInt,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
......
...@@ -4,7 +4,7 @@ package net.mamoe.mirai.network.protocol.tim.packet ...@@ -4,7 +4,7 @@ package net.mamoe.mirai.network.protocol.tim.packet
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) @Target(AnnotationTarget.CLASS)
annotation class PacketId( annotation class PacketId(
val value: UShort val value: UShort
) )
...@@ -27,6 +27,8 @@ abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable { ...@@ -27,6 +27,8 @@ abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable {
override fun close() = this.input.close() override fun close() = this.input.close()
override fun toString(): String = this.packetToString() override fun toString(): String = this.packetToString()
fun <S : ServerPacket> S.applySequence() = this.applySequence(this@ServerPacket.sequenceId)
} }
fun <S : ServerPacket> S.applySequence(sequenceId: UShort): S { fun <S : ServerPacket> S.applySequence(sequenceId: UShort): S {
......
...@@ -2,32 +2,101 @@ ...@@ -2,32 +2,101 @@
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.*
import kotlinx.io.core.readBytes
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.account import net.mamoe.mirai.network.account
import net.mamoe.mirai.network.protocol.tim.handler.ActionPacketHandler import net.mamoe.mirai.network.protocol.tim.TIMProtocol
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.*
import kotlin.properties.Delegates
@PacketId(0x03_88u)
//fixVer2=00 00 00 01 2E 01 00 00 69 35
//01 [3E 03 3F A2] [76 E4 B8 DD] 00 00 50 7A 00 0A 00 01 00 01 00 2D 55 73 65 72 44 61 74 61 49 6D 61 67 65 3A 43 32 43 5C 48 31 30 50 60 35 29 24 52 7D 57 45 56 54 41 4B 52 24 45 4E 54 45 58 2E 70 6E 67
// 00 00 00 F2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01
//01 3E 03 3F A2 76 E4 B8 DD 00 00 50 7B 00 0A 00 01 00 01 00 5E 4F 53 52 6F 6F 74 3A 43 3A 5C 55 73 65 72 73 5C 48 69 6D 31 38 5C 44 6F 63 75 6D 65 6E 74 73 5C 54 65 6E 63 65 6E 74 20 46 69 6C 65 73 5C 31 30 34 30 34 30 30 32 39 30 5C 49 6D 61 67 65 5C 43 32 43 5C 4E 41 4B 60 52 52 4E 24 49 24 24 4B 44 24 34 5B 5B 45 4E 24 4D 4A 30 2E 6A 70 67
// 00 00 06 99 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01
//01 3E 03 3F A2 76 E4 B8 DD 00 00 50 7C 00 0A 00 01 00 01 00 2D 55 73 65 72 44 61 74 61 49 6D 61 67 65 3A 43 32 43 5C 40 53 51 25 4F 46 43 50 36 4C 48 30 47 34 43 47 57 53 49 52 46 37 32 2E 70 6E 67
// 00 01 61 A7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01
@PacketId(0X01_BDu)
class ClientSubmitImageFilenamePacket(
private val bot: UInt,
private val target: UInt,
private val filename: String,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(bot)
writeHex(TIMProtocol.fixVer2)
//writeHex("04 00 00 00 01 2E 01 00 00 69 35")
encryptAndWrite(sessionKey) {
writeByte(0x01)
writeQQ(bot)
writeQQ(target)
writeZero(2)
writeUByte(0x02u)
writeRandom(1)
writeHex("00 0A 00 01 00 01")
val name = "UserDataImage:$filename"
writeShort(name.length.toShort())
writeStringUtf8(name)
writeHex("00 00")
writeRandom(2)//这个也与是哪个好友有关?
writeHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2E 01")//35 02? 最后这个值是与是哪个哈有有关
//this.debugPrintThis("ClientSubmitImageFilenamePacket")
}
//解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1A 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 28 5A 53 41 58 40 57 4B 52 4A 5A 31 7E 33 59 4F 53 53 4C 4D 32 4B 49 2E 6A 70 67 00 00 06 E2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02
//解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1B 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 28 5A 53 41 58 40 57 4B 52 4A 5A 31 7E 33 59 4F 53 53 4C 4D 32 4B 49 2E 6A 70 67 00 00 06 E2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02
//解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1C 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 29 37 42 53 4B 48 32 44 35 54 51 28 5A 35 7D 35 24 56 5D 32 35 49 4E 2E 6A 70 67 00 00 03 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02
}
}
@PacketId(0x01_BDu)
class ServerSubmitImageFilenameResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() = with(input) {
require(readBytes().contentEquals(expecting))
}
companion object {
private val expecting = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00)
}
@PacketId(0x01_BDu)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerSubmitImageFilenameResponsePacket = ServerSubmitImageFilenameResponsePacket(this.decryptBy(sessionKey)).applySequence()
}
}
@PacketId(0x03_52u)
expect class ClientTryGetImageIDPacket( expect class ClientTryGetImageIDPacket(
botNumber: Long, botNumber: UInt,
sessionKey: ByteArray, sessionKey: ByteArray,
groupNumberOrAccount: Long, target: UInt,
image: PlatformImage image: PlatformImage
) : ClientPacket ) : ClientPacket
@PacketId(0x03_88u) @PacketId(0x03_52u)
sealed class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPacket(input) { sealed class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
@PacketId(0x03_88u) @PacketId(0x03_52u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) { class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket { fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket {
val data = this.decryptAsByteArray(sessionKey) val data = this.decryptAsByteArray(sessionKey)
//00 00 00 08 00 00
//01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 B3 90 73 32 C0 5D 72 4B 18 AC 16 8A 23 92 21 3C E6 FD 51 33 CE C4 84 1C 4C 7B A2 E5 27 65 1C 99 EE D6 D4 D2 0D B8 10 2D 88 7E 13 71 75 09 36 46 0F BA 87 B3 EA 54 B2 2B 18 8F F3 5A 9D 55 C6 3B E4 CB 9E B3 69 79 E2 51 61 98 1B 04 49 76 58 29 75 E3 73 56 4B 89 A4 54 A2 E1 0C 17 72 8D 77 EA CD CF 9E 68 B7 01 65 7B F1 E3 B7 FC 04 0C F4 D8 8D B3 51 1B B2 4C 14 59 DE FA 0D 64 BD 50 2E ED 52 25 2F 62 63 66 38 63 39 39 65 2D 30 65 63 35 2D 34 33 33 31 2D 62 37 30 61 2D 31 36 33 35 32 66 66 64 38 33 33 33 5A 25 2F 62 63 66 38 63 39 39 65 2D 30 65 63 35 2D 34 33 33 31 2D 62 37 30 61 2D 31 36 33 35 32 66 66 64 38 33 33 33 60 00 68 80 80 08 20 01
println("ServerTryGetImageIDResponsePacket.size=" + data.size) println("ServerTryGetImageIDResponsePacket.size=" + data.size)
if (data.size == 209) {
//00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 BB C8 E4 E2 0F 38 FB AE FA 9D 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 0E 26 8D 39 E7 88 22 74 EC 88 2B 04 C5 D1 3D D2 09 A4 2E 48 22 F5 91 51 D5 82 7A 43 9F 45 70 77 79 83 21 87 4E AA 63 6E 73 D5 D3 DA 5F FC 36 BA 97 31 74 49 D9 97 83 58 74 06 BE F2 00 83 CC B9 50 D0 C4 D1 63 33 5F AE EA 1C 99 2D 0D E7 A2 94 97 6E 18 92 86 2C C0 36 E9 D9 E3 82 01 A3 B9 AC F1 90 67 73 F3 3C 0B 26 4C C4 DE 20 AF 3D B3 20 F8 50 B4 0E 78 0E 0E 1E 8C 56 02 21 10 5B 61 39 52 25 2F 31 38 37 31 34 66 66 39 2D 61 30 39 39 2D 34 61 38 64 2D 38 34 39 62 2D 38 37 35 65 65 30 36 65 34 64 32 36 5A 25 2F 31 38 37 31 34 66 66 39 2D 61 30 39 39 2D 34 61 38 64 2D 38 34 39 62 2D 38 37 35 65 65 30 36 65 34 64 32 36 60 00 68 80 80 08 20 01
if (data.size == 285) {
return ServerTryGetImageIDSuccessPacket(data.toReadPacket()).applySequence(sequenceId) return ServerTryGetImageIDSuccessPacket(data.toReadPacket()).applySequence(sequenceId)
} }
...@@ -36,59 +105,101 @@ sealed class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPa ...@@ -36,59 +105,101 @@ sealed class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPa
} }
} }
fun main() {
//GlobalSysTemp:II%E]PA}OVFK]61EGGF$356.jpg
//实际文件名为 II%E]PA}OVFK]61EGGF$356.jpg
println(ClientSubmitImageFilenamePacket(
1994701021u,
1040400290u,
"testfilename.png",
"99 82 67 D4 62 20 CA 5D 81 F8 6F 83 EE 8A F7 68".hexToBytes()
).packet.readBytes().toUHexString())
val data = "00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 B5 29 1A 1B 0E 63 79 8B 34 B1 4E 2A 2A 9E 69 09 A7 69 F5 C6 4F 95 DA 96 A9 1B E3 CD 6F 3D 30 EE 59 C0 30 22 BF F0 2D 88 2D A7 6C B2 09 AD D6 CE E1 46 84 FC 7D 19 AF 1A 37 91 98 AD 2C 45 25 AA 17 2F 81 DC 5A 7F 30 F4 2D 73 E5 1C 8B 8A 23 85 42 9D 8D 5C 18 15 32 D1 CA A3 4D 01 7C 59 11 73 DA B6 09 C2 6D 58 35 EF 48 88 44 0F 2D 17 09 52 DF D4 EA A7 85 2F 27 CE DF A8 F5 9B CD C9 84 C2 52 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 5A 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 60 00 68 80 80 08 20 01".hexToBytes()
println(ServerTryGetImageIDSuccessPacket(data.toReadPacket()).applySequence(1u).also { it.decode() })
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())
}
}
/** /**
* 服务器未存有图片, 返回一个 key 用于客户端上传 * 服务器未存有图片, 返回一个 key 用于客户端上传
*/ */
@PacketId(0x03_88u) @PacketId(0x03_52u)
class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) { class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
lateinit var uKey: ByteArray lateinit var uKey: ByteArray
var imageId: ImageId by Delegates.notNull()
override fun decode() { override fun decode() = with(input) {
this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u))//todo 优化 //00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03
uKey = this.input.readBytes(128) // 4A [80 01] B5 29 1A 1B 0E 63 79 8B 34 B1 4E 2A 2A 9E 69 09 A7 69 F5 C6 4F 95 DA 96 A9 1B E3 CD 6F 3D 30 EE 59 C0 30 22 BF F0 2D 88 2D A7 6C B2 09 AD D6 CE E1 46 84 FC 7D 19 AF 1A 37 91 98 AD 2C 45 25 AA 17 2F 81 DC 5A 7F 30 F4 2D 73 E5 1C 8B 8A 23 85 42 9D 8D 5C 18 15 32 D1 CA A3 4D 01 7C 59 11 73 DA B6 09 C2 6D 58 35 EF 48 88 44 0F 2D 17 09 52 DF D4 EA A7 85 2F 27 CE DF A8 F5 9B CD C9 84 C2 // 52 [25] 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 5A 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 60 00 68 80 80 08 20 01
DebugLogger.logPurple("获得 uKey(128)=${uKey.toUHexString()}")
discardExact(68)
discardExact(1)//4A, id
uKey = readBytes(readUnsignedVarInt().toInt())//128
discardExact(1)//52, id
imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
DebugLogger.logPurple("获得 uKey(${uKey.size})=${uKey.toUHexString()}")
DebugLogger.logPurple("获得 imageId(${imageId.value.length})=${imageId}")
println("ServerTryGetImageIDSuccessPacket后文=" + readRemainingBytes().toUHexString())
} }
} }
/** /**
* 服务器已经存有这个图片 * 服务器已经存有这个图片
*/ */
@PacketId(0x03_52u)
class ServerTryGetImageIDFailedPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) { class ServerTryGetImageIDFailedPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
override fun decode(): Unit = with(input) { override fun decode(): Unit = with(input) {
readRemainingBytes().debugPrint("ServerTryGetImageIDFailedPacket的body") readRemainingBytes().debugPrint("ServerTryGetImageIDFailedPacket的body")
} }
} }
suspend fun Group.uploadImage(imageId: String, image: PlatformImage) { suspend fun Group.uploadImage(image: PlatformImage): ImageId = this.bot.network.session.uploadGroupImage(number, image)
this.bot.network[ActionPacketHandler].session.uploadGroupImage(number, imageId, image)
}
suspend fun QQ.uploadImage(imageId: String, image: PlatformImage) { suspend fun QQ.uploadImage(image: PlatformImage): ImageId = this.bot.network.session.uploadFriendImage(number, image)
TODO()
} /**
* 需要在外 timeout 处理
*/
suspend fun BotSession.uploadFriendImage(qq: UInt, image: PlatformImage): ImageId {
ClientSubmitImageFilenamePacket(account, qq, "sdiovaoidsa.png", sessionKey).sendAndExpect<ServerSubmitImageFilenameResponsePacket, Unit> {
suspend fun BotSession.uploadGroupImage(groupNumberOrAccount: Long, imageId: String, image: PlatformImage) { }.join()
ClientTryGetImageIDPacket( return ClientTryGetImageIDPacket(account, sessionKey, qq, image).sendAndExpect<ServerTryGetImageIDResponsePacket, ImageId> {
account,
sessionKey,
groupNumberOrAccount,
image
).sendAndExpect<ServerTryGetImageIDResponsePacket> {
when (it) { when (it) {
is ServerTryGetImageIDFailedPacket -> { is ServerTryGetImageIDFailedPacket -> {
//服务器已存有图片 //服务器已存有图片
ImageId("UNKNOWN")
} }
is ServerTryGetImageIDSuccessPacket -> { is ServerTryGetImageIDSuccessPacket -> {
val data = image.toByteArray() val data = image.toByteArray()
httpPostGroupImage( require(httpPostFriendImage(
uKeyHex = it.uKey.toUHexString(""), uKeyHex = it.uKey.toUHexString(""),
botNumber = bot.qqAccount, botNumber = bot.qqAccount,
fileSize = data.size, fileSize = data.size,
imageData = data, imageData = data,
groupCode = groupNumberOrAccount qq = qq
) ))
//todo HTTP upload image. it.imageId
} }
} }
}.join() }.await()
}
suspend fun BotSession.uploadGroupImage(groupNumberOrAccount: UInt, image: PlatformImage): ImageId {
TODO()
} }
\ No newline at end of file
...@@ -2,10 +2,15 @@ ...@@ -2,10 +2,15 @@
package net.mamoe.mirai.network.protocol.tim.packet.action package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.* import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUShort
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.utils.* import net.mamoe.mirai.utils.encryptAndWrite
import net.mamoe.mirai.utils.writeHex
import net.mamoe.mirai.utils.writeQQ
/** /**
* 向服务器检查是否可添加某人为好友 * 向服务器检查是否可添加某人为好友
...@@ -14,8 +19,8 @@ import net.mamoe.mirai.utils.* ...@@ -14,8 +19,8 @@ import net.mamoe.mirai.utils.*
*/ */
@PacketId(0x00_A7u) @PacketId(0x00_A7u)
class ClientCanAddFriendPacket( class ClientCanAddFriendPacket(
val bot: Long, val bot: UInt,
val qq: Long, val qq: UInt,
val sessionKey: ByteArray val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
...@@ -85,8 +90,8 @@ class ServerCanAddFriendResponsePacket(input: ByteReadPacket) : ServerPacket(inp ...@@ -85,8 +90,8 @@ class ServerCanAddFriendResponsePacket(input: ByteReadPacket) : ServerPacket(inp
*/ */
@PacketId(0x00_AEu) @PacketId(0x00_AEu)
class ClientAddFriendPacket( class ClientAddFriendPacket(
val bot: Long, val bot: UInt,
val qq: Long, val qq: UInt,
val sessionKey: ByteArray val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
......
...@@ -10,55 +10,82 @@ import net.mamoe.mirai.message.toMessage ...@@ -10,55 +10,82 @@ import net.mamoe.mirai.message.toMessage
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId 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.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
fun main() { fun main() {
println("牛逼".toMessage().toChain().toPacket().readBytes().toUHexString()) println("牛逼".toMessage().toChain().toPacket(true).readBytes().toUHexString())
} }
@PacketId(0x00_CDu) @PacketId(0x00_CDu)
class ClientSendFriendMessagePacket( class ClientSendFriendMessagePacket(
private val botQQ: Long, private val botQQ: UInt,
private val targetQQ: Long, private val targetQQ: UInt,
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val message: MessageChain private val message: MessageChain
) : ClientPacket() { ) : ClientPacket() {
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(botQQ) writeQQ(botQQ)
writeHex(TIMProtocol.versionNewest) writeHex(TIMProtocol.versionNewest)
encryptAndWrite(sessionKey) { encryptAndWrite(sessionKey) {
//TIM最新 // TIM最新, 消息内容 "牛逼"
//3E 03 3F A2 // 3E 03 3F A2
//76 E4 B8 DD // 76 E4 B8 DD
//00 00 00 08 00 01 00 04 00 00 00 00 // 00 00 00 [08] 00 01 00 04 00 00 00 00
//38 03 // 38 03
//3E 03 3F A2 // 3E 03 3F A2
//76 E4 B8 DD // 76 E4 B8 DD
//C6 FB 06 30 0C 69 0C AD C6 AD 14 BF 0B C6 38 EA // C6 FB 06 30 0C 69 0C AD C6 AD 14 BF 0B C6 38 EA
//00 0B // 00 0B
//3D 7F // 3D 7F
//5D AA A8 E2 // 5D AA A8 E2
//01 1D // 01 1D
// 00 00 00 00
// 01
// 00
// 00
// 00 01 4D 53 47 00 00 00 00 00
// 5D AA A8 E2
// E2 AE 94 2D
// 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 E7 89 9B E9 80 BC
// TIM最新, 消息内容 "发图片群"
// 3E 03 3F A2
// 76 E4 B8 DD
// 00 00 00 [0D] 00 01 00 04 00 00 00 00 00 03 00 01 01
// 38 03
// 3E 03 3F A2
// 76 E4 B8 DD
// C6 FB 06 30 0C 69 0C AD C6 AD 14 BF 0B C6 38 EA
// 00 0B
// 3D 88
// 5D AA AE 33
// 01 1D
// 00 00 00 00 // 00 00 00 00
//01 // 01 00 00
// 00 // 00 01 4D 53 47 00 00 00 00 00
// 00 // 5D AA AE 33
//00 01 4D 53 47 00 00 00 00 00 // 7E 51 1D AA
//5D AA A8 E2 // 00 00 00 00 0C 00 86
//E2 AE 94 2D // 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91
//00 00 00 00 0C 00 86 // 00 00
//22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91
//00 00
// //
//01 00 09 01 00 06 E7 89 9B E9 80 BC // 01 00 0F 01 00 0C E5 8F 91 E5 9B BE E7 89 87 E7 BE A4
writeQQ(botQQ) writeQQ(botQQ)
writeQQ(targetQQ) writeQQ(targetQQ)
writeHex("00 00 00 08 00 01 00 04 00 00 00 00") writeHex("00 00 00 08 00 01 00 04 00 00 00 00")
writeHex("38 03")//TIM最新: 38 03 writeHex("38 03")
writeQQ(botQQ) writeQQ(botQQ)
writeQQ(targetQQ) writeQQ(targetQQ)
writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes())) writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes()))
...@@ -81,12 +108,11 @@ class ClientSendFriendMessagePacket( ...@@ -81,12 +108,11 @@ class ClientSendFriendMessagePacket(
writeHex("00 01 4D 53 47 00 00 00 00 00") writeHex("00 01 4D 53 47 00 00 00 00 00")
writeTime() writeTime()
writeRandom(4) writeRandom(4)
writeHex("00 00 00 00 0C 00 86")//TIM最新 0C 00 86 writeHex("00 00 00 00 0C 00 86")
writeHex(TIMProtocol.messageConstNewest)//... 85 E9 BB 91 writeHex(TIMProtocol.messageConstNewest)
writeZero(2) writeZero(2)
message.toPacket().debugPrint("CHAIN") writePacket(message.toPacket(false))
writePacket(message.toPacket())
/* /*
//Plain text //Plain text
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.action package net.mamoe.mirai.network.protocol.tim.packet.action
...@@ -15,8 +15,8 @@ import net.mamoe.mirai.utils.* ...@@ -15,8 +15,8 @@ import net.mamoe.mirai.utils.*
@PacketId(0x00_02u) @PacketId(0x00_02u)
class ClientSendGroupMessagePacket( class ClientSendGroupMessagePacket(
private val botQQ: Long, private val botQQ: UInt,
private val groupId: Long,//不是 number private val groupId: UInt,//不是 number
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val message: MessageChain private val message: MessageChain
) : ClientPacket() { ) : ClientPacket() {
...@@ -28,7 +28,7 @@ class ClientSendGroupMessagePacket( ...@@ -28,7 +28,7 @@ class ClientSendGroupMessagePacket(
writeByte(0x2A) writeByte(0x2A)
writeGroup(groupId) writeGroup(groupId)
writeLVPacket { writeShortLVPacket {
writeHex("00 01 01") writeHex("00 01 01")
writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00") writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
...@@ -38,7 +38,7 @@ class ClientSendGroupMessagePacket( ...@@ -38,7 +38,7 @@ class ClientSendGroupMessagePacket(
writeHex(TIMProtocol.messageConst1) writeHex(TIMProtocol.messageConst1)
writeZero(2) writeZero(2)
writePacket(message.toPacket()) writePacket(message.toPacket(true))
} }
/*it.writeByte(0x01) /*it.writeByte(0x01)
it.writeShort(bytes.size + 3) it.writeShort(bytes.size + 3)
......
...@@ -63,7 +63,7 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event ...@@ -63,7 +63,7 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
0x0052u -> ServerGroupMessageEventPacket(input, eventIdentity) 0x0052u -> ServerGroupMessageEventPacket(input, eventIdentity)
0x00A6u -> ServerFriendMessageEventPacket(input.debugColorizedPrint("好友消息事件", ignoreUntilFirstConst = true), eventIdentity) 0x00A6u -> ServerFriendMessageEventPacket(input, eventIdentity)
//0210: 00 00 00 0E 00 08 00 02 00 01 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 26 08 02 1A 02 08 49 0A 08 08 00 10 B2 DE 8C ED 05 0A 0C 08 A2 FF 8C F0 03 10 E4 A1 A7 ED 05 0A 0C 08 DD F1 92 B7 07 10 B1 DE 8C ED 05 //0210: 00 00 00 0E 00 08 00 02 00 01 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 26 08 02 1A 02 08 49 0A 08 08 00 10 B2 DE 8C ED 05 0A 0C 08 A2 FF 8C F0 03 10 E4 A1 A7 ED 05 0A 0C 08 DD F1 92 B7 07 10 B1 DE 8C ED 05
// 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 16 00 00 00 37 08 02 1A 12 08 95 02 10 90 04 40 98 E1 8C ED 05 48 AF 96 C3 A4 03 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 1A 29 08 00 10 05 18 98 E1 8C ED 05 20 01 28 FF FF FF FF 0F 32 15 E5 AF B9 E6 96 B9 E6 AD A3 E5 9C A8 E8 BE 93 E5 85 A5 2E 2E 2E // 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 16 00 00 00 37 08 02 1A 12 08 95 02 10 90 04 40 98 E1 8C ED 05 48 AF 96 C3 A4 03 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 1A 29 08 00 10 05 18 98 E1 8C ED 05 20 01 28 FF FF FF FF 0F 32 15 E5 AF B9 E6 96 B9 E6 AD A3 E5 9C A8 E8 BE 93 E5 85 A5 2E 2E 2E
...@@ -101,7 +101,7 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event ...@@ -101,7 +101,7 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
} }
inner class ResponsePacket( inner class ResponsePacket(
val bot: Long, val bot: UInt,
val sessionKey: ByteArray val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override val id: UShort get() = this@ServerEventPacket.id override val id: UShort get() = this@ServerEventPacket.id
......
...@@ -17,7 +17,7 @@ import net.mamoe.mirai.utils.writeQQ ...@@ -17,7 +17,7 @@ import net.mamoe.mirai.utils.writeQQ
*/ */
@PacketId(0x00_ECu) @PacketId(0x00_ECu)
class ClientChangeOnlineStatusPacket( class ClientChangeOnlineStatusPacket(
private val bot: Long, private val bot: UInt,
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val loginStatus: OnlineStatus private val loginStatus: OnlineStatus
) : ClientPacket() { ) : ClientPacket() {
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
...@@ -16,7 +16,7 @@ import net.mamoe.mirai.utils.* ...@@ -16,7 +16,7 @@ import net.mamoe.mirai.utils.*
*/ */
@PacketId(0x08_36u) @PacketId(0x08_36u)
class ClientPasswordSubmissionPacket constructor( class ClientPasswordSubmissionPacket constructor(
private val bot: Long, private val bot: UInt,
private val password: String, private val password: String,
private val loginTime: Int, private val loginTime: Int,
private val loginIP: String, private val loginIP: String,
...@@ -57,7 +57,7 @@ class ClientPasswordSubmissionPacket constructor( ...@@ -57,7 +57,7 @@ class ClientPasswordSubmissionPacket constructor(
private fun BytePacketBuilder.writePart1( private fun BytePacketBuilder.writePart1(
qq: Long, qq: UInt,
password: String, password: String,
loginTime: Int, loginTime: Int,
loginIP: String, loginIP: String,
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
...@@ -14,7 +14,7 @@ import net.mamoe.mirai.utils.* ...@@ -14,7 +14,7 @@ import net.mamoe.mirai.utils.*
*/ */
@PacketId(0x00_1Du) @PacketId(0x00_1Du)
class ClientSKeyRequestPacket( class ClientSKeyRequestPacket(
private val qq: Long, private val qq: UInt,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
...@@ -28,7 +28,7 @@ class ClientSKeyRequestPacket( ...@@ -28,7 +28,7 @@ class ClientSKeyRequestPacket(
@PacketId(0x00_1Du) @PacketId(0x00_1Du)
class ClientSKeyRefreshmentRequestPacket( class ClientSKeyRefreshmentRequestPacket(
private val qq: Long, private val qq: UInt,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
...@@ -46,7 +46,7 @@ class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) { ...@@ -46,7 +46,7 @@ class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() = with(input) { override fun decode() = with(input) {
discardExact(4) discardExact(4)
//debugDiscardExact(2) //debugDiscardExact(2)
sKey = this.readString(10) sKey = this.readString(10)//16??
DebugLogger.logPurple("SKey=$sKey") DebugLogger.logPurple("SKey=$sKey")
DebugLogger.logPurple("Skey包后面${this.readRemainingBytes().toUHexString()}") DebugLogger.logPurple("Skey包后面${this.readRemainingBytes().toUHexString()}")
} }
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
...@@ -9,7 +9,7 @@ import net.mamoe.mirai.utils.* ...@@ -9,7 +9,7 @@ import net.mamoe.mirai.utils.*
@PacketId(0x08_28u) @PacketId(0x08_28u)
class ClientSessionRequestPacket( class ClientSessionRequestPacket(
private val bot: Long, private val bot: UInt,
private val serverIp: String, private val serverIp: String,
private val token38: IoBuffer, private val token38: IoBuffer,
private val token88: IoBuffer, private val token88: IoBuffer,
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
...@@ -60,7 +60,7 @@ class ServerTouchResponsePacket(input: ByteReadPacket) : ServerPacket(input) { ...@@ -60,7 +60,7 @@ class ServerTouchResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
* @author Him188moe * @author Him188moe
*/ */
@PacketId(0x08_25u) @PacketId(0x08_25u)
class ClientTouchPacket(private val bot: Long, private val serverIp: String) : ClientPacket() { class ClientTouchPacket(private val bot: UInt, private val serverIp: String) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot) this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer) this.writeHex(TIMProtocol.fixVer)
...@@ -84,7 +84,7 @@ class ClientTouchPacket(private val bot: Long, private val serverIp: String) : C ...@@ -84,7 +84,7 @@ class ClientTouchPacket(private val bot: Long, private val serverIp: String) : C
* @author Him188moe * @author Him188moe
*/ */
@PacketId(0x08_25u) @PacketId(0x08_25u)
class ClientTouchRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() { class ClientTouchRedirectionPacket(private val serverIP: String, private val qq: UInt) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer) this.writeHex(TIMProtocol.fixVer)
......
...@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.* ...@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.*
*/ */
@PacketId(0x00_BAu) @PacketId(0x00_BAu)
class ClientCaptchaTransmissionRequestPacket( class ClientCaptchaTransmissionRequestPacket(
private val qq: Long, private val qq: UInt,
private val token0825: ByteArray, private val token0825: ByteArray,
private val verificationSequence: Int, private val verificationSequence: Int,
private val token00BA: ByteArray private val token00BA: ByteArray
...@@ -44,7 +44,7 @@ class ClientCaptchaTransmissionRequestPacket( ...@@ -44,7 +44,7 @@ class ClientCaptchaTransmissionRequestPacket(
*/ */
@PacketId(0x00_BAu) @PacketId(0x00_BAu)
class ClientCaptchaSubmitPacket( class ClientCaptchaSubmitPacket(
private val qq: Long, private val qq: UInt,
private val token0825: ByteArray, private val token0825: ByteArray,
private val captcha: String, private val captcha: String,
private val verificationToken: IoBuffer private val verificationToken: IoBuffer
...@@ -83,7 +83,7 @@ class ClientCaptchaSubmitPacket( ...@@ -83,7 +83,7 @@ class ClientCaptchaSubmitPacket(
*/ */
@PacketId(0x00_BAu) @PacketId(0x00_BAu)
class ClientCaptchaRefreshPacket( class ClientCaptchaRefreshPacket(
private val qq: Long, private val qq: UInt,
private val token0825: ByteArray private val token0825: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
data class BotAccount( data class BotAccount(
val account: Long,//实际上是 UInt val account: UInt,
val password: String//todo 不保存 password? val password: String//todo 不保存 password?
) )
\ No newline at end of file
...@@ -80,7 +80,8 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket { ...@@ -80,7 +80,8 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
0x00_CD_u -> ServerSendFriendMessageResponsePacket(this) 0x00_CD_u -> ServerSendFriendMessageResponsePacket(this)
0x00_02_u -> ServerSendGroupMessageResponsePacket(this) 0x00_02_u -> ServerSendGroupMessageResponsePacket(this)
0x00_A7_u -> ServerCanAddFriendResponsePacket(this) 0x00_A7_u -> ServerCanAddFriendResponsePacket(this)
0x03_88_u -> ServerTryGetImageIDResponsePacket.Encrypted(this) 0x03_52_u -> ServerTryGetImageIDResponsePacket.Encrypted(this)
0x01_BDu -> ServerSubmitImageFilenameResponsePacket.Encrypted(this)
else -> UnknownServerPacket.Encrypted(this, id, sequenceId) else -> UnknownServerPacket.Encrypted(this, id, sequenceId)
}.applySequence(sequenceId) }.applySequence(sequenceId)
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
class ContactList<C : Contact> : MutableMap<Long, C> by mutableMapOf() class ContactList<C : Contact> : MutableMap<UInt, C> by mutableMapOf()
\ No newline at end of file \ No newline at end of file
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.*
import kotlinx.io.core.Input
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
internal object DebugLogger : MiraiLogger by PlatformLogger("Packet Debug") internal object DebugLogger : MiraiLogger by PlatformLogger("Packet Debug")
...@@ -15,7 +12,7 @@ internal fun ByteArray.debugPrint(name: String): ByteArray { ...@@ -15,7 +12,7 @@ internal fun ByteArray.debugPrint(name: String): ByteArray {
return this return this
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("")) @Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun IoBuffer.debugPrint(name: String): IoBuffer { internal fun IoBuffer.debugPrint(name: String): IoBuffer {
val readBytes = this.readBytes() val readBytes = this.readBytes()
DebugLogger.logPurple(name + "=" + readBytes.toUHexString()) DebugLogger.logPurple(name + "=" + readBytes.toUHexString())
...@@ -27,20 +24,33 @@ internal fun Input.debugDiscardExact(n: Number, name: String = "") { ...@@ -27,20 +24,33 @@ internal fun Input.debugDiscardExact(n: Number, name: String = "") {
DebugLogger.logPurple("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString()) DebugLogger.logPurple("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("")) @Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket { internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
val bytes = this.readBytes() val bytes = this.readBytes()
DebugLogger.logPurple("ByteReadPacket $name=" + bytes.toUHexString()) DebugLogger.logPurple("ByteReadPacket $name=" + bytes.toUHexString())
return bytes.toReadPacket() return bytes.toReadPacket()
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("")) @Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket { internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket {
val bytes = this.readBytes() val bytes = this.readBytes()
bytes.printColorizedHex(name, ignoreUntilFirstConst) bytes.printColorizedHex(name, ignoreUntilFirstConst)
return bytes.toReadPacket() return bytes.toReadPacket()
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" "))
internal fun BytePacketBuilder.debugColorizedPrintThis(name: String = "") {
val data = this.build().readBytes()
data.printColorizedHex(name)
this.writeFully(data)
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" "))
internal fun BytePacketBuilder.debugPrintThis(name: String = "") {
val data = this.build().readBytes()
data.debugPrint(name)
this.writeFully(data)
}
internal fun String.printStringFromHex() { internal fun String.printStringFromHex() {
println(this.hexToBytes().stringOfWitch()) println(this.hexToBytes().stringOfWitch())
...@@ -54,4 +64,10 @@ internal fun ByteArray.printColorizedHex(name: String = "", ignoreUntilFirstCons ...@@ -54,4 +64,10 @@ internal fun ByteArray.printColorizedHex(name: String = "", ignoreUntilFirstCons
} }
expect fun compareHex(hex1s: String, hex2s: String): String expect fun compareHex(hex1s: String, hex2s: String): String
expect fun String.colorize(ignoreUntilFirstConst: Boolean = false): String expect fun String.colorize(ignoreUntilFirstConst: Boolean = false): String
\ No newline at end of file
fun main() {
"00 02 3E 03 3F A2 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 39 00 00 00 0B 00 00 00 2E 51 51 E7 A9 BA E9 97 B4 20 0A 20 20 E6 9C 89 E6 96 B0 E8 AE BF E5 AE A2 20 0A 20 20 E6 9C 89 E6 96 B0 E5 A5 BD E5 8F 8B E5 8A A8 E6 80 81 00 00 01 2C 00 00 00 00"
.printStringFromHex()
}
\ No newline at end of file
...@@ -13,24 +13,34 @@ fun BytePacketBuilder.writeZero(count: Int) = repeat(count) { this.writeByte(0) ...@@ -13,24 +13,34 @@ fun BytePacketBuilder.writeZero(count: Int) = repeat(count) { this.writeByte(0)
fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) } fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) }
fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt()) fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt())
fun BytePacketBuilder.writeQQ(qq: UInt) = this.writeUInt(qq)
fun BytePacketBuilder.writeGroup(groupIdOrGroupNumber: Long) = this.writeFully(groupIdOrGroupNumber.toUInt().toByteArray()) fun BytePacketBuilder.writeGroup(groupIdOrGroupNumber: Long) = this.writeUInt(groupIdOrGroupNumber.toUInt())
fun BytePacketBuilder.writeGroup(groupIdOrGroupNumber: UInt) = this.writeUInt(groupIdOrGroupNumber)
fun BytePacketBuilder.writeLVByteArray(byteArray: ByteArray) { fun BytePacketBuilder.writeLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size.toShort()) this.writeShort(byteArray.size.toShort())
this.writeFully(byteArray) this.writeFully(byteArray)
} }
fun BytePacketBuilder.writeLVPacket(packet: ByteReadPacket) { fun BytePacketBuilder.writeShortLVPacket(packet: ByteReadPacket) {
this.writeShort(packet.remaining.toShort()) this.writeShort(packet.remaining.toShort())
this.writePacket(packet) this.writePacket(packet)
packet.release() packet.release()
} }
fun BytePacketBuilder.writeLVPacket(builder: BytePacketBuilder.() -> Unit) = this.writeLVPacket(BytePacketBuilder().apply(builder).build()) fun BytePacketBuilder.writeUVarintLVPacket(packet: ByteReadPacket) {
this.writeUVarLong(packet.remaining)
this.writePacket(packet)
packet.release()
}
fun BytePacketBuilder.writeShortLVPacket(builder: BytePacketBuilder.() -> Unit) = this.writeShortLVPacket(BytePacketBuilder().apply(builder).build())
fun BytePacketBuilder.writeUVarintLVPacket(builder: BytePacketBuilder.() -> Unit) = this.writeUVarintLVPacket(BytePacketBuilder().apply(builder).build())
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun BytePacketBuilder.writeLVString(str: String) = this.writeLVByteArray(str.toByteArray()) fun BytePacketBuilder.writeShortLVString(str: String) = this.writeLVByteArray(str.toByteArray())
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun BytePacketBuilder.writeLVHex(hex: String) = this.writeLVByteArray(hex.hexToBytes()) fun BytePacketBuilder.writeLVHex(hex: String) = this.writeLVByteArray(hex.hexToBytes())
...@@ -45,7 +55,7 @@ fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder. ...@@ -45,7 +55,7 @@ fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.
fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = writeFully(TEA.encrypt(BytePacketBuilder().apply(encoder).use { it.build().readBytes() }, key)) fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = writeFully(TEA.encrypt(BytePacketBuilder().apply(encoder).use { it.build().readBytes() }, key))
fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder) fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
fun BytePacketBuilder.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray) { fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray) {
val firstMD5 = md5(password) val firstMD5 = md5(password)
val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray()) val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray())
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
...@@ -38,10 +40,14 @@ expect fun localIpAddress(): String ...@@ -38,10 +40,14 @@ expect fun localIpAddress(): String
/** /**
* 上传群图片 * 上传群图片
*/ */
expect suspend fun httpPostGroupImage( expect suspend fun httpPostFriendImage(
uKeyHex: String, uKeyHex: String,
fileSize: Int, fileSize: Int,
botNumber: Long, botNumber: UInt,
groupCode: Long, qq: UInt,
imageData: ByteArray imageData: ByteArray
): Boolean ): Boolean
\ No newline at end of file
fun main() {
"46 52 25 46 60 30 59 4F 4A 5A 51".printStringFromHex()
}
\ No newline at end of file
...@@ -23,7 +23,7 @@ fun ByteArray.encryptBy(keyHex: String): ByteArray = TEA.encrypt(checkLength(), ...@@ -23,7 +23,7 @@ fun ByteArray.encryptBy(keyHex: String): ByteArray = TEA.encrypt(checkLength(),
private fun ByteArray.checkLength(): ByteArray { private fun ByteArray.checkLength(): ByteArray {
size.let { size.let {
require(it % 8 == 0 && it >= 16) { "data must len % 8 == 0 && len >= 16 but given $it" } require(it % 8 == 0 && it >= 16) { "data must len % 8 == 0 && len >= 16 but given (length=$it) ${this.toUHexString()}" }
} }
return this return this
} }
\ No newline at end of file
...@@ -49,6 +49,7 @@ fun String.hexToUBytes(): UByteArray = HexCache.hexToUBytes(this) ...@@ -49,6 +49,7 @@ fun String.hexToUBytes(): UByteArray = HexCache.hexToUBytes(this)
fun String.hexToInt(): Int = hexToBytes().toUInt().toInt() fun String.hexToInt(): Int = hexToBytes().toUInt().toInt()
fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() } fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
fun getRandomString(length: Int, charRange: CharRange): String = String(CharArray(length) { charRange.random() })
fun ByteArray.toUInt(): UInt = this[0].toUInt().and(255u).shl(24) + this[1].toUInt().and(255u).shl(16) + this[2].toUInt().and(255u).shl(8) + this[3].toUInt().and(255u).shl(0) fun ByteArray.toUInt(): UInt = this[0].toUInt().and(255u).shl(24) + this[1].toUInt().and(255u).shl(16) + this[2].toUInt().and(255u).shl(8) + this[3].toUInt().and(255u).shl(0)
fun ByteArray.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it } fun ByteArray.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it }
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
...@@ -18,7 +20,7 @@ import net.mamoe.mirai.utils.ContactList ...@@ -18,7 +20,7 @@ import net.mamoe.mirai.utils.ContactList
* @author Him188moe * @author Him188moe
*/ */
@Suppress("unused") @Suppress("unused")
actual sealed class Contact actual constructor(bot: Bot, number: Long) : PlatformContactBase(bot, number) { actual sealed class Contact actual constructor(bot: Bot, number: UInt) : PlatformContactBase(bot, number) {
abstract override suspend fun sendMessage(message: MessageChain) abstract override suspend fun sendMessage(message: MessageChain)
...@@ -71,7 +73,7 @@ actual sealed class Contact actual constructor(bot: Bot, number: Long) : Platfor ...@@ -71,7 +73,7 @@ actual sealed class Contact actual constructor(bot: Bot, number: Long) : Platfor
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)` * Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
* @author Him188moe * @author Him188moe
*/ */
actual class Group actual constructor(bot: Bot, number: Long) : Contact(bot, number) { actual class Group actual constructor(bot: Bot, number: UInt) : Contact(bot, number) {
actual val groupId = groupNumberToId(number) actual val groupId = groupNumberToId(number)
actual val members: ContactList<QQ> actual val members: ContactList<QQ>
//todo members //todo members
...@@ -100,7 +102,7 @@ actual class Group actual constructor(bot: Bot, number: Long) : Contact(bot, num ...@@ -100,7 +102,7 @@ actual class Group actual constructor(bot: Bot, number: Long) : Contact(bot, num
* *
* @author Him188moe * @author Him188moe
*/ */
actual class QQ actual constructor(bot: Bot, number: Long) : Contact(bot, number) { actual class QQ actual constructor(bot: Bot, number: UInt) : Contact(bot, number) {
actual override suspend fun sendMessage(message: MessageChain) { actual override suspend fun sendMessage(message: MessageChain) {
bot.network[EventPacketHandler].sendFriendMessage(this, message) bot.network[EventPacketHandler].sendFriendMessage(this, message)
} }
......
...@@ -2,97 +2,186 @@ ...@@ -2,97 +2,186 @@
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder import kotlinx.io.core.*
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import java.io.File
import javax.imageio.ImageIO
actual typealias ClientTryGetImageIDPacket = ClientTryGetImageIDPacketJvm actual typealias ClientTryGetImageIDPacket = ClientTryGetImageIDPacketJvm
fun main() {
val packet = ClientTryGetImageIDPacketJvm(1040400290u,
"99 82 67 D4 62 20 CA 5D 81 F8 6F 83 EE 8A F7 68".hexToBytes(),
2978594313u,
ImageIO.read(File(("C:\\Users\\Him18\\Desktop\\哈哈哈操.jpg"))))
println(packet.packet.readBytes().toUHexString())
"89 FC A6 8C 0B".hexToBytes().read {
println(readUnsignedVarInt())
}
}
/** /**
* 请求上传图片. 将发送图片的 md5, size. * 请求上传图片. 将发送图片的 md5, size, width, height.
* 服务器返回以下之一: * 服务器返回以下之一:
* - 服务器已经存有这个图片 [ServerTryGetImageIDFailedPacket] * - 服务器已经存有这个图片 [ServerTryGetImageIDFailedPacket]
* - 服务器未存有, 返回一个 key 用于客户端上传 [ServerTryGetImageIDSuccessPacket] * - 服务器未存有, 返回一个 key 用于客户端上传 [ServerTryGetImageIDSuccessPacket]
* *
* @author Him188moe * @author Him188moe
*/ */
@PacketId(0x03_88u) @PacketId(0x03_52u)
class ClientTryGetImageIDPacketJvm( class ClientTryGetImageIDPacketJvm(
private val botNumber: Long, private val botNumber: UInt,
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val groupNumberOrAccount: Long, private val target: UInt,
private val image: PlatformImage private val image: PlatformImage
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
//00 00 00 07 00 00 00 4B 08 01 12 03 98 01 01 08 01 12 47 08 A2 FF 8C F0 03 10 89 FC A6 8C 0B 18 00 22 10 2B 23 D7 05 CA D1 F2 CF 37 10 FE 58 26 92 FC C4 28 FD 08 32 1A 7B 00 47 00 47 00 42 00 7E 00 49 00 31 00 5A 00 4D 00 43 00 28 00 25 00 49 00 38 01 48 00 70 42 78 42
//一次 body @PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
//00 00 00 00 00 00 00 00 3C 61 3C 48 85 91 81 B9 DF 27 D9 C3 20 43 F7 1C 73 DA 2A 84 74 AC 78 AC CC 38 54 8F AE 06 8C 22 AA AF 2E C1 E4 70 8C 31 63 52 95 F2 6F C3 9A 2D 77 4B F7 7B 4F C4 1A 6D 7A 3F 22 D8 9D B3 48 99 F3 E7 4F D0 2D 31 94 40 ED A7 5C D9 CE 70 B1 F7 B8 1B 3D CA B3 0E BE 86 33 56 B4 E4 30 AD 66 30 C1 C7 15 6A 71 B6 49 DC DC 0E 74 4B CE 12 3F ED override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(botNumber)
this.writeQQ(botNumber) //04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00
this.writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00") writeHex("04 00 00 00 01 2E 01 00 00 69 35 00 00 00 00 00 00 00 00")
val imageData = image.toByteArray()
encryptAndWrite(sessionKey) {
//好友图片
// 00 00 00
// 07 00
// 00 00
// proto
// [4D 08]后文长度
// 01 12
// 03 98
// 01 01
// 08 01
// 12 49
// 08 [A2 FF 8C F0 03](1040400290 varint)
// 10 [DD F1 92 B7 07](1994701021 varint)
// 18 00
// 22 [10](=16) [E9 BA 47 2E 36 ED D4 BF 8C 4F E5 6A CB A0 2D 5E](md5)
// 28 [CE 0E](1870 varint)
// 32 1A
// 39 00
// 51 00
// 24 00
// 32 00
// 4A 00
// 53 00
// 25 00
// 4C 00
// 56 00
// 42 00
// 33 00
// 44 00
// 44 00
// 38 01
// 48 00
// 70 [92 03](402 varint)
// 78 [E3 01](227 varint)
//好友图片
/*
* 00 00 00 07 00 00 00
* [4E 08]后文长度
* 01 12
* 03 98
* 01 01
* 08 01
* 12 4A
* 08 [A2 FF 8C F0 03](varint)
* 10 [DD F1 92 B7 07](varint)
* 18 00//24
* 22 10 72 02 57 44 84 1D 83 FC C0 85 A1 E9 10 AA 9C 2C
* 28 [BD D9 19](421053 varint)
* 32 1A//48
* 49 00
* 49 00
* 25 00
* 45 00
* 5D 00
* 50 00
* 41 00
* 7D 00
* 4F 00
* 56 00
* 46 00
* 4B 00
* 5D 00
* 38 01
* 48 00//78
*
*
* 70 [80 14]
* 78 [A0 0B]//84
*/
val byteArray = image.toByteArray()
this.encryptAndWrite(sessionKey) {
writeZero(3) writeZero(3)
writeUShort(0x07_00u)
writeHex("07 00") writeZero(1)
writeZero(2) //proto
val packet = buildPacket {
writeHex("5B")//原5E writeUByte(0x08u)
writeHex("08") writeUShort(0x01_12u)
writeHex("01 12 03 98 01 01 10 01") writeUShort(0x03_98u)
writeUShort(0x01_01u)
writeHex("1A") writeUShort(0x08_01u)
writeHex("57")//原5A
writeHex("08") writeUShort(0x12_47u)//?似乎会变
writeUVarInt(groupNumberOrAccount)//FB D2 D8 94
writeUByte(0x08u)
writeByte(0x02) writeUVarInt(target)//todo 这两qq号反过来放也tm可以成功
writeHex("10")
writeUVarInt(botNumber)//A2 FF 8C F0 writeUByte(0x10u)
writeUVarInt(botNumber)
writeHex("18 00")
writeUShort(0x18_00u)
writeHex("22")
writeHex("10") writeUByte(0x22u)
writeFully(md5(byteArray)) writeUByte(0x10u)
writeFully(md5(imageData))
writeHex("28")
writeUVarInt(byteArray.size.toUInt())//E2 0D writeUByte(0x28u)
writeUVarInt(imageData.size.toUInt())
writeHex("32")
writeHex("1A") writeUByte(0x32u)
//28 00 5A 00 53 00 41 00 58 00 40 00 57 00 4B 00 52 00 4A 00 5A 00 31 00 7E 00 38 01 48 01 50 38 58 34 60 04 6A 05 32 36 39 33 33 70 00 78 03 80 01 00 //长度应为1A
writeUVarintLVPacket {
writeUShort(0x28_00u)
writeHex("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") writeUShort(0x46_00u)
writeUShort(0x51_00u)
writeHex("38 01") writeUShort(0x56_00u)
writeUShort(0x4B_00u)
writeHex("48 01") writeUShort(0x41_00u)
writeUShort(0x49_00u)
writeHex("50") writeUShort(0x25_00u)
writeUVarInt(image.width.toUInt()) writeUShort(0x4B_00u)
writeHex("58") writeUShort(0x24_00u)
writeUVarInt(image.height.toUInt()) writeUShort(0x55_00u)
writeUShort(0x30_00u)
writeHex("60 04") writeUShort(0x24_00u)
}
writeHex("6A")
writeHex("05") writeUShort(0x38_01u)
writeHex("32 36 36 35 36") writeUShort(0x48_00u)
writeHex("70 00") writeUByte(0x70u)
writeUVarInt(image.width.toUInt())
writeHex("78 03") writeUByte(0x78u)
writeUVarInt(image.height.toUInt())
writeHex("80 01") }
writeShort((packet.remaining - 7).toShort())//why?
writeHex("00") writePacket(packet)
//println(this.build().readBytes().toUHexString())
} }
} }
} }
\ No newline at end of file
...@@ -7,4 +7,4 @@ import javax.imageio.ImageIO ...@@ -7,4 +7,4 @@ import javax.imageio.ImageIO
actual typealias PlatformImage = BufferedImage actual typealias PlatformImage = BufferedImage
@JvmOverloads @JvmOverloads
actual fun BufferedImage.toByteArray(formatName: String): ByteArray = ByteArrayOutputStream().use { ImageIO.write(this, "JPG", it); it.toByteArray() } actual fun BufferedImage.toByteArray(formatName: String): ByteArray = ByteArrayOutputStream().use { ImageIO.write(this, "PNG", it); it.toByteArray() }
\ No newline at end of file \ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.coroutines.Dispatchers import java.net.HttpURLConnection
import kotlinx.coroutines.withContext
import org.jsoup.Connection
import org.jsoup.Jsoup
import java.net.InetAddress import java.net.InetAddress
import java.net.URL
import java.security.MessageDigest import java.security.MessageDigest
import java.util.zip.CRC32 import java.util.zip.CRC32
...@@ -19,36 +19,62 @@ actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(host ...@@ -19,36 +19,62 @@ actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(host
actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
actual suspend fun httpPostGroupImage( fun main() {
"00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 B5 29 1A 1B 0E 63 79 8B 34 B1 4E 2A 2A 9E 69 09 A7 69 F5 C6 4F 95 DA 96 A9 1B E3 CD 6F 3D 30 EE 59 C0 30 22 BF F0 2D 88 2D A7 6C B2 09 AD D6 CE E1 46 84 FC 7D 19 AF 1A 37 91 98 AD 2C 45 25 AA 17 2F 81 DC 5A 7F 30 F4 2D 73 E5 1C 8B 8A 23 85 42 9D 8D 5C 18 15 32 D1 CA A3 4D 01 7C 59 11 73 DA B6 09 C2 6D 58 35 EF 48 88 44 0F 2D 17 09 52 DF D4 EA A7 85 2F 27 CE DF A8 F5 9B CD C9 84 C2 52 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 5A 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 60 00 68 80 80 08 20 01"
.printStringFromHex()
println(md5("00 00 00 08 00 00 01 0D 12 06 98 01 01 A0 01 00 08 01 12 86 02 08 00 10 AB A7 89 D8 02 18 00 28 00 38 B4 C7 E6 B0 02 38 F1 C0 A1 BF 05 38 FB AE FA 95 0A 38 E5 C6 BF EC 06 40 B0 6D 40 90 3F 40 50 40 BB 03 4A 80 01 B5 29 1A 1B 0E 63 79 8B 34 B1 4E 2A 2A 9E 69 09 A7 69 F5 C6 4F 95 DA 96 A9 1B E3 CD 6F 3D 30 EE 59 C0 30 22 BF F0 2D 88 2D A7 6C B2 09 AD D6 CE E1 46 84 FC 7D 19 AF 1A 37 91 98 AD 2C 45 25 AA 17 2F 81 DC 5A 7F 30 F4 2D 73 E5 1C 8B 8A 23 85 42 9D 8D 5C 18 15 32 D1 CA A3 4D 01 7C 59 11 73 DA B6 09 C2 6D 58 35 EF 48 88 44 0F 2D 17 09 52 DF D4 EA A7 85 2F 27 CE DF A8 F5 9B CD C9 84 C2 52 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 5A 25 2F 30 31 65 65 36 34 32 36 2D 35 66 66 31 2D 34 63 66 30 2D 38 32 37 38 2D 65 38 36 33 34 64 32 39 30 39 65 66 60 00 68 80 80 08 20 01").toUHexString())
}
actual suspend fun httpPostFriendImage(
uKeyHex: String, uKeyHex: String,
fileSize: Int, fileSize: Int,
botNumber: Long, botNumber: UInt,
groupCode: Long, qq: UInt,
imageData: ByteArray imageData: ByteArray
): Boolean = Jsoup ): Boolean {/*Jsoup
.connect("http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" + //htdata2.qq.com
// 101.227.143.109/cgi-bin/httpconn
// ?htcmd=0x6ff0070
// &ver=5603
// &ukey=3B121C959B85035F12497519221FB9E09740E477D3A440D28253E96C95BD72EA1D11B25189894F0256F3E0F3D553FB92925A8834F85583C78D0D9639A3F35C730783D45C065FF9E9E74765183A11492D50750C6BB5DCAD9F171285B68F6A11061CDDA740AD2DCD28D5B2DB2D6440143FA53F1B6F14584DB49E926FDDC4F49907
// &filesize=137791
// &range=0
// &uin=1040400290
.connect("http://101.227.143.109/cgi-bin/httpconn" +
"?htcmd=0x6ff0070" +
"&ver=5603" +
"&ukey=" + uKeyHex.replace(" ", "") + "&ukey=" + uKeyHex.replace(" ", "") +
"&filezise=" + fileSize + "&filezise=" + fileSize +
"&range=" + "0" + "&range=" + "0" +
"&uin=" + botNumber + "&uin=" + botNumber.toLong())
"&groupcode=" + groupCode) //.userAgent("QQClient")
.userAgent("QQClient")
.header("Content-Length", fileSize.toString()) .header("Content-Length", fileSize.toString())
.requestBody(String(imageData)) .requestBody(String(imageData, Charset.forName("ASCII")))
.method(Connection.Method.POST) .method(Connection.Method.POST)
.ignoreContentType(true) .ignoreContentType(true)
.let { .let {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
it.execute() it.execute()
} }
} };*/
/*
val conn = URL(builder).openConnection() as HttpURLConnection val conn = URL("http://101.227.143.109/cgi-bin/httpconn" +
conn.setRequestProperty("User-Agent", "QQClient") "?htcmd=0x6ff0070" +
conn.setRequestProperty("Content-Length", "" + fileSize) "&ver=5603" +
conn.requestMethod = "POST" "&ukey=" + uKeyHex.replace(" ", "") +
conn.doOutput = true "&filezise=" + fileSize +
conn.outputStream.write(img) "&range=" + "0" +
"&uin=" + botNumber.toLong()).openConnection() as HttpURLConnection
conn.connect()*/ conn.setRequestProperty("User-Agent", "QQClient")
.statusCode() == 200 conn.setRequestProperty("Content-Length", "" + fileSize)
\ No newline at end of file conn.requestMethod = "POST"
conn.doOutput = true
conn.outputStream.buffered().write(imageData)
conn.connect()
println(conn.responseMessage)
println(conn.responseCode)
return conn.responseCode == 200
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
...@@ -44,7 +46,7 @@ suspend fun main() { ...@@ -44,7 +46,7 @@ suspend fun main() {
runBlocking { runBlocking {
val bot = Bot( val bot = Bot(
BotAccount( BotAccount(
qq, qq.toUInt(),
if (password.endsWith(".")) password.substring(0, password.length - 1) else password if (password.endsWith(".")) password.substring(0, password.length - 1) else password
), ),
Console() Console()
......
...@@ -8,6 +8,8 @@ dependencies { ...@@ -8,6 +8,8 @@ dependencies {
implementation rootProject.ext.coroutineCommon implementation rootProject.ext.coroutineCommon
implementation rootProject.ext.kotlinJvm implementation rootProject.ext.kotlinJvm
implementation rootProject.ext.kotlinxIOJvm implementation rootProject.ext.kotlinxIOJvm
compile "org.jetbrains.kotlin:kotlin-reflect:1.3.50"
implementation 'org.jsoup:jsoup:1.12.1'
} }
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
......
import net.mamoe.mirai.utils.toUHexString
import org.jsoup.Connection
import org.jsoup.Jsoup
fun main() {
println(Jsoup.connect("http://61.183.164.34/gchatpic_new/B814D8D6A55D6DB423469E38D2A17AAA23836D74E7A656A4A8288C6078950A33B4A49E854E59B6D2314EFC47D6475902EDE8CADAFAF7F2A7670CAC05EA8314A1241128102F0A3AAF9C07284B1AE35E52F6D0A265235AFA6B/0?vuin=1040400290&term=255&srvver=26933&rf=n")
.cookie("ST", "00015DAA9C030040F3B82971FCBC718AA35573100B9CDBA2CB0DE38AF8710CA22E2986246345FC96B82BA0C211FDA700C397DF99FCC0989D67FD75F00B2FFB9CE0D032C3DCAC5A77")
.method(Connection.Method.GET)
.ignoreContentType(true)
.execute()
.bodyAsBytes().toUHexString())
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate") @file:Suppress("EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate", "EXPERIMENTAL_UNSIGNED_LITERALS")
import jpcap.JpcapCaptor import jpcap.JpcapCaptor
import jpcap.packet.IPPacket import jpcap.packet.IPPacket
...@@ -31,7 +31,7 @@ object Main { ...@@ -31,7 +31,7 @@ object Main {
jpcap = JpcapCaptor.openDevice(devices[0], caplen, promiscCheck, 50) jpcap = JpcapCaptor.openDevice(devices[0], caplen, promiscCheck, 50)
while (true) { while (true) {
assert(jpcap != null) assert(jpcap != null)
val pk = jpcap!!.packet val pk = jpcap!!.packet ?: continue
if (pk is IPPacket && pk.version.toInt() == 4) { if (pk is IPPacket && pk.version.toInt() == 4) {
if (pk is UDPPacket) { if (pk is UDPPacket) {
...@@ -47,6 +47,7 @@ object Main { ...@@ -47,6 +47,7 @@ object Main {
} }
} else { } else {
try { try {
//println("发出包全部=${pk.data.toUHexString()}")
dataSent(pk.data) dataSent(pk.data)
println() println()
} catch (e: Throwable) { } catch (e: Throwable) {
...@@ -73,20 +74,36 @@ object Main { ...@@ -73,20 +74,36 @@ object Main {
* 7. 查看内存, 从 `eax` 开始的 16 bytes 便是 `sessionKey` * 7. 查看内存, 从 `eax` 开始的 16 bytes 便是 `sessionKey`
*/ */
val sessionKey: ByteArray = "99 82 67 D4 62 20 CA 5D 81 F8 6F 83 EE 8A F7 68".hexToBytes() val sessionKey: ByteArray = "99 82 67 D4 62 20 CA 5D 81 F8 6F 83 EE 8A F7 68".hexToBytes()
val qq: UInt = 1040400290u
fun dataReceived(data: ByteArray) { fun dataReceived(data: ByteArray) {
println("--------------")
println("接收数据包")
//println("raw = " + data.toUHexString()) //println("raw = " + data.toUHexString())
data.read { data.read {
discardExact(3) discardExact(3)
val idHex = readInt().toUHexString(" ") val idHex = readInt().toUHexString(" ")
discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00. 但更可能是应该 discard 8 if (idHex.startsWith("00 81")) {
return@read
}
if (readUInt() != qq) {
return@read
}
println("--------------")
println("接收数据包")
discardExact(3)//0x00 0x00 0x00. 但更可能是应该 discard 8
println("id=$idHex") println("id=$idHex")
println("解密body=${this.readRemainingBytes().cutTail(1).decryptBy(sessionKey).toUHexString()}") val remaining = this.readRemainingBytes().cutTail(1)
} try {
val decrypted = remaining.decryptBy(sessionKey)
println("解密body=${decrypted.toUHexString()}")
packetReceived(data.read { parseServerPacket(data.size) })
} catch (e: DecryptionFailedException) {
println("密文body=" + remaining.toUHexString())
println("解密body=解密失败")
}
packetReceived(data.read { this.parseServerPacket(data.size) }) }
} }
fun packetReceived(packet: ServerPacket) { fun packetReceived(packet: ServerPacket) {
...@@ -118,15 +135,38 @@ object Main { ...@@ -118,15 +135,38 @@ object Main {
} }
fun dataSent(rawPacket: ByteArray) = rawPacket.cutTail(1).read { fun dataSent(rawPacket: ByteArray) = rawPacket.cutTail(1).read {
// 02 38 03
// 03 52 78 9F
// 3E 03 3F A2 04 00 00 00 01 2E 01 00 00 69 35 00 00 00 00 00 00 00 00
// 02 38 03
// 01 BD 63 D6
// 3E 03 3F A2 02 00 00 00 01 2E 01 00 00 69 35
println("---------------------------") println("---------------------------")
discardExact(3)//head discardExact(3)//head
val idHex = readBytes(4).toUHexString() val idHex = readBytes(4).toUHexString()
println("发出包ID = $idHex") println("发出包ID = $idHex")
readUInt()//客户端登录的qq if (readUInt() != qq) {
println("TIM的fixVer2=" + readBytes(TIMProtocol.fixVer2.hexToBytes().size).toUHexString()) 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
else -> error("unknown fixVer2 flag=$flag")
}.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
val encryptedBody = readRemainingBytes() val encryptedBody = readRemainingBytes()
println("解密body = ${encryptedBody.decryptBy(sessionKey).toUHexString()}") try {
println("解密body=${encryptedBody.decryptBy(sessionKey).toUHexString()}")
} catch (e: DecryptionFailedException) {
println("密文=" + encryptedBody.toUHexString())
println("解密body=解密失败")
}
encryptedBody.read { encryptedBody.read {
...@@ -139,13 +179,18 @@ object Main { ...@@ -139,13 +179,18 @@ object Main {
val messageData = raw.decryptBy(sessionKey) val messageData = raw.decryptBy(sessionKey)
println("解密结果: " + messageData.toUHexString()) println("解密结果: " + messageData.toUHexString())
println("尝试解消息") println("尝试解消息")
messageData.read {
discardExact( try {
4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2 messageData.read {
+ 1 discardExact(
) 4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2
val chain = readMessageChain() + 1
println(chain) )
val chain = readMessageChain()
println(chain)
}
} catch (e: Exception) {
println("失败")
} }
} }
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package demo1 package demo1
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.events.FriendMessageEvent import net.mamoe.mirai.event.events.FriendMessageEvent
import net.mamoe.mirai.event.events.GroupMessageEvent import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.event.subscribeAll import net.mamoe.mirai.event.subscribeAll
...@@ -10,12 +14,16 @@ import net.mamoe.mirai.event.subscribeAlways ...@@ -10,12 +14,16 @@ import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeUntilFalse import net.mamoe.mirai.event.subscribeUntilFalse
import net.mamoe.mirai.login import net.mamoe.mirai.login
import net.mamoe.mirai.message.Image import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.PlainText import net.mamoe.mirai.message.PlainText
import net.mamoe.mirai.network.protocol.tim.packet.ClientRawPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.BotAccount import net.mamoe.mirai.network.protocol.tim.packet.uploadImage
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.network.session
import net.mamoe.mirai.utils.PlatformLogger import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.*
import java.io.File import java.io.File
import javax.imageio.ImageIO
private fun readTestAccount(): BotAccount? { private fun readTestAccount(): BotAccount? {
val file = File("testAccount.txt") val file = File("testAccount.txt")
...@@ -25,7 +33,7 @@ private fun readTestAccount(): BotAccount? { ...@@ -25,7 +33,7 @@ private fun readTestAccount(): BotAccount? {
val lines = file.readLines() val lines = file.readLines()
return try { return try {
BotAccount(lines[0].toLong(), lines[1]) BotAccount(lines[0].toUInt(), lines[1])
} catch (e: IndexOutOfBoundsException) { } catch (e: IndexOutOfBoundsException) {
null null
} }
...@@ -34,7 +42,7 @@ private fun readTestAccount(): BotAccount? { ...@@ -34,7 +42,7 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
suspend fun main() { suspend fun main() {
val bot = Bot(readTestAccount() ?: BotAccount(//填写你的账号 val bot = Bot(readTestAccount() ?: BotAccount(//填写你的账号
account = 1994701121, account = 1994701121u,
password = "123456" password = "123456"
), PlatformLogger()) ), PlatformLogger())
...@@ -61,20 +69,31 @@ suspend fun main() { ...@@ -61,20 +69,31 @@ suspend fun main() {
"复读" in it.message -> it.sender.sendMessage(it.message) "复读" in it.message -> it.sender.sendMessage(it.message)
"发群" in it.message -> { "发群" in it.message -> Group(bot, 580266363u).sendMessage("h")
"直接发送包" in it.message -> {
val d = ("01 " + 1994701021u.toByteArray().toUHexString() + " 3E 03 3F A2 00 00 02 BB 00 0A 00 01 00 01 00 5E 4F 53 52 6F 6F 74 3A 43 3A 5C 55 73 65 72 73 5C 48 69 6D 31 38 5C 44 6F 63 75 6D 65 6E 74 73 5C 54 65 6E 63 65 6E 74 20 46 69 6C 65 73 5C 31 30 34 30 34 30 30 32 39 30 5C 49 6D 61 67 65 5C 43 32 43 5C 7B 47 47 42 7E 49 31 5A 4D 43 28 25 49 4D 5A 5F 47 55 51 36 35 5D 51 2E 6A 70 67 00 00 04 7D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 35 02")
.hexToBytes()
it.bot.network.socket.sendPacket(ClientRawPacket(0x01_BDu, it.bot.qqAccount, "00 00 00 01 2E 01 00 00 69 35".hexToBytes(), it.bot.network.session.sessionKey, d))
}
"上传好友图片" in it.message -> withTimeoutOrNull(3000) {
val id = QQ(bot, 1040400290u).uploadImage(ImageIO.read(File("C:\\Users\\Him18\\Desktop\\lemon.png").readBytes().inputStream()))
it.reply(id.value)
delay(1000)
it.reply(Image(id))
} }
/*it.event eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image -> /*it.event eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, Group(session.bot, 580266363)).of() image.upload(session, Group(session.bot, 580266363)).of()
})*/ })*/
it.message eq "发图片群2" -> Group(bot, 580266363).sendMessage(Image("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg")) it.message eq "发图片群2" -> Group(bot, 580266363u).sendMessage(Image(ImageId("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg")))
/* it.event eq "发图片" -> sendFriendMessage(it.sender, PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image -> /* it.event eq "发图片" -> sendFriendMessage(it.sender, PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, it.sender).of() image.upload(session, it.sender).of()
})*/ })*/
it.message eq "发图片2" -> it.reply(PlainText("test") + Image("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg")) it.message eq "发图片2" -> it.reply(PlainText("test") + Image(ImageId("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg")))
} }
} }
......
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