Improve performance

This commit is contained in:
Him188 2019-10-18 18:42:45 +08:00
parent 24404f9af4
commit fdb3be9453
35 changed files with 279 additions and 261 deletions

View File

@ -24,15 +24,17 @@ internal fun IoBuffer.parsePlainText(): PlainText {
internal fun IoBuffer.parseMessageImage0x06(): Image {
discardExact(1)
this.debugPrint("好友的图片")
//MiraiLogger.logDebug(this.toUHexString())
val filenameLength = readShort()
val suffix = readString(filenameLength).substringAfter(".")
discardExact(this@parseMessageImage0x06.readRemaining - 37 - 1 - filenameLength - 2)
val imageId = readString(36)
MiraiLogger.logDebug(imageId)
discardExact(1)//0x41
return Image("{$imageId}.$suffix")
with(this.debugPrint("好友的图片")) {
//MiraiLogger.logDebug(this.toUHexString())
val filenameLength = readShort()
val suffix = readString(filenameLength).substringAfter(".")
discardExact(this.readRemaining - 37 - 1 - filenameLength - 2 - 8 - 4)
val imageId = readString(36)
MiraiLogger.logDebug("imageId=$imageId")//todo ID似乎错了??
discardExact(1)//0x41
return Image("{$imageId}.$suffix")
}
}
internal fun IoBuffer.parseMessageImage0x03(): Image {

View File

@ -1,6 +1,8 @@
package net.mamoe.mirai.network
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelChildren
import kotlinx.io.core.Closeable
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocketAdapter
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
@ -27,6 +29,7 @@ import kotlin.coroutines.ContinuationInterceptor
*
* A BotNetworkHandler is used to connect with Tencent servers.
*/
@Suppress("PropertyName")
interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable {
/**
* [BotNetworkHandler] 的协程作用域.
@ -38,11 +41,11 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable {
* - SKey 刷新 [ClientSKeyRefreshmentRequestPacket]
* - 所有数据包处理和发送
*
* [BotNetworkHandler.close] 时将会 [取消][CoroutineScope.cancel] 所有此作用域下的协程
* [BotNetworkHandler.close] 时将会 [取消][kotlin.coroutines.CoroutineContext.cancelChildren] 所有此作用域下的协程
*/
val NetworkScope: CoroutineScope
var socket: Socket
val socket: Socket
/**
* 得到 [PacketHandler].
@ -57,9 +60,10 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable {
suspend fun login(configuration: LoginConfiguration): LoginResult
/**
* 添加一个临时包处理器
* 添加一个临时包处理器, 并发送相应的包
*
* @see [TemporaryPacketHandler]
* @see [BotSession.sendAndExpect] 发送并期待一个包
* @see [TemporaryPacketHandler] 临时包处理器
*/
suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>)

View File

@ -1,3 +1,5 @@
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package net.mamoe.mirai.network
import kotlinx.coroutines.CompletableJob
@ -50,7 +52,7 @@ class BotSession(
*
* 实现方法:
* ```kotlin
* session.expectPacket<ServerPacketXXX> {
* session.sendAndExpect<ServerPacketXXX> {
* toSend { ClientPacketXXX(...) }
* onExpect {//it: ServerPacketXXX
*
@ -62,7 +64,7 @@ class BotSession(
* @param handlerTemporary 处理器.
*/
//@JvmSynthetic
suspend inline fun <reified P : ServerPacket> expectPacket(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableJob {
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
@ -71,28 +73,28 @@ class BotSession(
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket].
* 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时.
* 由于包名可能过长, 可使用 `DataPacketSocketAdapter.expectPacket(PacketProcessor)` 替代.
* 由于包名可能过长, 可使用 `DataPacketSocketAdapter.sendAndExpect(PacketProcessor)` 替代.
*
* 实现方法:
* ```kotlin
* session.expectPacket<ServerPacketXXX>(ClientPacketXXX(...)) {//it: ServerPacketXXX
*
* ClientPacketXXX(...).sendAndExpect<ServerPacketXXX> {
* //it: ServerPacketXXX
* }
* ```
*
* @param P 期待的包
* @param toSend 将要发送的包
* @param handler 处理期待的包
*/
//@JvmSynthetic
suspend inline fun <reified P : ServerPacket> expectPacket(toSend: ClientPacket, noinline handler: suspend (P) -> Unit): CompletableJob {
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()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, job, this).also {
it.toSend(toSend)
bot.network.addHandler(TemporaryPacketHandler(P::class, job, this@BotSession).also {
it.toSend(this)
it.onExpect(handler)
})
return job
}
suspend inline fun ClientPacket.send() = socket.sendPacket(this)
}

View File

@ -30,6 +30,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
override val NetworkScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
override lateinit var socket: BotSocketAdapter
private set
internal val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*>>()
private val handlersLock = Mutex()
@ -73,6 +74,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
add(EventPacketHandler(session).asNode(EventPacketHandler))
add(ActionPacketHandler(session).asNode(ActionPacketHandler))
bot.logger.logPurple("Successfully logged in")
}
private lateinit var sessionKey: ByteArray
@ -110,9 +112,10 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
try {
channel.read(buffer)//JVM: withContext(IO)
} catch (e: ReadPacketInternalException) {
} catch (e: Exception) {
e.log()
//read failed, continue and reread
continue
} catch (e: Throwable) {
e.log()//other unexpected exceptions caught.
continue
}
@ -123,11 +126,11 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
NetworkScope.launch {
try {
//Ensure the packet is consumed totally so that all buffers are released
//`.use`: Ensure that the packet is consumed totally so that all the buffers are released
ByteReadPacket(buffer, IoBuffer.Pool).use {
distributePacket(it.parseServerPacket(buffer.readRemaining))
}
} catch (e: Exception) {
} catch (e: Throwable) {
e.log()
}
}
@ -168,6 +171,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
try {
packet.decode()
} catch (e: Exception) {
e.log()
bot.printPacketDebugging(packet)
packet.close()
throw e
@ -238,9 +242,6 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
bot.logger.logError("Caught SendPacketInternalException: ${e.cause?.message}")
bot.reinitializeNetworkHandler(configuration)
return@withContext
} catch (e: Throwable) {
e.log()
return@withContext
} finally {
buffer.release(IoBuffer.Pool)
}
@ -338,7 +339,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
is ServerLoginResponseCaptchaInitPacket -> {
//[token00BA]来源之一: 验证码
this.token00BA = packet.token00BA
this.captchaCache = packet.verifyCodePart1
this.captchaCache = packet.captchaPart1
if (packet.unknownBoolean == true) {
this.captchaSectionId = 1
@ -390,12 +391,14 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
privateKey = privateKey,
token0825 = token0825,
token00BA = packet.tokenUnknown ?: token00BA,
randomDeviceName = socket.configuration.randomDeviceName
randomDeviceName = socket.configuration.randomDeviceName,
tlv0006 = packet.tlv0006
))
}
is ServerSessionKeyResponsePacket -> {
sessionKey = packet.sessionKey
bot.logger.logPurple("sessionKey = ${sessionKey.toUHexString()}")
heartbeatJob = NetworkScope.launch {
while (socket.isOpen) {
@ -412,14 +415,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
is ServerLoginSuccessPacket -> {
BotLoginSucceedEvent(bot).broadcast()
//登录成功后会收到大量上次的消息, 忽略掉 todo 优化
NetworkScope.launch {
delay(3000)
this@TIMBotNetworkHandler[EventPacketHandler].ignoreMessage = false
}
onLoggedIn(sessionKey)
this.close()//The LoginHandler is useless since then
}

View File

@ -16,6 +16,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResp
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerSKeyResponsePacket
import net.mamoe.mirai.utils.log
/**
* 动作: 获取好友列表, 点赞, 踢人等.
@ -64,7 +65,11 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
sKeyRefresherJob = session.scope.launch {
while (session.isOpen) {
delay(1800000)
session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.account, session.sessionKey))
try {
session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.account, session.sessionKey))
} catch (e: Throwable) {
e.log()
}
}
}
}

View File

@ -41,7 +41,7 @@ interface DataPacketSocketAdapter : Closeable {
*
* 可通过 hook 事件 [ServerPacketReceivedEvent] 来获取服务器返回.
*
* @see [BotSession.expectPacket] kotlin DSL
* @see [BotSession.sendAndExpect] kotlin DSL
*/
suspend fun sendPacket(packet: ClientPacket)

View File

@ -26,9 +26,6 @@ import net.mamoe.mirai.utils.MiraiLogger
class EventPacketHandler(session: BotSession) : PacketHandler(session) {
companion object Key : PacketHandler.Key<EventPacketHandler>
internal var ignoreMessage: Boolean = true
override suspend fun onPacketReceived(packet: ServerPacket): Unit = with(session) {
when (packet) {
is ServerGroupUploadFileEventPacket -> {
@ -36,14 +33,12 @@ class EventPacketHandler(session: BotSession) : PacketHandler(session) {
}
is ServerFriendMessageEventPacket -> {
if (ignoreMessage) return
FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message).broadcast()
if (!packet.isPrevious) {
FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message).broadcast()
}
}
is ServerGroupMessageEventPacket -> {
if (ignoreMessage) return
if (packet.qq.toLong() == bot.account.account) return
GroupMessageEvent(bot, bot.getGroupByNumber(packet.groupNumber), bot.getQQ(packet.qq), packet.message).broadcast()

View File

@ -17,11 +17,11 @@ import kotlin.reflect.KClass
* }
* ```
*
* @see BotSession.expectPacket
* @see BotSession.sendAndExpect
*/
class TemporaryPacketHandler<P : ServerPacket>(
private val expectationClass: KClass<P>,
private val deferred: CompletableJob,
private val job: CompletableJob,
private val fromSession: BotSession
) {
private lateinit var toSend: ClientPacket
@ -54,7 +54,7 @@ class TemporaryPacketHandler<P : ServerPacket>(
kotlin.runCatching {
@Suppress("UNCHECKED_CAST")
expect(packet as P)
}.onFailure { deferred.completeExceptionally(it) }.onSuccess { deferred.complete() }
}.onFailure { job.completeExceptionally(it) }.onSuccess { job.complete() }
return true
}
return false

View File

@ -5,7 +5,9 @@ package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.encryptAndWrite
import net.mamoe.mirai.utils.writeHex
import net.mamoe.mirai.utils.writeQQ
@PacketId(0x00_58u)
class ClientHeartbeatPacket(
@ -13,9 +15,9 @@ class ClientHeartbeatPacket(
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer)
this.encryptAndWrite(sessionKey) {
writeQQ(bot)
writeHex(TIMProtocol.fixVer)
encryptAndWrite(sessionKey) {
writeHex("00 01 00 01")
}
}

View File

@ -2,17 +2,16 @@
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.Closeable
import net.mamoe.mirai.utils.toUHexString
/**
* 数据包.
*/
abstract class Packet : Closeable {
abstract class Packet {
/**
* 2 Ubyte
*/
open val id: UShort = (this::class.annotations.firstOrNull { it is PacketId } as? PacketId)?.value ?: error("Annotation PacketId not found")
open val id: UShort by lazy { (this::class.annotations.firstOrNull { it is PacketId } as? PacketId)?.value ?: error("Annotation PacketId not found") }
/**
* 包序列 id. 唯一

View File

@ -26,13 +26,20 @@ fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) =
writeFully(uniqueId)
}
fun <S : ServerEventPacket> S.applyId(id: UShort): S {
this.id = id
return this
}
/**
* Packet id: `00 CE` or `00 17`
*
* @author Him188moe
*/
abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: EventPacketIdentity) : ServerPacket(input) {
class Raw(input: ByteReadPacket) : ServerPacket(input) {
override var id: UShort = 0u
class Raw(input: ByteReadPacket, override val id: UShort) : ServerPacket(input) {
fun distribute(): ServerEventPacket = with(input) {
val eventIdentity = EventPacketIdentity(
from = readUInt(),
@ -65,7 +72,7 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
println(readUByte().toUInt())
//todo 错了. 可能是 00 79 才是.
return@with ServerFriendTypingCanceledPacket(input, eventIdentity)
ServerFriendTypingCanceledPacket(input, eventIdentity)
/*
if (readUByte().toUInt() == 0x37u) ServerFriendTypingStartedPacket(input, eventIdentity)
else /*0x22*/ ServerFriendTypingCanceledPacket(input, eventIdentity)*/
@ -81,8 +88,8 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
}.applyId(id).applySequence(sequenceId)
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey)).applyId(id).applySequence(sequenceId)
class Encrypted(input: ByteReadPacket, override var id: UShort, override var sequenceId: UShort) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey), id).applySequence(sequenceId)
}
}
@ -185,8 +192,8 @@ class ServerGroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventP
senderName = map.getValue(18).read {
val tlv = readTLVMap(true)
tlv.printTLVMap("子map")
//群主的18: 05 00 04 00 00 00 03 08 00 04 00 00 00 04 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
////群主的18: 05 00 04 00 00 00 03 08 00 04 00 00 00 04 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
//群主的 子map= {5=00 00 00 03, 8=00 00 00 04, 1=48 69 6D 31 38 38 6D 6F 65, 3=04, 4=00 00 00 08}
when {
tlv.containsKey(0x01) -> String(tlv.getValue(0x01))
tlv.containsKey(0x02) -> String(tlv.getValue(0x02))
@ -202,7 +209,7 @@ class ServerGroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventP
//刚刚的消息: 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD 11 F4 B2 F2 1A E7 1F C4 F1 3F 23 FB 74 80 42 64 00 0B 78 1A 5D A3 26 C1 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A3 26 C1 AA 34 08 42 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E4 BD A0 E5 A5 BD 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
fun main() {
println("08 02 1A 12 08 95 02 10 90 04 40 D6 DE 8C ED 05 48 CF B5 90 D6 02 08 DD F1 92 B7 07 10 DD F1 92 B7 07 1A 14 08 00 10 05 18 D6 DE 8C ED 05 20 02 28 FF FF FF FF 0F 32 00".hexToBytes().stringOf())
println("01 00 32 AA 02 2F 50 03 60 00 68 00 9A 01 26 08 09 20 BF 02 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00".hexToBytes().stringOf())
}
fun main2() {

View File

@ -16,7 +16,8 @@ import kotlin.properties.Delegates
* @see parseServerPacket
*/
abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable {
override var id: UShort = super.id
override val id: UShort by lazy { super.id }
override var sequenceId: UShort by Delegates.notNull()
open fun decode() {
@ -28,11 +29,6 @@ abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable {
override fun toString(): String = this.packetToString()
}
fun <S : ServerPacket> S.applyId(id: UShort): S {
this.id = id
return this
}
fun <S : ServerPacket> S.applySequence(sequenceId: UShort): S {
this.sequenceId = sequenceId
return this

View File

@ -1,14 +1,18 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.gotoWhere
import net.mamoe.mirai.utils.toReadPacket
expect class PlatformImage
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.account
import net.mamoe.mirai.network.protocol.tim.handler.ActionPacketHandler
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.*
@PacketId(0x03_88u)
expect class ClientTryGetImageIDPacket(
botNumber: Long,
sessionKey: ByteArray,
@ -16,19 +20,18 @@ expect class ClientTryGetImageIDPacket(
image: PlatformImage
) : ClientPacket
abstract class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
@PacketId(0x03_88u)
sealed class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
@PacketId(0x03_88u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket {
val data = this.decryptAsByteArray(sessionKey)
println(data.size)
println(data.size)
println("ServerTryGetImageIDResponsePacket.size=" + data.size)
if (data.size == 209) {
return ServerTryGetImageIDSuccessPacket(data.toReadPacket()).applySequence(sequenceId)
}
return ServerTryGetImageIDFailedPacket(data.toReadPacket())
return ServerTryGetImageIDFailedPacket(data.toReadPacket()).applySequence(sequenceId)
}
}
}
@ -36,12 +39,14 @@ abstract class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : Server
/**
* 服务器未存有图片, 返回一个 key 用于客户端上传
*/
@PacketId(0x03_88u)
class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
lateinit var uKey: ByteArray
override fun decode() {
this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u))
this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u))//todo 优化
uKey = this.input.readBytes(128)
DebugLogger.logPurple("获得 uKey(128)=${uKey.toUHexString()}")
}
}
@ -49,7 +54,41 @@ class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImag
* 服务器已经存有这个图片
*/
class ServerTryGetImageIDFailedPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
override fun decode() {
override fun decode(): Unit = with(input) {
readRemainingBytes().debugPrint("ServerTryGetImageIDFailedPacket的body")
}
}
suspend fun Group.uploadImage(imageId: String, image: PlatformImage) {
this.bot.network[ActionPacketHandler].session.uploadGroupImage(number, imageId, image)
}
suspend fun QQ.uploadImage(imageId: String, image: PlatformImage) {
TODO()
}
suspend fun BotSession.uploadGroupImage(groupNumberOrAccount: Long, imageId: String, image: PlatformImage) {
ClientTryGetImageIDPacket(
account,
sessionKey,
groupNumberOrAccount,
image
).sendAndExpect<ServerTryGetImageIDResponsePacket> {
when (it) {
is ServerTryGetImageIDFailedPacket -> {
//服务器已存有图片
}
is ServerTryGetImageIDSuccessPacket -> {
val data = image.toByteArray()
httpPostGroupImage(
uKeyHex = it.uKey.toUHexString(""),
botNumber = bot.qqAccount,
fileSize = data.size,
imageData = data,
groupCode = groupNumberOrAccount
)
//todo HTTP upload image.
}
}
}.join()
}

View File

@ -7,19 +7,22 @@ import kotlinx.io.core.writeUByte
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.utils.*
import net.mamoe.mirai.utils.OnlineStatus
import net.mamoe.mirai.utils.encryptAndWrite
import net.mamoe.mirai.utils.writeHex
import net.mamoe.mirai.utils.writeQQ
/**
* 改变在线状态: "我在线上", "隐身"
*/
@PacketId(0x00_ECu)
class ClientChangeOnlineStatusPacket(
private val qq: Long,
private val bot: Long,
private val sessionKey: ByteArray,
private val loginStatus: OnlineStatus
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
writeHex("01 00")

View File

@ -45,8 +45,10 @@ class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() = with(input) {
discardExact(4)
sKey = this.readString(10)//todo test
MiraiLogger.logDebug("SKey=$sKey")
//debugDiscardExact(2)
sKey = this.readString(10)
DebugLogger.logPurple("SKey=$sKey")
DebugLogger.logPurple("Skey包后面${this.readRemainingBytes().toUHexString()}")
}
@PacketId(0x00_1Du)

View File

@ -16,8 +16,6 @@ class ServerLoginResponseFailedPacket(val loginResult: LoginResult, input: ByteR
/**
* 服务器进行加密后返回 privateKey
*
* @author NaturalHG
*/
@PacketId(0x08_36u)
class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
@ -29,7 +27,7 @@ class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket) : ServerLoginR
@Tested
override fun decode() {
this.input.discardExact(5)//01 00 1E 00 10
privateKeyUpdate = this.input.readBytes(0x10)//22
privateKeyUpdate = this.input.readBytes(0x10)
this.input.discardExact(4)//00 06 00 78
tlv0006 = this.input.readIoBuffer(0x78)
@ -127,7 +125,7 @@ class ServerLoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginRespo
@PacketId(0x08_36u)
class ServerLoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var verifyCodePart1: IoBuffer
lateinit var captchaPart1: IoBuffer
lateinit var token00BA: ByteArray
var unknownBoolean: Boolean? = null
@ -136,8 +134,8 @@ class ServerLoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginR
override fun decode() {
this.input.discardExact(78)
//println(this.input.readRemainingBytes().toUHexString())
val verifyCodeLength = this.input.readShort()//2bytes
this.verifyCodePart1 = this.input.readIoBuffer(verifyCodeLength)
val captchaLength = this.input.readShort()//2bytes
this.captchaPart1 = this.input.readIoBuffer(captchaLength)
this.input.discardExact(1)

View File

@ -9,14 +9,14 @@ import net.mamoe.mirai.utils.*
@PacketId(0x08_28u)
class ClientSessionRequestPacket(
private val qq: Long,
private val bot: Long,
private val serverIp: String,
private val token38: IoBuffer,
private val token88: IoBuffer,
private val encryptionKey: IoBuffer
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeQQ(bot)
this.writeHex("02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A")
this.writeHex("00 38")
this.writeFully(token38)
@ -31,7 +31,7 @@ class ClientSessionRequestPacket(
writeHex("00 36 00 12 00 02 00 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00")
writeHex(TIMProtocol.constantData1)
writeHex(TIMProtocol.constantData2)
writeQQ(qq)
writeQQ(bot)
writeHex("00 00 00 00 00 1F 00 22 00 01")
writeHex("1A 68 73 66 E4 BA 79 92 CC C2 D4 EC 14 7C 8B AF 43 B0 62 FB 65 58 A9 EB 37 55 1D 26 13 A8 E5 3D")//device ID

View File

@ -69,12 +69,12 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
}
0x08_28u -> ServerSessionKeyResponsePacket.Encrypted(this)
0x00_EC_u -> ServerSKeyResponsePacket(this)
0x00_EC_u -> ServerLoginSuccessPacket(this)
0x00_1D_u -> ServerSKeyResponsePacket.Encrypted(this)
0x00_5C_u -> ServerAccountInfoResponsePacket.Encrypted(this)
0x00_58_u -> ServerHeartbeatResponsePacket(this)
0x00_BA_u -> ServerCaptchaPacket.Encrypted(this)
0x00_CE_u, 0x00_17_u -> ServerEventPacket.Raw.Encrypted(this)
0x00_CE_u, 0x00_17_u -> ServerEventPacket.Raw.Encrypted(this, id, sequenceId)
0x00_81_u -> ServerFieldOnlineStatusChangedPacket.Encrypted(this)
0x00_CD_u -> ServerSendFriendMessageResponsePacket(this)
0x00_02_u -> ServerSendGroupMessageResponsePacket(this)
@ -82,7 +82,7 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
0x03_88_u -> ServerTryGetImageIDResponsePacket.Encrypted(this)
else -> UnknownServerPacket.Encrypted(this, id, sequenceId)
}.applyId(id).applySequence(sequenceId)
}.applySequence(sequenceId)
}
fun Input.readIP(): String = buildString(4 + 3) {
@ -145,6 +145,7 @@ fun Input.readLVNumber(): Number {
//@JvmSynthetic
@Deprecated("Low efficiency", ReplaceWith(""))
fun <I : Input> I.gotoWhere(matcher: UByteArray): I {
@Suppress("DEPRECATION")
return this.gotoWhere(matcher.toByteArray())
}

View File

@ -15,19 +15,19 @@ internal fun ByteArray.debugPrint(name: String): ByteArray {
return this
}
@Deprecated("Low Efficiency", ReplaceWith(""))
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(""))
internal fun IoBuffer.debugPrint(name: String): IoBuffer {
val readBytes = this.readBytes()
DebugLogger.logPurple(name + "=" + readBytes.toUHexString())
return readBytes.toIoBuffer()
}
@Deprecated("Low Efficiency", ReplaceWith("discardExact(n)"))
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("discardExact(n)"))
internal fun Input.debugDiscardExact(n: Number, name: String = "") {
DebugLogger.logPurple("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
}
@Deprecated("Low Efficiency", ReplaceWith(""))
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(""))
internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
val bytes = this.readBytes()
DebugLogger.logPurple("ByteReadPacket $name=" + bytes.toUHexString())

View File

@ -0,0 +1,9 @@
package net.mamoe.mirai.utils
import kotlin.jvm.JvmOverloads
expect class PlatformImage
@JvmOverloads
expect fun PlatformImage.toByteArray(formatName: String = "JPG"): ByteArray

View File

@ -31,4 +31,15 @@ expect fun solveIpAddress(hostname: String): String
/**
* Localhost 解析
*/
expect fun localIpAddress(): String
expect fun localIpAddress(): String
/**
* 上传群图片
*/
expect suspend fun httpPostGroupImage(
uKeyHex: String,
fileSize: Int,
botNumber: Long,
groupCode: Long,
imageData: ByteArray
): Boolean

View File

@ -0,0 +1,22 @@
package net.mamoe.mirai.utils
import kotlin.properties.Delegates
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
fun <T : Any> Delegates.notNullBy(initializer: () -> T): ReadWriteProperty<Any?, T> = NotNullVarWithDefault(lazy(initializer = initializer))
class NotNullVarWithDefault<T : Any>(
private val initializer: Lazy<T>
) : ReadWriteProperty<Any?, T> {
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: initializer.value
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}

View File

@ -35,7 +35,6 @@ actual sealed class Contact actual constructor(bot: Bot, number: Long) : Platfor
*/
fun blockingSendMessage(message: Message) = runBlocking { sendMessage(message) }
/**
* 阻塞发送一个消息. 仅应在 Java 使用
*/
@ -44,23 +43,17 @@ actual sealed class Contact actual constructor(bot: Bot, number: Long) : Platfor
/**
* 异步发送一个消息. 仅应在 Java 使用
*/
fun asyncSendMessage(chain: MessageChain) {
bot.network.NetworkScope.launch { sendMessage(chain) }
}
fun asyncSendMessage(chain: MessageChain) = bot.network.NetworkScope.launch { sendMessage(chain) }
/**
* 异步发送一个消息. 仅应在 Java 使用
*/
fun asyncSendMessage(message: Message) {
bot.network.NetworkScope.launch { sendMessage(message) }
}
fun asyncSendMessage(message: Message) = bot.network.NetworkScope.launch { sendMessage(message) }
/**
* 异步发送一个消息. 仅应在 Java 使用
*/
fun asyncSendMessage(plain: String) {
bot.network.NetworkScope.launch { sendMessage(plain) }
}
fun asyncSendMessage(plain: String) = bot.network.NetworkScope.launch { sendMessage(plain) }
}
/**

View File

@ -1,73 +0,0 @@
package net.mamoe.mirai.message
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientTryGetImageIDPacketJvm
import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDFailedPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.ImageNetworkUtils
import net.mamoe.mirai.utils.md5
import net.mamoe.mirai.utils.toByteArray
import net.mamoe.mirai.utils.toUHexString
import java.awt.image.BufferedImage
import java.io.File
import java.net.URL
import javax.imageio.ImageIO
/**
* 不确定是否存在于服务器的 [Image].
* 必须在发送之前 [UnsolvedImage.upload] [Contact.uploadImage], 否则会发送失败.
*
* @suppress todo 重新设计
* @author Him188moe
*/
class UnsolvedImage(private val filename: String, val image: BufferedImage) {
constructor(imageFile: File) : this(imageFile.name, ImageIO.read(imageFile))
constructor(url: URL) : this(File(url.file))
suspend fun upload(session: BotSession, contact: Contact): CompletableJob {
return session.expectPacket<ServerTryGetImageIDResponsePacket> {
toSend { ClientTryGetImageIDPacketJvm(session.bot.qqAccount, session.sessionKey, contact.number, image) }
onExpect {
when (it) {
is ServerTryGetImageIDFailedPacket -> {
//已经存在于服务器了
}
is ServerTryGetImageIDSuccessPacket -> {
val data = image.toByteArray()
withContext(Dispatchers.IO) {
if (!ImageNetworkUtils.postImage(it.uKey.toUHexString(), data.size, session.bot.qqAccount, contact.number, data)) {
throw RuntimeException("cannot upload image")
}
}
}
}
}
}
}
fun toImage(): Image {
return Image(getImageId(filename))
}
companion object {
@JvmStatic
fun getImageId(filename: String): String {
val md5 = md5(filename)
return "{" + md5.copyOfRange(0, 4).toUHexString("") + "-"
.plus(md5.copyOfRange(4, 6).toUHexString("")) + "-"
.plus(md5.copyOfRange(6, 8).toUHexString("")) + "-"
.plus(md5.copyOfRange(8, 10).toUHexString("")) + "-"
.plus(md5.copyOfRange(10, 16).toUHexString("")) + "}." + if (filename.endsWith(".jpeg")) "jpg" else filename.substringAfter(".", "jpg")
}
}
}

View File

@ -4,7 +4,6 @@ package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.internal.DangerousInternalIoApi
import net.mamoe.mirai.utils.toUHexString
import java.lang.reflect.Field
@ -37,7 +36,8 @@ private object IgnoreIdList : List<String> by listOf(
"EMPTY_ID_HEX",
"input",
"output",
"UninitializedByteReadPacket"
"UninitializedByteReadPacket",
"sessionKey"
)
internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.idHexString})") + this::class.java.allDeclaredFields

View File

@ -5,9 +5,6 @@ package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.*
import java.awt.image.BufferedImage
actual typealias PlatformImage = BufferedImage
actual typealias ClientTryGetImageIDPacket = ClientTryGetImageIDPacketJvm

View File

@ -1,37 +0,0 @@
package net.mamoe.mirai.utils
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import javax.imageio.ImageIO
/**
* @author NaturalHG
*/
object ImageNetworkUtils {
@Throws(IOException::class)
fun postImage(uKeyHex: String, fileSize: Int, botNumber: Long, groupCode: Long, img: ByteArray): Boolean {
//http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc&ukey=” 删全部空 (ukey) “&filesize=” 到文本 (fileSize) “&range=0&uin=” g_uin “&groupcode=” Group
val builder = "http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" +
"&ukey=" + uKeyHex.replace(" ", "") +
"&filezise=" + fileSize +
"&range=" + "0" +
"&uin=" + botNumber +
"&groupcode=" + groupCode
val conn = URL(builder).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "QQClient")
conn.setRequestProperty("Content-Length", "" + fileSize)
conn.requestMethod = "POST"
conn.doOutput = true
conn.outputStream.write(img)
conn.connect()
return conn.responseCode == 200
}
}
fun BufferedImage.toByteArray(): ByteArray = ByteArrayOutputStream().use { ImageIO.write(this, "JPG", it); it.toByteArray() }

View File

@ -1,18 +0,0 @@
package net.mamoe.mirai.utils
import java.net.InetAddress
import java.security.MessageDigest
import java.util.zip.CRC32
actual val currentTime: Long = System.currentTimeMillis()
actual val deviceName: String = InetAddress.getLocalHost().hostName
actual fun crc32(key: ByteArray): Int = CRC32().let { it.update(key); it.value.toInt() }
actual fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray)
actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress
actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress

View File

@ -0,0 +1,10 @@
package net.mamoe.mirai.utils
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
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() }

View File

@ -14,6 +14,7 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv
private val serverAddress: InetSocketAddress = InetSocketAddress(serverHost, serverPort.toInt())
private val channel: DatagramChannel = DatagramChannel.open().connect(serverAddress)
@Throws(ReadPacketInternalException::class)
actual suspend fun read(buffer: IoBuffer) = withContext(Dispatchers.IO) {
try {
(channel as ReadableByteChannel).read(buffer)
@ -22,6 +23,7 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv
}
}
@Throws(SendPacketInternalException::class)
actual suspend fun send(buffer: IoBuffer) = withContext(Dispatchers.IO) {
buffer.readDirect {
try {

View File

@ -0,0 +1,56 @@
package net.mamoe.mirai.utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jsoup.Connection
import org.jsoup.Jsoup
import java.net.InetAddress
import java.security.MessageDigest
import java.util.zip.CRC32
actual val currentTime: Long = System.currentTimeMillis()
actual val deviceName: String = InetAddress.getLocalHost().hostName
actual fun crc32(key: ByteArray): Int = CRC32().let { it.update(key); it.value.toInt() }
actual fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray)
actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress
actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
actual suspend fun httpPostGroupImage(
uKeyHex: String,
fileSize: Int,
botNumber: Long,
groupCode: Long,
imageData: ByteArray
): Boolean = Jsoup
.connect("http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" +
"&ukey=" + uKeyHex.replace(" ", "") +
"&filezise=" + fileSize +
"&range=" + "0" +
"&uin=" + botNumber +
"&groupcode=" + groupCode)
.userAgent("QQClient")
.header("Content-Length", fileSize.toString())
.requestBody(String(imageData))
.method(Connection.Method.POST)
.ignoreContentType(true)
.let {
withContext(Dispatchers.IO) {
it.execute()
}
}
/*
val conn = URL(builder).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "QQClient")
conn.setRequestProperty("Content-Length", "" + fileSize)
conn.requestMethod = "POST"
conn.doOutput = true
conn.outputStream.write(img)
conn.connect()*/
.statusCode() == 200

View File

@ -76,7 +76,6 @@ object Main {
}
}
/**
* 可从 TIM 内存中读取
*
@ -89,7 +88,7 @@ object Main {
* 6. 运行到 `mov eax,dword ptr ss:[ebp+10]`
* 7. 查看内存, `eax` 开始的 16 bytes 便是 `sessionKey`
*/
val sessionKey: ByteArray = "9E A6 16 46 FF 15 FB 73 2F 31 0D 7E CB C4 E6 49".hexToBytes()
val sessionKey: ByteArray = "F1 68 24 ED A8 6D 33 6E 5C B7 E0 F4 45 77 21 04".hexToBytes()
fun dataReceived(data: ByteArray) {
println("--------------")

View File

@ -1,7 +1,6 @@
package demo1
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.event.events.FriendMessageEvent
@ -17,14 +16,11 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console
import net.mamoe.mirai.utils.MiraiLogger
import kotlin.coroutines.coroutineContext
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
suspend fun main() {
val bot = Bot(BotAccount(//填写你的账号
account = 2903772581,
password = "zxc123456"
account = 1994701021,
password = "asdhim188666"
), Console())
bot.login {
@ -79,7 +75,7 @@ suspend fun main() {
subscribeAll<FriendMessageEvent> {
always {
//获取第一个纯文本消息
val firstText = it.message.first<PlainText>()
val firstText = it.message.firstOrNull<PlainText>()
}
}