Commit fdf50e4d authored by Him188's avatar Him188

Image uploading

parent 4d346a2c
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai
import kotlinx.coroutines.runBlocking
......@@ -172,7 +174,7 @@ object MiraiServer {
get() {
for (it in qqList.split("\n").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) {
bot.logGreen("Login succeed")
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai
import kotlinx.atomicfu.atomic
......@@ -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))
}
}
......
......@@ -17,14 +17,14 @@ import net.mamoe.mirai.utils.toUHexString
*/
//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: UInt): Group = getGroupByNumber(number.toLong())
fun Bot.getGroupByNumber(number: Long): Group = this.contacts.getGroupByNumber(number.toUInt())
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
......@@ -39,7 +39,7 @@ suspend fun Bot.login(configuration: BotNetworkConfiguration.() -> Unit): LoginR
suspend fun Bot.login(): LoginResult = this.network.login(BotNetworkConfiguration.Default)
//BotAccount
val Bot.qqAccount: Long get() = this.account.account
val Bot.qqAccount: UInt get() = this.account.account
//logging
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot
......@@ -11,7 +13,7 @@ import net.mamoe.mirai.message.toChain
*
* @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)
......@@ -33,4 +35,4 @@ abstract class PlatformContactBase internal constructor(val bot: Bot, val number
* 在不同平台可能有不同的实现.
* 如在 JVM, suspend 调用不便, [Contact] 中有简化调用的 `blocking`() 和 `async`
*/
expect sealed class Contact(bot: Bot, number: Long) : PlatformContactBase
\ No newline at end of file
expect sealed class Contact(bot: Bot, number: UInt) : PlatformContactBase
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot
......@@ -13,8 +15,8 @@ import net.mamoe.mirai.utils.ContactList
*
* @author Him188moe
*/
expect class Group(bot: Bot, number: Long) : Contact {
val groupId: Long
expect class Group(bot: Bot, number: UInt) : Contact {
val groupId: UInt
val members: ContactList<QQ>
override suspend fun sendMessage(message: MessageChain)
......@@ -23,7 +25,7 @@ expect class Group(bot: Bot, number: Long) : Contact {
companion object
}
fun Group.Companion.groupNumberToId(number: Long): Long {//求你别出错
fun Group.Companion.groupNumberToId(number: UInt): UInt {//求你别出错
val left: Long = number.toString().let {
if (it.length < 6) {
return@groupNumberToId number
......@@ -36,84 +38,84 @@ fun Group.Companion.groupNumberToId(number: Long): Long {//求你别出错
return when (left) {
in 1..10 -> {
((left + 202).toString() + right.toString()).toLong()
((left + 202).toString() + right.toString()).toUInt()
}
in 11..19 -> {
((left + 469).toString() + right.toString()).toLong()
((left + 469).toString() + right.toString()).toUInt()
}
in 20..66 -> {
((left + 208).toString() + right.toString()).toLong()
((left + 208).toString() + right.toString()).toUInt()
}
in 67..156 -> {
((left + 1943).toString() + right.toString()).toLong()
((left + 1943).toString() + right.toString()).toUInt()
}
in 157..209 -> {
((left + 199).toString() + right.toString()).toLong()
((left + 199).toString() + right.toString()).toUInt()
}
in 210..309 -> {
((left + 389).toString() + right.toString()).toLong()
((left + 389).toString() + right.toString()).toUInt()
}
in 310..499 -> {
((left + 349).toString() + right.toString()).toLong()
((left + 349).toString() + right.toString()).toUInt()
}
else -> number
}
}
fun Group.Companion.groupIdToNumber(id: Long): Long {//求你别出错
var left: Long = id.toString().let {
fun Group.Companion.groupIdToNumber(id: UInt): UInt {//求你别出错
var left: UInt = id.toString().let {
if (it.length < 6) {
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 -> {
val right: Long = id.toString().let {
it.substring(it.length - 6).toLong()
val right: UInt = id.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 202).toString() + right.toString()).toLong()
((left - 202u).toString() + right.toString()).toUInt()
}
in 480..488 -> {
val right: Long = id.toString().let {
it.substring(it.length - 6).toLong()
val right: UInt = id.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 469).toString() + right.toString()).toLong()
((left - 469u).toString() + right.toString()).toUInt()
}
in 2100..2146 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
val right: UInt = id.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toLong()
((left - 208).toString() + right.toString()).toLong()
left = left.toString().substring(0 until 3).toUInt()
((left - 208u).toString() + right.toString()).toUInt()
}
in 2010..2099 -> {
val right: Long = id.toString().let {
it.substring(it.length - 6).toLong()
val right: UInt = id.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 1943).toString() + right.toString()).toLong()
((left - 1943u).toString() + right.toString()).toUInt()
}
in 2147..2199 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
val right: UInt = id.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toLong()
((left - 199).toString() + right.toString()).toLong()
left = left.toString().substring(0 until 3).toUInt()
((left - 199u).toString() + right.toString()).toUInt()
}
in 4100..4199 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
val right: UInt = id.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toLong()
((left - 389).toString() + right.toString()).toLong()
left = left.toString().substring(0 until 3).toUInt()
((left - 389u).toString() + right.toString()).toUInt()
}
in 3800..3989 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
val right: UInt = id.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toLong()
((left - 349).toString() + right.toString()).toLong()
left = left.toString().substring(0 until 3).toUInt()
((left - 349u).toString() + right.toString()).toUInt()
}
else -> id
}
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot
......@@ -14,7 +16,7 @@ import net.mamoe.mirai.message.MessageChain
*
* @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 sendXMLMessage(message: String)
......
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.message
......@@ -87,21 +87,24 @@ data class PlainText(override val stringValue: String) : Message() {
// ==================================== 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() {
override val stringValue: String = "[$id]"
data class Image(val id: ImageId, val filename: String = "") : Message() {
override val stringValue: String = "[${id.value}]"
}
inline class ImageId(val value: String)
// ==================================== At ====================================
/**
* At 一个人
*/
data class At(val targetQQ: Long) : Message() {
data class At(val targetQQ: UInt) : Message() {
constructor(target: QQ) : this(target.number)
override val stringValue: String = "[@$targetQQ]"
......
......@@ -8,7 +8,8 @@ enum class MessageType(val value: UByte) {
PLAIN_TEXT(0x01u),
AT(0x06u),
FACE(0x02u),
IMAGE(0x03u),//may be 0x06?
GROUP_IMAGE(0x03u),
FRIEND_IMAGE(0x06u),
;
......
......@@ -37,8 +37,8 @@ internal fun IoBuffer.parseMessageImage0x06(): Image {
val suffix = readString(filenameLength).substringAfter(".")
discardExact(8)//03 00 04 00 00 02 9C 04
val length = readShort()//=27
discardExact(1)//无意义符号?
return Image("{${readString(length - 2/*去掉首尾各一个无意义符号*/)}}.$suffix")
return Image(ImageId(readString(length)), "")//todo 文件名解析
//return Image("{${readString(length)}}.$suffix")
}
......@@ -56,8 +56,8 @@ fun main() {
}
internal fun IoBuffer.parseMessageImage0x03(): Image {
discardExact(1)
return Image(String(readLVByteArray()))
discardExact(1) //TODO 这里的文件名解析
return Image(ImageId(String(readLVByteArray())), "")
}
internal fun ByteReadPacket.readMessage(): Message? {
......@@ -67,7 +67,7 @@ internal fun ByteReadPacket.readMessage(): Message? {
return try {
when (messageType) {
//todo 在每个parse里面都 discard 了第一byte.
//todo 在每个parse里面都 discard 了第一 byte.
0x01 -> sectionData.parsePlainText()
0x02 -> sectionData.parseMessageFace()
0x03 -> sectionData.parseMessageImage0x03()
......@@ -128,14 +128,14 @@ fun ByteReadPacket.readMessageChain(): MessageChain {
return chain
}
fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
this@toPacket.forEach { message ->
writePacket(with(message) {
when (this) {
is Face -> buildPacket {
writeUByte(MessageType.FACE.value)
writeLVPacket {
writeShortLVPacket {
writeShort(1)
writeUByte(id.id)
......@@ -150,11 +150,18 @@ fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
is At -> throw UnsupportedOperationException("At is not supported now but is expecting to be supported")
is Image -> buildPacket {
writeUByte(MessageType.IMAGE.value)
if (forGroup) {
writeUByte(MessageType.GROUP_IMAGE.value)
/*
* 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
* 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
*/
writeLVPacket {
writeShortLVPacket {
//todo
writeByte(0x02)
writeLVString(id)
writeShortLVString(id.value.substring(1..35))
writeHex("04 00 " +
"04 9B 53 B0 08 " +
"05 00 " +
......@@ -164,17 +171,67 @@ fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
"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)
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 {
writeUByte(MessageType.PLAIN_TEXT.value)
writeLVPacket {
writeShortLVPacket {
writeByte(0x01)
writeLVString(stringValue)
writeShortLVString(stringValue)
}
}
......
......@@ -70,7 +70,7 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> {
* @see [BotSession.sendAndExpect] 发送并期待一个包
* @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
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
......@@ -65,10 +65,10 @@ class BotSession(
* @param handlerTemporary 处理器.
*/
//@JvmSynthetic
suspend inline fun <reified P : ServerPacket> sendAndExpect(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableJob {
val job = coroutineContext[Job].takeIf { it != null }?.let { Job(it) } ?: Job()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, job, this).also(handlerTemporary))
return job
suspend inline fun <reified P : ServerPacket, R> sendAndExpect(handlerTemporary: TemporaryPacketHandler<P, R>.() -> Unit): CompletableDeferred<R> {
val deferred: CompletableDeferred<R> = coroutineContext[Job].takeIf { it != null }?.let { CompletableDeferred<R>(it) } ?: CompletableDeferred()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this).also(handlerTemporary))
return deferred
}
/**
......@@ -86,13 +86,13 @@ class BotSession(
* @param P 期待的包
* @param handler 处理期待的包
*/
suspend inline fun <reified P : ServerPacket> ClientPacket.sendAndExpect(noinline handler: suspend (P) -> Unit): CompletableJob {
val job = coroutineContext[Job].takeIf { it != null }?.let { Job(it) } ?: Job()
bot.network.addHandler(TemporaryPacketHandler(P::class, job, this@BotSession).also {
suspend inline fun <reified P : ServerPacket, R> ClientPacket.sendAndExpect(noinline handler: suspend (P) -> R): CompletableDeferred<R> {
val deferred: CompletableDeferred<R> = coroutineContext[Job].takeIf { it != null }?.let { CompletableDeferred<R>(it) } ?: CompletableDeferred()
bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this@BotSession).also {
it.toSend(this)
it.onExpect(handler)
})
return job
return deferred
}
suspend inline fun ClientPacket.send() = socket.sendPacket(this)
......@@ -101,6 +101,6 @@ class BotSession(
suspend fun BotSession.distributePacket(packet: ServerPacket) = this.socket.distributePacket(packet)
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
\ No newline at end of file
......@@ -33,13 +33,13 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
override lateinit var socket: BotSocketAdapter
private set
internal val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*>>()
internal val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*, *>>()
private val handlersLock = Mutex()
private var heartbeatJob: Job? = null
override suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) {
override suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*, *>) {
handlersLock.withLock {
temporaryPacketHandlers.add(temporaryPacketHandler)
}
......@@ -408,7 +408,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
class HeartbeatTimeoutException : CancellationException("heartbeat timeout")
if (withTimeoutOrNull(configuration.heartbeatTimeout.millisecondsLong) {
ClientHeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<ServerHeartbeatResponsePacket> {}
ClientHeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<ServerHeartbeatResponsePacket, Unit> {}
} == null) {
bot.logPurple("Heartbeat timed out")
bot.reinitializeNetworkHandler(configuration, HeartbeatTimeoutException())
......
......@@ -50,7 +50,12 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
}
is ServerSubmitImageFilenameResponsePacket -> {
}
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 -> {
......@@ -84,7 +89,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
}
//@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 session = AddFriendSession(account, future, message)
// uploadImageSessions.add(session)
......@@ -160,7 +165,7 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
}
private inner class AddFriendSession(
private val qq: Long,
private val qq: UInt,
private val future: CompletableDeferred<AddFriendResult>,
private val message: Lazy<String>
) {
......
......@@ -41,7 +41,7 @@ class EventPacketHandler(session: BotSession) : PacketHandler(session) {
}
is ServerGroupMessageEventPacket -> {
if (packet.qq.toLong() == bot.account.account) return
if (packet.qq == bot.account.account) return
GroupMessageEvent(
bot,
......
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.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
......@@ -19,14 +19,14 @@ import kotlin.reflect.KClass
*
* @see BotSession.sendAndExpect
*/
class TemporaryPacketHandler<P : ServerPacket>(
class TemporaryPacketHandler<P : ServerPacket, R>(
private val expectationClass: KClass<P>,
private val job: CompletableJob,
private val deferred: CompletableDeferred<R>,
private val fromSession: BotSession
) {
private lateinit var toSend: ClientPacket
private lateinit var expect: suspend (P) -> Unit
private lateinit var expect: suspend (P) -> R
lateinit var session: BotSession//无需覆盖
......@@ -40,7 +40,7 @@ class TemporaryPacketHandler<P : ServerPacket>(
}
fun onExpect(handler: suspend (P) -> Unit) {
fun onExpect(handler: suspend (P) -> R) {
this.expect = handler
}
......@@ -51,10 +51,15 @@ class TemporaryPacketHandler<P : ServerPacket>(
suspend fun shouldRemove(session: BotSession, packet: ServerPacket): Boolean {
if (expectationClass.isInstance(packet) && session === this.fromSession) {
kotlin.runCatching {
@Suppress("UNCHECKED_CAST")
val ret = try {
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 false
......
......@@ -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)
@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
import net.mamoe.mirai.utils.encryptAndWrite
import net.mamoe.mirai.utils.writeHex
import net.mamoe.mirai.utils.writeQQ
import net.mamoe.mirai.utils.writeRandom
/**
* 获取升级天数等.
......@@ -18,7 +17,7 @@ import net.mamoe.mirai.utils.writeRandom
*/
@PacketId(0x00_5Cu)
class ClientAccountInfoRequestPacket(
private val qq: Long,
private val qq: UInt,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
......
......@@ -11,7 +11,7 @@ import net.mamoe.mirai.utils.writeQQ
@PacketId(0x00_58u)
class ClientHeartbeatPacket(
private val bot: Long,
private val bot: UInt,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
......
......@@ -4,7 +4,7 @@ package net.mamoe.mirai.network.protocol.tim.packet
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE)
@Target(AnnotationTarget.CLASS)
annotation class PacketId(
val value: UShort
)
......@@ -27,6 +27,8 @@ abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable {
override fun close() = this.input.close()
override fun toString(): String = this.packetToString()
fun <S : ServerPacket> S.applySequence() = this.applySequence(this@ServerPacket.sequenceId)
}
fun <S : ServerPacket> S.applySequence(sequenceId: UShort): S {
......
......@@ -2,10 +2,15 @@
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.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.*
*/
@PacketId(0x00_A7u)
class ClientCanAddFriendPacket(
val bot: Long,
val qq: Long,
val bot: UInt,
val qq: UInt,
val sessionKey: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
......@@ -85,8 +90,8 @@ class ServerCanAddFriendResponsePacket(input: ByteReadPacket) : ServerPacket(inp
*/
@PacketId(0x00_AEu)
class ClientAddFriendPacket(
val bot: Long,
val qq: Long,
val bot: UInt,
val qq: UInt,
val sessionKey: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
......
......@@ -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.packet.ClientPacket
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.utils.*
fun main() {
println("牛逼".toMessage().toChain().toPacket().readBytes().toUHexString())
println("牛逼".toMessage().toChain().toPacket(true).readBytes().toUHexString())
}
@PacketId(0x00_CDu)
class ClientSendFriendMessagePacket(
private val botQQ: Long,
private val targetQQ: Long,
private val botQQ: UInt,
private val targetQQ: UInt,
private val sessionKey: ByteArray,
private val message: MessageChain
) : ClientPacket() {
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(botQQ)
writeHex(TIMProtocol.versionNewest)
encryptAndWrite(sessionKey) {
//TIM最新
//3E 03 3F A2
//76 E4 B8 DD
//00 00 00 08 00 01 00 04 00 00 00 00
//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 7F
//5D AA A8 E2
//01 1D
// TIM最新, 消息内容 "牛逼"
// 3E 03 3F A2
// 76 E4 B8 DD
// 00 00 00 [08] 00 01 00 04 00 00 00 00
// 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 7F
// 5D AA A8 E2
// 01 1D
// 00 00 00 00
//01
// 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
// 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
// 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
// 01 00 00
// 00 01 4D 53 47 00 00 00 00 00
// 5D AA AE 33
// 7E 51 1D AA
// 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 0F 01 00 0C E5 8F 91 E5 9B BE E7 89 87 E7 BE A4
writeQQ(botQQ)
writeQQ(targetQQ)
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(targetQQ)
writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes()))
......@@ -81,12 +108,11 @@ class ClientSendFriendMessagePacket(
writeHex("00 01 4D 53 47 00 00 00 00 00")
writeTime()
writeRandom(4)
writeHex("00 00 00 00 0C 00 86")//TIM最新 0C 00 86
writeHex(TIMProtocol.messageConstNewest)//... 85 E9 BB 91
writeHex("00 00 00 00 0C 00 86")
writeHex(TIMProtocol.messageConstNewest)
writeZero(2)
message.toPacket().debugPrint("CHAIN")
writePacket(message.toPacket())
writePacket(message.toPacket(false))
/*
//Plain text
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.action
......@@ -15,8 +15,8 @@ import net.mamoe.mirai.utils.*
@PacketId(0x00_02u)
class ClientSendGroupMessagePacket(
private val botQQ: Long,
private val groupId: Long,//不是 number
private val botQQ: UInt,
private val groupId: UInt,//不是 number
private val sessionKey: ByteArray,
private val message: MessageChain
) : ClientPacket() {
......@@ -28,7 +28,7 @@ class ClientSendGroupMessagePacket(
writeByte(0x2A)
writeGroup(groupId)
writeLVPacket {
writeShortLVPacket {
writeHex("00 01 01")
writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
......@@ -38,7 +38,7 @@ class ClientSendGroupMessagePacket(
writeHex(TIMProtocol.messageConst1)
writeZero(2)
writePacket(message.toPacket())
writePacket(message.toPacket(true))
}
/*it.writeByte(0x01)
it.writeShort(bytes.size + 3)
......
......@@ -63,7 +63,7 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
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
// 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
}
inner class ResponsePacket(
val bot: Long,
val bot: UInt,
val sessionKey: ByteArray
) : ClientPacket() {
override val id: UShort get() = this@ServerEventPacket.id
......
......@@ -17,7 +17,7 @@ import net.mamoe.mirai.utils.writeQQ
*/
@PacketId(0x00_ECu)
class ClientChangeOnlineStatusPacket(
private val bot: Long,
private val bot: UInt,
private val sessionKey: ByteArray,
private val loginStatus: OnlineStatus
) : ClientPacket() {
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login
......@@ -16,7 +16,7 @@ import net.mamoe.mirai.utils.*
*/
@PacketId(0x08_36u)
class ClientPasswordSubmissionPacket constructor(
private val bot: Long,
private val bot: UInt,
private val password: String,
private val loginTime: Int,
private val loginIP: String,
......@@ -57,7 +57,7 @@ class ClientPasswordSubmissionPacket constructor(
private fun BytePacketBuilder.writePart1(
qq: Long,
qq: UInt,
password: String,
loginTime: Int,
loginIP: String,
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login
......@@ -14,7 +14,7 @@ import net.mamoe.mirai.utils.*
*/
@PacketId(0x00_1Du)
class ClientSKeyRequestPacket(
private val qq: Long,
private val qq: UInt,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
......@@ -28,7 +28,7 @@ class ClientSKeyRequestPacket(
@PacketId(0x00_1Du)
class ClientSKeyRefreshmentRequestPacket(
private val qq: Long,
private val qq: UInt,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
......@@ -46,7 +46,7 @@ class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() = with(input) {
discardExact(4)
//debugDiscardExact(2)
sKey = this.readString(10)
sKey = this.readString(10)//16??
DebugLogger.logPurple("SKey=$sKey")
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
......@@ -9,7 +9,7 @@ import net.mamoe.mirai.utils.*
@PacketId(0x08_28u)
class ClientSessionRequestPacket(
private val bot: Long,
private val bot: UInt,
private val serverIp: String,
private val token38: 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
......@@ -60,7 +60,7 @@ class ServerTouchResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
* @author Him188moe
*/
@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) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer)
......@@ -84,7 +84,7 @@ class ClientTouchPacket(private val bot: Long, private val serverIp: String) : C
* @author Him188moe
*/
@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) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
......
......@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.*
*/
@PacketId(0x00_BAu)
class ClientCaptchaTransmissionRequestPacket(
private val qq: Long,
private val qq: UInt,
private val token0825: ByteArray,
private val verificationSequence: Int,
private val token00BA: ByteArray
......@@ -44,7 +44,7 @@ class ClientCaptchaTransmissionRequestPacket(
*/
@PacketId(0x00_BAu)
class ClientCaptchaSubmitPacket(
private val qq: Long,
private val qq: UInt,
private val token0825: ByteArray,
private val captcha: String,
private val verificationToken: IoBuffer
......@@ -83,7 +83,7 @@ class ClientCaptchaSubmitPacket(
*/
@PacketId(0x00_BAu)
class ClientCaptchaRefreshPacket(
private val qq: Long,
private val qq: UInt,
private val token0825: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
data class BotAccount(
val account: Long,//实际上是 UInt
val account: UInt,
val password: String//todo 不保存 password?
)
\ No newline at end of file
......@@ -80,7 +80,8 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
0x00_CD_u -> ServerSendFriendMessageResponsePacket(this)
0x00_02_u -> ServerSendGroupMessageResponsePacket(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)
}.applySequence(sequenceId)
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import net.mamoe.mirai.contact.Contact
class ContactList<C : Contact> : MutableMap<Long, C> by mutableMapOf()
\ No newline at end of file
class ContactList<C : Contact> : MutableMap<UInt, C> by mutableMapOf()
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import kotlinx.io.core.*
internal object DebugLogger : MiraiLogger by PlatformLogger("Packet Debug")
......@@ -15,7 +12,7 @@ internal fun ByteArray.debugPrint(name: String): ByteArray {
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 {
val readBytes = this.readBytes()
DebugLogger.logPurple(name + "=" + readBytes.toUHexString())
......@@ -27,20 +24,33 @@ internal fun Input.debugDiscardExact(n: Number, name: String = "") {
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 {
val bytes = this.readBytes()
DebugLogger.logPurple("ByteReadPacket $name=" + bytes.toUHexString())
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 {
val bytes = this.readBytes()
bytes.printColorizedHex(name, ignoreUntilFirstConst)
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() {
println(this.hexToBytes().stringOfWitch())
......@@ -55,3 +65,9 @@ internal fun ByteArray.printColorizedHex(name: String = "", ignoreUntilFirstCons
expect fun compareHex(hex1s: String, hex2s: String): String
expect fun String.colorize(ignoreUntilFirstConst: Boolean = false): String
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)
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: 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) {
this.writeShort(byteArray.size.toShort())
this.writeFully(byteArray)
}
fun BytePacketBuilder.writeLVPacket(packet: ByteReadPacket) {
fun BytePacketBuilder.writeShortLVPacket(packet: ByteReadPacket) {
this.writeShort(packet.remaining.toShort())
this.writePacket(packet)
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")
fun BytePacketBuilder.writeLVString(str: String) = this.writeLVByteArray(str.toByteArray())
fun BytePacketBuilder.writeShortLVString(str: String) = this.writeLVByteArray(str.toByteArray())
@Suppress("DEPRECATION")
fun BytePacketBuilder.writeLVHex(hex: String) = this.writeLVByteArray(hex.hexToBytes())
......@@ -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(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 secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray())
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import com.soywiz.klock.DateTime
......@@ -38,10 +40,14 @@ expect fun localIpAddress(): String
/**
* 上传群图片
*/
expect suspend fun httpPostGroupImage(
expect suspend fun httpPostFriendImage(
uKeyHex: String,
fileSize: Int,
botNumber: Long,
groupCode: Long,
botNumber: UInt,
qq: UInt,
imageData: ByteArray
): Boolean
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(),
private fun ByteArray.checkLength(): ByteArray {
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
}
\ No newline at end of file
......@@ -49,6 +49,7 @@ fun String.hexToUBytes(): UByteArray = HexCache.hexToUBytes(this)
fun String.hexToInt(): Int = hexToBytes().toUInt().toInt()
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.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it }
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.contact
import kotlinx.coroutines.launch
......@@ -18,7 +20,7 @@ import net.mamoe.mirai.utils.ContactList
* @author Him188moe
*/
@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)
......@@ -71,7 +73,7 @@ actual sealed class Contact actual constructor(bot: Bot, number: Long) : Platfor
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
* @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 members: ContactList<QQ>
//todo members
......@@ -100,7 +102,7 @@ actual class Group actual constructor(bot: Bot, number: Long) : Contact(bot, num
*
* @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) {
bot.network[EventPacketHandler].sendFriendMessage(this, message)
}
......
......@@ -2,97 +2,186 @@
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.writeFully
import kotlinx.io.core.*
import net.mamoe.mirai.utils.*
import java.io.File
import javax.imageio.ImageIO
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]
* - 服务器未存有, 返回一个 key 用于客户端上传 [ServerTryGetImageIDSuccessPacket]
*
* @author Him188moe
*/
@PacketId(0x03_88u)
@PacketId(0x03_52u)
class ClientTryGetImageIDPacketJvm(
private val botNumber: Long,
private val botNumber: UInt,
private val sessionKey: ByteArray,
private val groupNumberOrAccount: Long,
private val target: UInt,
private val image: PlatformImage
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
//一次 body
//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
//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
this.writeQQ(botNumber)
this.writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00")
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(botNumber)
//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)
writeUShort(0x07_00u)
writeZero(1)
//proto
val packet = buildPacket {
writeUByte(0x08u)
writeUShort(0x01_12u)
writeUShort(0x03_98u)
writeUShort(0x01_01u)
writeUShort(0x08_01u)
writeUShort(0x12_47u)//?似乎会变
writeUByte(0x08u)
writeUVarInt(target)//todo 这两qq号反过来放也tm可以成功
writeUByte(0x10u)
writeUVarInt(botNumber)
writeUShort(0x18_00u)
writeUByte(0x22u)
writeUByte(0x10u)
writeFully(md5(imageData))
writeUByte(0x28u)
writeUVarInt(imageData.size.toUInt())
writeUByte(0x32u)
//长度应为1A
writeUVarintLVPacket {
writeUShort(0x28_00u)
writeUShort(0x46_00u)
writeUShort(0x51_00u)
writeUShort(0x56_00u)
writeUShort(0x4B_00u)
writeUShort(0x41_00u)
writeUShort(0x49_00u)
writeUShort(0x25_00u)
writeUShort(0x4B_00u)
writeUShort(0x24_00u)
writeUShort(0x55_00u)
writeUShort(0x30_00u)
writeUShort(0x24_00u)
}
writeHex("07 00")
writeZero(2)
writeHex("5B")//原5E
writeHex("08")
writeHex("01 12 03 98 01 01 10 01")
writeHex("1A")
writeHex("57")//原5A
writeHex("08")
writeUVarInt(groupNumberOrAccount)//FB D2 D8 94
writeByte(0x02)
writeHex("10")
writeUVarInt(botNumber)//A2 FF 8C F0
writeHex("18 00")
writeHex("22")
writeHex("10")
writeFully(md5(byteArray))
writeHex("28")
writeUVarInt(byteArray.size.toUInt())//E2 0D
writeHex("32")
writeHex("1A")
//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
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")
writeHex("38 01")
writeHex("48 01")
writeUShort(0x38_01u)
writeUShort(0x48_00u)
writeHex("50")
writeUByte(0x70u)
writeUVarInt(image.width.toUInt())
writeHex("58")
writeUByte(0x78u)
writeUVarInt(image.height.toUInt())
}
writeShort((packet.remaining - 7).toShort())//why?
writePacket(packet)
writeHex("60 04")
writeHex("6A")
writeHex("05")
writeHex("32 36 36 35 36")
writeHex("70 00")
writeHex("78 03")
writeHex("80 01")
writeHex("00")
//println(this.build().readBytes().toUHexString())
}
}
}
\ No newline at end of file
......@@ -7,4 +7,4 @@ import javax.imageio.ImageIO
actual typealias PlatformImage = BufferedImage
@JvmOverloads
actual fun BufferedImage.toByteArray(formatName: String): ByteArray = ByteArrayOutputStream().use { ImageIO.write(this, "JPG", it); it.toByteArray() }
\ No newline at end of file
actual fun BufferedImage.toByteArray(formatName: String): ByteArray = ByteArrayOutputStream().use { ImageIO.write(this, "PNG", it); it.toByteArray() }
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jsoup.Connection
import org.jsoup.Jsoup
import java.net.HttpURLConnection
import java.net.InetAddress
import java.net.URL
import java.security.MessageDigest
import java.util.zip.CRC32
......@@ -19,36 +19,62 @@ actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(host
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,
fileSize: Int,
botNumber: Long,
groupCode: Long,
botNumber: UInt,
qq: UInt,
imageData: ByteArray
): Boolean = Jsoup
.connect("http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" +
): Boolean {/*Jsoup
//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(" ", "") +
"&filezise=" + fileSize +
"&range=" + "0" +
"&uin=" + botNumber +
"&groupcode=" + groupCode)
.userAgent("QQClient")
"&uin=" + botNumber.toLong())
//.userAgent("QQClient")
.header("Content-Length", fileSize.toString())
.requestBody(String(imageData))
.requestBody(String(imageData, Charset.forName("ASCII")))
.method(Connection.Method.POST)
.ignoreContentType(true)
.let {
withContext(Dispatchers.IO) {
it.execute()
}
}
/*
val conn = URL(builder).openConnection() as HttpURLConnection
};*/
val conn = URL("http://101.227.143.109/cgi-bin/httpconn" +
"?htcmd=0x6ff0070" +
"&ver=5603" +
"&ukey=" + uKeyHex.replace(" ", "") +
"&filezise=" + fileSize +
"&range=" + "0" +
"&uin=" + botNumber.toLong()).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "QQClient")
conn.setRequestProperty("Content-Length", "" + fileSize)
conn.requestMethod = "POST"
conn.doOutput = true
conn.outputStream.write(img)
conn.outputStream.buffered().write(imageData)
conn.connect()*/
.statusCode() == 200
\ No newline at end of file
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.GlobalScope
import kotlinx.coroutines.runBlocking
......@@ -44,7 +46,7 @@ suspend fun main() {
runBlocking {
val bot = Bot(
BotAccount(
qq,
qq.toUInt(),
if (password.endsWith(".")) password.substring(0, password.length - 1) else password
),
Console()
......
......@@ -8,6 +8,8 @@ dependencies {
implementation rootProject.ext.coroutineCommon
implementation rootProject.ext.kotlinJvm
implementation rootProject.ext.kotlinxIOJvm
compile "org.jetbrains.kotlin:kotlin-reflect:1.3.50"
implementation 'org.jsoup:jsoup:1.12.1'
}
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.packet.IPPacket
......@@ -31,7 +31,7 @@ object Main {
jpcap = JpcapCaptor.openDevice(devices[0], caplen, promiscCheck, 50)
while (true) {
assert(jpcap != null)
val pk = jpcap!!.packet
val pk = jpcap!!.packet ?: continue
if (pk is IPPacket && pk.version.toInt() == 4) {
if (pk is UDPPacket) {
......@@ -47,6 +47,7 @@ object Main {
}
} else {
try {
//println("发出包全部=${pk.data.toUHexString()}")
dataSent(pk.data)
println()
} catch (e: Throwable) {
......@@ -73,20 +74,36 @@ object Main {
* 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 qq: UInt = 1040400290u
fun dataReceived(data: ByteArray) {
println("--------------")
println("接收数据包")
//println("raw = " + data.toUHexString())
data.read {
discardExact(3)
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("解密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) {
......@@ -118,15 +135,38 @@ object Main {
}
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("---------------------------")
discardExact(3)//head
val idHex = readBytes(4).toUHexString()
println("发出包ID = $idHex")
readUInt()//客户端登录的qq
println("TIM的fixVer2=" + readBytes(TIMProtocol.fixVer2.hexToBytes().size).toUHexString())
if (readUInt() != qq) {
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()
println("解密body = ${encryptedBody.decryptBy(sessionKey).toUHexString()}")
try {
println("解密body=${encryptedBody.decryptBy(sessionKey).toUHexString()}")
} catch (e: DecryptionFailedException) {
println("密文=" + encryptedBody.toUHexString())
println("解密body=解密失败")
}
encryptedBody.read {
......@@ -139,6 +179,8 @@ object Main {
val messageData = raw.decryptBy(sessionKey)
println("解密结果: " + messageData.toUHexString())
println("尝试解消息")
try {
messageData.read {
discardExact(
4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2
......@@ -147,6 +189,9 @@ object Main {
val chain = readMessageChain()
println(chain)
}
} catch (e: Exception) {
println("失败")
}
}
"03 88" -> {
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package demo1
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull
import net.mamoe.mirai.Bot
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.GroupMessageEvent
import net.mamoe.mirai.event.subscribeAll
......@@ -10,12 +14,16 @@ import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeUntilFalse
import net.mamoe.mirai.login
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.ImageId
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.utils.BotAccount
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlatformLogger
import net.mamoe.mirai.network.protocol.tim.packet.uploadImage
import net.mamoe.mirai.network.session
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.*
import java.io.File
import javax.imageio.ImageIO
private fun readTestAccount(): BotAccount? {
val file = File("testAccount.txt")
......@@ -25,7 +33,7 @@ private fun readTestAccount(): BotAccount? {
val lines = file.readLines()
return try {
BotAccount(lines[0].toLong(), lines[1])
BotAccount(lines[0].toUInt(), lines[1])
} catch (e: IndexOutOfBoundsException) {
null
}
......@@ -34,7 +42,7 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = Bot(readTestAccount() ?: BotAccount(//填写你的账号
account = 1994701121,
account = 1994701121u,
password = "123456"
), PlatformLogger())
......@@ -61,20 +69,31 @@ suspend fun main() {
"复读" 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 ->
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 ->
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