Commit fdb3be94 authored by Him188's avatar Him188

Improve performance

parent 24404f9a
...@@ -24,15 +24,17 @@ internal fun IoBuffer.parsePlainText(): PlainText { ...@@ -24,15 +24,17 @@ internal fun IoBuffer.parsePlainText(): PlainText {
internal fun IoBuffer.parseMessageImage0x06(): Image { internal fun IoBuffer.parseMessageImage0x06(): Image {
discardExact(1) discardExact(1)
this.debugPrint("好友的图片") with(this.debugPrint("好友的图片")) {
//MiraiLogger.logDebug(this.toUHexString()) //MiraiLogger.logDebug(this.toUHexString())
val filenameLength = readShort() val filenameLength = readShort()
val suffix = readString(filenameLength).substringAfter(".") val suffix = readString(filenameLength).substringAfter(".")
discardExact(this@parseMessageImage0x06.readRemaining - 37 - 1 - filenameLength - 2) discardExact(this.readRemaining - 37 - 1 - filenameLength - 2 - 8 - 4)
val imageId = readString(36) val imageId = readString(36)
MiraiLogger.logDebug(imageId) MiraiLogger.logDebug("imageId=$imageId")//todo ID似乎错了??
discardExact(1)//0x41 discardExact(1)//0x41
return Image("{$imageId}.$suffix") return Image("{$imageId}.$suffix")
}
} }
internal fun IoBuffer.parseMessageImage0x03(): Image { internal fun IoBuffer.parseMessageImage0x03(): Image {
......
package net.mamoe.mirai.network 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 kotlinx.io.core.Closeable
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocketAdapter import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocketAdapter
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
...@@ -27,6 +29,7 @@ import kotlin.coroutines.ContinuationInterceptor ...@@ -27,6 +29,7 @@ import kotlin.coroutines.ContinuationInterceptor
* *
* A BotNetworkHandler is used to connect with Tencent servers. * A BotNetworkHandler is used to connect with Tencent servers.
*/ */
@Suppress("PropertyName")
interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable { interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable {
/** /**
* [BotNetworkHandler] 的协程作用域. * [BotNetworkHandler] 的协程作用域.
...@@ -38,11 +41,11 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable { ...@@ -38,11 +41,11 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable {
* - SKey 刷新 [ClientSKeyRefreshmentRequestPacket] * - SKey 刷新 [ClientSKeyRefreshmentRequestPacket]
* - 所有数据包处理和发送 * - 所有数据包处理和发送
* *
* [BotNetworkHandler.close] 时将会 [取消][CoroutineScope.cancel] 所有此作用域下的协程 * [BotNetworkHandler.close] 时将会 [取消][kotlin.coroutines.CoroutineContext.cancelChildren] 所有此作用域下的协程
*/ */
val NetworkScope: CoroutineScope val NetworkScope: CoroutineScope
var socket: Socket val socket: Socket
/** /**
* 得到 [PacketHandler]. * 得到 [PacketHandler].
...@@ -57,9 +60,10 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable { ...@@ -57,9 +60,10 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable {
suspend fun login(configuration: LoginConfiguration): LoginResult suspend fun login(configuration: LoginConfiguration): LoginResult
/** /**
* 添加一个临时包处理器 * 添加一个临时包处理器, 并发送相应的包
* *
* @see [TemporaryPacketHandler] * @see [BotSession.sendAndExpect] 发送并期待一个包
* @see [TemporaryPacketHandler] 临时包处理器
*/ */
suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>)
......
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package net.mamoe.mirai.network package net.mamoe.mirai.network
import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CompletableJob
...@@ -50,7 +52,7 @@ class BotSession( ...@@ -50,7 +52,7 @@ class BotSession(
* *
* 实现方法: * 实现方法:
* ```kotlin * ```kotlin
* session.expectPacket<ServerPacketXXX> { * session.sendAndExpect<ServerPacketXXX> {
* toSend { ClientPacketXXX(...) } * toSend { ClientPacketXXX(...) }
* onExpect {//it: ServerPacketXXX * onExpect {//it: ServerPacketXXX
* *
...@@ -62,7 +64,7 @@ class BotSession( ...@@ -62,7 +64,7 @@ class BotSession(
* @param handlerTemporary 处理器. * @param handlerTemporary 处理器.
*/ */
//@JvmSynthetic //@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() val job = coroutineContext[Job].takeIf { it != null }?.let { Job(it) } ?: Job()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, job, this).also(handlerTemporary)) this.bot.network.addHandler(TemporaryPacketHandler(P::class, job, this).also(handlerTemporary))
return job return job
...@@ -71,28 +73,28 @@ class BotSession( ...@@ -71,28 +73,28 @@ class BotSession(
/** /**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket]. * 发送一个数据包, 并期待接受一个特定的 [ServerPacket].
* 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时. * 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时.
* 由于包名可能过长, 可使用 `DataPacketSocketAdapter.expectPacket(PacketProcessor)` 替代. * 由于包名可能过长, 可使用 `DataPacketSocketAdapter.sendAndExpect(PacketProcessor)` 替代.
* *
* 实现方法: * 实现方法:
* ```kotlin * ```kotlin
* session.expectPacket<ServerPacketXXX>(ClientPacketXXX(...)) {//it: ServerPacketXXX * ClientPacketXXX(...).sendAndExpect<ServerPacketXXX> {
* * //it: ServerPacketXXX
* } * }
* ``` * ```
* *
* @param P 期待的包 * @param P 期待的包
* @param toSend 将要发送的包
* @param handler 处理期待的包 * @param handler 处理期待的包
*/ */
//@JvmSynthetic suspend inline fun <reified P : ServerPacket> ClientPacket.sendAndExpect(noinline handler: suspend (P) -> Unit): CompletableJob {
suspend inline fun <reified P : ServerPacket> expectPacket(toSend: ClientPacket, noinline handler: suspend (P) -> Unit): CompletableJob {
val job = coroutineContext[Job].takeIf { it != null }?.let { Job(it) } ?: Job() val job = coroutineContext[Job].takeIf { it != null }?.let { Job(it) } ?: Job()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, job, this).also { bot.network.addHandler(TemporaryPacketHandler(P::class, job, this@BotSession).also {
it.toSend(toSend) it.toSend(this)
it.onExpect(handler) it.onExpect(handler)
}) })
return job return job
} }
suspend inline fun ClientPacket.send() = socket.sendPacket(this)
} }
......
...@@ -30,6 +30,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -30,6 +30,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
override val NetworkScope: CoroutineScope = CoroutineScope(Dispatchers.Default) override val NetworkScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
override lateinit var socket: BotSocketAdapter override lateinit var socket: BotSocketAdapter
private set
internal val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*>>() internal val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*>>()
private val handlersLock = Mutex() private val handlersLock = Mutex()
...@@ -73,6 +74,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -73,6 +74,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
add(EventPacketHandler(session).asNode(EventPacketHandler)) add(EventPacketHandler(session).asNode(EventPacketHandler))
add(ActionPacketHandler(session).asNode(ActionPacketHandler)) add(ActionPacketHandler(session).asNode(ActionPacketHandler))
bot.logger.logPurple("Successfully logged in")
} }
private lateinit var sessionKey: ByteArray private lateinit var sessionKey: ByteArray
...@@ -110,9 +112,10 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -110,9 +112,10 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
try { try {
channel.read(buffer)//JVM: withContext(IO) channel.read(buffer)//JVM: withContext(IO)
} catch (e: ReadPacketInternalException) { } catch (e: ReadPacketInternalException) {
//read failed, continue and reread
} catch (e: Exception) { continue
e.log() } catch (e: Throwable) {
e.log()//other unexpected exceptions caught.
continue continue
} }
...@@ -123,11 +126,11 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -123,11 +126,11 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
NetworkScope.launch { NetworkScope.launch {
try { 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 { ByteReadPacket(buffer, IoBuffer.Pool).use {
distributePacket(it.parseServerPacket(buffer.readRemaining)) distributePacket(it.parseServerPacket(buffer.readRemaining))
} }
} catch (e: Exception) { } catch (e: Throwable) {
e.log() e.log()
} }
} }
...@@ -168,6 +171,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -168,6 +171,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
try { try {
packet.decode() packet.decode()
} catch (e: Exception) { } catch (e: Exception) {
e.log()
bot.printPacketDebugging(packet) bot.printPacketDebugging(packet)
packet.close() packet.close()
throw e throw e
...@@ -238,9 +242,6 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -238,9 +242,6 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
bot.logger.logError("Caught SendPacketInternalException: ${e.cause?.message}") bot.logger.logError("Caught SendPacketInternalException: ${e.cause?.message}")
bot.reinitializeNetworkHandler(configuration) bot.reinitializeNetworkHandler(configuration)
return@withContext return@withContext
} catch (e: Throwable) {
e.log()
return@withContext
} finally { } finally {
buffer.release(IoBuffer.Pool) buffer.release(IoBuffer.Pool)
} }
...@@ -338,7 +339,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -338,7 +339,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
is ServerLoginResponseCaptchaInitPacket -> { is ServerLoginResponseCaptchaInitPacket -> {
//[token00BA]来源之一: 验证码 //[token00BA]来源之一: 验证码
this.token00BA = packet.token00BA this.token00BA = packet.token00BA
this.captchaCache = packet.verifyCodePart1 this.captchaCache = packet.captchaPart1
if (packet.unknownBoolean == true) { if (packet.unknownBoolean == true) {
this.captchaSectionId = 1 this.captchaSectionId = 1
...@@ -390,12 +391,14 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -390,12 +391,14 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
privateKey = privateKey, privateKey = privateKey,
token0825 = token0825, token0825 = token0825,
token00BA = packet.tokenUnknown ?: token00BA, token00BA = packet.tokenUnknown ?: token00BA,
randomDeviceName = socket.configuration.randomDeviceName randomDeviceName = socket.configuration.randomDeviceName,
tlv0006 = packet.tlv0006
)) ))
} }
is ServerSessionKeyResponsePacket -> { is ServerSessionKeyResponsePacket -> {
sessionKey = packet.sessionKey sessionKey = packet.sessionKey
bot.logger.logPurple("sessionKey = ${sessionKey.toUHexString()}")
heartbeatJob = NetworkScope.launch { heartbeatJob = NetworkScope.launch {
while (socket.isOpen) { while (socket.isOpen) {
...@@ -412,14 +415,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : ...@@ -412,14 +415,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
is ServerLoginSuccessPacket -> { is ServerLoginSuccessPacket -> {
BotLoginSucceedEvent(bot).broadcast() BotLoginSucceedEvent(bot).broadcast()
//登录成功后会收到大量上次的消息, 忽略掉 todo 优化
NetworkScope.launch {
delay(3000)
this@TIMBotNetworkHandler[EventPacketHandler].ignoreMessage = false
}
onLoggedIn(sessionKey) onLoggedIn(sessionKey)
this.close()//The LoginHandler is useless since then this.close()//The LoginHandler is useless since then
} }
......
...@@ -16,6 +16,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResp ...@@ -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.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRequestPacket import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerSKeyResponsePacket 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) { ...@@ -64,7 +65,11 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
sKeyRefresherJob = session.scope.launch { sKeyRefresherJob = session.scope.launch {
while (session.isOpen) { while (session.isOpen) {
delay(1800000) delay(1800000)
try {
session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.account, session.sessionKey)) session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.account, session.sessionKey))
} catch (e: Throwable) {
e.log()
}
} }
} }
} }
......
...@@ -41,7 +41,7 @@ interface DataPacketSocketAdapter : Closeable { ...@@ -41,7 +41,7 @@ interface DataPacketSocketAdapter : Closeable {
* *
* 可通过 hook 事件 [ServerPacketReceivedEvent] 来获取服务器返回. * 可通过 hook 事件 [ServerPacketReceivedEvent] 来获取服务器返回.
* *
* @see [BotSession.expectPacket] kotlin DSL * @see [BotSession.sendAndExpect] kotlin DSL
*/ */
suspend fun sendPacket(packet: ClientPacket) suspend fun sendPacket(packet: ClientPacket)
......
...@@ -26,9 +26,6 @@ import net.mamoe.mirai.utils.MiraiLogger ...@@ -26,9 +26,6 @@ import net.mamoe.mirai.utils.MiraiLogger
class EventPacketHandler(session: BotSession) : PacketHandler(session) { class EventPacketHandler(session: BotSession) : PacketHandler(session) {
companion object Key : PacketHandler.Key<EventPacketHandler> companion object Key : PacketHandler.Key<EventPacketHandler>
internal var ignoreMessage: Boolean = true
override suspend fun onPacketReceived(packet: ServerPacket): Unit = with(session) { override suspend fun onPacketReceived(packet: ServerPacket): Unit = with(session) {
when (packet) { when (packet) {
is ServerGroupUploadFileEventPacket -> { is ServerGroupUploadFileEventPacket -> {
...@@ -36,14 +33,12 @@ class EventPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -36,14 +33,12 @@ class EventPacketHandler(session: BotSession) : PacketHandler(session) {
} }
is ServerFriendMessageEventPacket -> { is ServerFriendMessageEventPacket -> {
if (ignoreMessage) return if (!packet.isPrevious) {
FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message).broadcast() FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message).broadcast()
} }
}
is ServerGroupMessageEventPacket -> { is ServerGroupMessageEventPacket -> {
if (ignoreMessage) return
if (packet.qq.toLong() == bot.account.account) return if (packet.qq.toLong() == bot.account.account) return
GroupMessageEvent(bot, bot.getGroupByNumber(packet.groupNumber), bot.getQQ(packet.qq), packet.message).broadcast() GroupMessageEvent(bot, bot.getGroupByNumber(packet.groupNumber), bot.getQQ(packet.qq), packet.message).broadcast()
......
...@@ -17,11 +17,11 @@ import kotlin.reflect.KClass ...@@ -17,11 +17,11 @@ import kotlin.reflect.KClass
* } * }
* ``` * ```
* *
* @see BotSession.expectPacket * @see BotSession.sendAndExpect
*/ */
class TemporaryPacketHandler<P : ServerPacket>( class TemporaryPacketHandler<P : ServerPacket>(
private val expectationClass: KClass<P>, private val expectationClass: KClass<P>,
private val deferred: CompletableJob, private val job: CompletableJob,
private val fromSession: BotSession private val fromSession: BotSession
) { ) {
private lateinit var toSend: ClientPacket private lateinit var toSend: ClientPacket
...@@ -54,7 +54,7 @@ class TemporaryPacketHandler<P : ServerPacket>( ...@@ -54,7 +54,7 @@ class TemporaryPacketHandler<P : ServerPacket>(
kotlin.runCatching { kotlin.runCatching {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
expect(packet as P) expect(packet as P)
}.onFailure { deferred.completeExceptionally(it) }.onSuccess { deferred.complete() } }.onFailure { job.completeExceptionally(it) }.onSuccess { job.complete() }
return true return true
} }
return false return false
......
...@@ -5,7 +5,9 @@ package net.mamoe.mirai.network.protocol.tim.packet ...@@ -5,7 +5,9 @@ package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.TIMProtocol 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) @PacketId(0x00_58u)
class ClientHeartbeatPacket( class ClientHeartbeatPacket(
...@@ -13,9 +15,9 @@ class ClientHeartbeatPacket( ...@@ -13,9 +15,9 @@ class ClientHeartbeatPacket(
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot) writeQQ(bot)
this.writeHex(TIMProtocol.fixVer) writeHex(TIMProtocol.fixVer)
this.encryptAndWrite(sessionKey) { encryptAndWrite(sessionKey) {
writeHex("00 01 00 01") writeHex("00 01 00 01")
} }
} }
......
...@@ -2,17 +2,16 @@ ...@@ -2,17 +2,16 @@
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.Closeable
import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.utils.toUHexString
/** /**
* 数据包. * 数据包.
*/ */
abstract class Packet : Closeable { abstract class Packet {
/** /**
* 2 Ubyte * 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. 唯一 * 包序列 id. 唯一
......
...@@ -26,13 +26,20 @@ fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = ...@@ -26,13 +26,20 @@ fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) =
writeFully(uniqueId) writeFully(uniqueId)
} }
fun <S : ServerEventPacket> S.applyId(id: UShort): S {
this.id = id
return this
}
/** /**
* Packet id: `00 CE` or `00 17` * Packet id: `00 CE` or `00 17`
* *
* @author Him188moe * @author Him188moe
*/ */
abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: EventPacketIdentity) : ServerPacket(input) { 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) { fun distribute(): ServerEventPacket = with(input) {
val eventIdentity = EventPacketIdentity( val eventIdentity = EventPacketIdentity(
from = readUInt(), from = readUInt(),
...@@ -65,7 +72,7 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event ...@@ -65,7 +72,7 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
println(readUByte().toUInt()) println(readUByte().toUInt())
//todo 错了. 可能是 00 79 才是. //todo 错了. 可能是 00 79 才是.
return@with ServerFriendTypingCanceledPacket(input, eventIdentity) ServerFriendTypingCanceledPacket(input, eventIdentity)
/* /*
if (readUByte().toUInt() == 0x37u) ServerFriendTypingStartedPacket(input, eventIdentity) if (readUByte().toUInt() == 0x37u) ServerFriendTypingStartedPacket(input, eventIdentity)
else /*0x22*/ ServerFriendTypingCanceledPacket(input, eventIdentity)*/ else /*0x22*/ ServerFriendTypingCanceledPacket(input, eventIdentity)*/
...@@ -81,8 +88,8 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event ...@@ -81,8 +88,8 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
}.applyId(id).applySequence(sequenceId) }.applyId(id).applySequence(sequenceId)
} }
class Encrypted(input: ByteReadPacket) : ServerPacket(input) { class Encrypted(input: ByteReadPacket, override var id: UShort, override var sequenceId: UShort) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey)).applyId(id).applySequence(sequenceId) fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey), id).applySequence(sequenceId)
} }
} }
...@@ -185,8 +192,8 @@ class ServerGroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventP ...@@ -185,8 +192,8 @@ class ServerGroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventP
senderName = map.getValue(18).read { senderName = map.getValue(18).read {
val tlv = readTLVMap(true) val tlv = readTLVMap(true)
tlv.printTLVMap("子map") 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 { when {
tlv.containsKey(0x01) -> String(tlv.getValue(0x01)) tlv.containsKey(0x01) -> String(tlv.getValue(0x01))
tlv.containsKey(0x02) -> String(tlv.getValue(0x02)) tlv.containsKey(0x02) -> String(tlv.getValue(0x02))
...@@ -202,7 +209,7 @@ class ServerGroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventP ...@@ -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 //刚刚的消息: 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() { 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() { fun main2() {
......
...@@ -16,7 +16,8 @@ import kotlin.properties.Delegates ...@@ -16,7 +16,8 @@ import kotlin.properties.Delegates
* @see parseServerPacket * @see parseServerPacket
*/ */
abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable { 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() override var sequenceId: UShort by Delegates.notNull()
open fun decode() { open fun decode() {
...@@ -28,11 +29,6 @@ abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable { ...@@ -28,11 +29,6 @@ abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable {
override fun toString(): String = this.packetToString() 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 { fun <S : ServerPacket> S.applySequence(sequenceId: UShort): S {
this.sequenceId = sequenceId this.sequenceId = sequenceId
return this return this
......
@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 package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.gotoWhere import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.utils.toReadPacket import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.BotSession
expect class PlatformImage 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( expect class ClientTryGetImageIDPacket(
botNumber: Long, botNumber: Long,
sessionKey: ByteArray, sessionKey: ByteArray,
...@@ -16,19 +20,18 @@ expect class ClientTryGetImageIDPacket( ...@@ -16,19 +20,18 @@ expect class ClientTryGetImageIDPacket(
image: PlatformImage image: PlatformImage
) : ClientPacket ) : ClientPacket
@PacketId(0x03_88u)
abstract class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPacket(input) { sealed class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
@PacketId(0x03_88u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) { class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket { fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket {
val data = this.decryptAsByteArray(sessionKey) val data = this.decryptAsByteArray(sessionKey)
println(data.size) println("ServerTryGetImageIDResponsePacket.size=" + data.size)
println(data.size)
if (data.size == 209) { if (data.size == 209) {
return ServerTryGetImageIDSuccessPacket(data.toReadPacket()).applySequence(sequenceId) 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 ...@@ -36,12 +39,14 @@ abstract class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : Server
/** /**
* 服务器未存有图片, 返回一个 key 用于客户端上传 * 服务器未存有图片, 返回一个 key 用于客户端上传
*/ */
@PacketId(0x03_88u)
class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) { class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
lateinit var uKey: ByteArray lateinit var uKey: ByteArray
override fun decode() { override fun decode() {
this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u)) this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u))//todo 优化
uKey = this.input.readBytes(128) uKey = this.input.readBytes(128)
DebugLogger.logPurple("获得 uKey(128)=${uKey.toUHexString()}")
} }
} }
...@@ -49,7 +54,41 @@ class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImag ...@@ -49,7 +54,41 @@ class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImag
* 服务器已经存有这个图片 * 服务器已经存有这个图片
*/ */
class ServerTryGetImageIDFailedPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) { 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()
} }
\ No newline at end of file
...@@ -7,19 +7,22 @@ import kotlinx.io.core.writeUByte ...@@ -7,19 +7,22 @@ import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.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) @PacketId(0x00_ECu)
class ClientChangeOnlineStatusPacket( class ClientChangeOnlineStatusPacket(
private val qq: Long, private val bot: Long,
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val loginStatus: OnlineStatus private val loginStatus: OnlineStatus
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq) this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2) this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) { this.encryptAndWrite(sessionKey) {
writeHex("01 00") writeHex("01 00")
......
...@@ -45,8 +45,10 @@ class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) { ...@@ -45,8 +45,10 @@ class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() = with(input) { override fun decode() = with(input) {
discardExact(4) discardExact(4)
sKey = this.readString(10)//todo test //debugDiscardExact(2)
MiraiLogger.logDebug("SKey=$sKey") sKey = this.readString(10)
DebugLogger.logPurple("SKey=$sKey")
DebugLogger.logPurple("Skey包后面${this.readRemainingBytes().toUHexString()}")
} }
@PacketId(0x00_1Du) @PacketId(0x00_1Du)
......
...@@ -16,8 +16,6 @@ class ServerLoginResponseFailedPacket(val loginResult: LoginResult, input: ByteR ...@@ -16,8 +16,6 @@ class ServerLoginResponseFailedPacket(val loginResult: LoginResult, input: ByteR
/** /**
* 服务器进行加密后返回 privateKey * 服务器进行加密后返回 privateKey
*
* @author NaturalHG
*/ */
@PacketId(0x08_36u) @PacketId(0x08_36u)
class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) { class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
...@@ -29,7 +27,7 @@ class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket) : ServerLoginR ...@@ -29,7 +27,7 @@ class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket) : ServerLoginR
@Tested @Tested
override fun decode() { override fun decode() {
this.input.discardExact(5)//01 00 1E 00 10 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 this.input.discardExact(4)//00 06 00 78
tlv0006 = this.input.readIoBuffer(0x78) tlv0006 = this.input.readIoBuffer(0x78)
...@@ -127,7 +125,7 @@ class ServerLoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginRespo ...@@ -127,7 +125,7 @@ class ServerLoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginRespo
@PacketId(0x08_36u) @PacketId(0x08_36u)
class ServerLoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) { class ServerLoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var verifyCodePart1: IoBuffer lateinit var captchaPart1: IoBuffer
lateinit var token00BA: ByteArray lateinit var token00BA: ByteArray
var unknownBoolean: Boolean? = null var unknownBoolean: Boolean? = null
...@@ -136,8 +134,8 @@ class ServerLoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginR ...@@ -136,8 +134,8 @@ class ServerLoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginR
override fun decode() { override fun decode() {
this.input.discardExact(78) this.input.discardExact(78)
//println(this.input.readRemainingBytes().toUHexString()) //println(this.input.readRemainingBytes().toUHexString())
val verifyCodeLength = this.input.readShort()//2bytes val captchaLength = this.input.readShort()//2bytes
this.verifyCodePart1 = this.input.readIoBuffer(verifyCodeLength) this.captchaPart1 = this.input.readIoBuffer(captchaLength)
this.input.discardExact(1) this.input.discardExact(1)
......
...@@ -9,14 +9,14 @@ import net.mamoe.mirai.utils.* ...@@ -9,14 +9,14 @@ import net.mamoe.mirai.utils.*
@PacketId(0x08_28u) @PacketId(0x08_28u)
class ClientSessionRequestPacket( class ClientSessionRequestPacket(
private val qq: Long, private val bot: Long,
private val serverIp: String, private val serverIp: String,
private val token38: IoBuffer, private val token38: IoBuffer,
private val token88: IoBuffer, private val token88: IoBuffer,
private val encryptionKey: IoBuffer private val encryptionKey: IoBuffer
) : ClientPacket() { ) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) { 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("02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A")
this.writeHex("00 38") this.writeHex("00 38")
this.writeFully(token38) this.writeFully(token38)
...@@ -31,7 +31,7 @@ class ClientSessionRequestPacket( ...@@ -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("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.constantData1)
writeHex(TIMProtocol.constantData2) writeHex(TIMProtocol.constantData2)
writeQQ(qq) writeQQ(bot)
writeHex("00 00 00 00 00 1F 00 22 00 01") 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 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
......
...@@ -69,12 +69,12 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket { ...@@ -69,12 +69,12 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
} }
0x08_28u -> ServerSessionKeyResponsePacket.Encrypted(this) 0x08_28u -> ServerSessionKeyResponsePacket.Encrypted(this)
0x00_EC_u -> ServerSKeyResponsePacket(this) 0x00_EC_u -> ServerLoginSuccessPacket(this)
0x00_1D_u -> ServerSKeyResponsePacket.Encrypted(this) 0x00_1D_u -> ServerSKeyResponsePacket.Encrypted(this)
0x00_5C_u -> ServerAccountInfoResponsePacket.Encrypted(this) 0x00_5C_u -> ServerAccountInfoResponsePacket.Encrypted(this)
0x00_58_u -> ServerHeartbeatResponsePacket(this) 0x00_58_u -> ServerHeartbeatResponsePacket(this)
0x00_BA_u -> ServerCaptchaPacket.Encrypted(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_81_u -> ServerFieldOnlineStatusChangedPacket.Encrypted(this)
0x00_CD_u -> ServerSendFriendMessageResponsePacket(this) 0x00_CD_u -> ServerSendFriendMessageResponsePacket(this)
0x00_02_u -> ServerSendGroupMessageResponsePacket(this) 0x00_02_u -> ServerSendGroupMessageResponsePacket(this)
...@@ -82,7 +82,7 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket { ...@@ -82,7 +82,7 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
0x03_88_u -> ServerTryGetImageIDResponsePacket.Encrypted(this) 0x03_88_u -> ServerTryGetImageIDResponsePacket.Encrypted(this)
else -> UnknownServerPacket.Encrypted(this, id, sequenceId) else -> UnknownServerPacket.Encrypted(this, id, sequenceId)
}.applyId(id).applySequence(sequenceId) }.applySequence(sequenceId)
} }
fun Input.readIP(): String = buildString(4 + 3) { fun Input.readIP(): String = buildString(4 + 3) {
...@@ -145,6 +145,7 @@ fun Input.readLVNumber(): Number { ...@@ -145,6 +145,7 @@ fun Input.readLVNumber(): Number {
//@JvmSynthetic //@JvmSynthetic
@Deprecated("Low efficiency", ReplaceWith("")) @Deprecated("Low efficiency", ReplaceWith(""))
fun <I : Input> I.gotoWhere(matcher: UByteArray): I { fun <I : Input> I.gotoWhere(matcher: UByteArray): I {
@Suppress("DEPRECATION")
return this.gotoWhere(matcher.toByteArray()) return this.gotoWhere(matcher.toByteArray())
} }
......
...@@ -15,19 +15,19 @@ internal fun ByteArray.debugPrint(name: String): ByteArray { ...@@ -15,19 +15,19 @@ internal fun ByteArray.debugPrint(name: String): ByteArray {
return this return this
} }
@Deprecated("Low Efficiency", ReplaceWith("")) @Deprecated("Low efficiency, only for debug purpose", ReplaceWith(""))
internal fun IoBuffer.debugPrint(name: String): IoBuffer { internal fun IoBuffer.debugPrint(name: String): IoBuffer {
val readBytes = this.readBytes() val readBytes = this.readBytes()
DebugLogger.logPurple(name + "=" + readBytes.toUHexString()) DebugLogger.logPurple(name + "=" + readBytes.toUHexString())
return readBytes.toIoBuffer() 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 = "") { internal fun Input.debugDiscardExact(n: Number, name: String = "") {
DebugLogger.logPurple("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString()) DebugLogger.logPurple("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
} }
@Deprecated("Low Efficiency", ReplaceWith("")) @Deprecated("Low efficiency, only for debug purpose", ReplaceWith(""))
internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket { internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
val bytes = this.readBytes() val bytes = this.readBytes()
DebugLogger.logPurple("ByteReadPacket $name=" + bytes.toUHexString()) DebugLogger.logPurple("ByteReadPacket $name=" + bytes.toUHexString())
......
package net.mamoe.mirai.utils
import kotlin.jvm.JvmOverloads
expect class PlatformImage
@JvmOverloads
expect fun PlatformImage.toByteArray(formatName: String = "JPG"): ByteArray
\ No newline at end of file
...@@ -32,3 +32,14 @@ expect fun solveIpAddress(hostname: String): String ...@@ -32,3 +32,14 @@ expect fun solveIpAddress(hostname: String): String
* Localhost 解析 * Localhost 解析
*/ */
expect fun localIpAddress(): String expect fun localIpAddress(): String
/**
* 上传群图片
*/
expect suspend fun httpPostGroupImage(
uKeyHex: String,
fileSize: Int,
botNumber: Long,
groupCode: Long,
imageData: ByteArray
): Boolean
\ No newline at end of file
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
}
}
...@@ -35,7 +35,6 @@ actual sealed class Contact actual constructor(bot: Bot, number: Long) : Platfor ...@@ -35,7 +35,6 @@ actual sealed class Contact actual constructor(bot: Bot, number: Long) : Platfor
*/ */
fun blockingSendMessage(message: Message) = runBlocking { sendMessage(message) } fun blockingSendMessage(message: Message) = runBlocking { sendMessage(message) }
/** /**
* 阻塞发送一个消息. 仅应在 Java 使用 * 阻塞发送一个消息. 仅应在 Java 使用
*/ */
...@@ -44,23 +43,17 @@ actual sealed class Contact actual constructor(bot: Bot, number: Long) : Platfor ...@@ -44,23 +43,17 @@ actual sealed class Contact actual constructor(bot: Bot, number: Long) : Platfor
/** /**
* 异步发送一个消息. 仅应在 Java 使用 * 异步发送一个消息. 仅应在 Java 使用
*/ */
fun asyncSendMessage(chain: MessageChain) { fun asyncSendMessage(chain: MessageChain) = bot.network.NetworkScope.launch { sendMessage(chain) }
bot.network.NetworkScope.launch { sendMessage(chain) }
}
/** /**
* 异步发送一个消息. 仅应在 Java 使用 * 异步发送一个消息. 仅应在 Java 使用
*/ */
fun asyncSendMessage(message: Message) { fun asyncSendMessage(message: Message) = bot.network.NetworkScope.launch { sendMessage(message) }
bot.network.NetworkScope.launch { sendMessage(message) }
}
/** /**
* 异步发送一个消息. 仅应在 Java 使用 * 异步发送一个消息. 仅应在 Java 使用
*/ */
fun asyncSendMessage(plain: String) { fun asyncSendMessage(plain: String) = bot.network.NetworkScope.launch { sendMessage(plain) }
bot.network.NetworkScope.launch { sendMessage(plain) }
}
} }
/** /**
......
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")
}
}
}
\ No newline at end of file
...@@ -4,7 +4,6 @@ package net.mamoe.mirai.network.protocol.tim.packet ...@@ -4,7 +4,6 @@ package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import kotlinx.io.core.internal.DangerousInternalIoApi
import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.utils.toUHexString
import java.lang.reflect.Field import java.lang.reflect.Field
...@@ -37,7 +36,8 @@ private object IgnoreIdList : List<String> by listOf( ...@@ -37,7 +36,8 @@ private object IgnoreIdList : List<String> by listOf(
"EMPTY_ID_HEX", "EMPTY_ID_HEX",
"input", "input",
"output", "output",
"UninitializedByteReadPacket" "UninitializedByteReadPacket",
"sessionKey"
) )
internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.idHexString})") + this::class.java.allDeclaredFields internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.idHexString})") + this::class.java.allDeclaredFields
......
...@@ -5,9 +5,6 @@ package net.mamoe.mirai.network.protocol.tim.packet ...@@ -5,9 +5,6 @@ package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import java.awt.image.BufferedImage
actual typealias PlatformImage = BufferedImage
actual typealias ClientTryGetImageIDPacket = ClientTryGetImageIDPacketJvm actual typealias ClientTryGetImageIDPacket = ClientTryGetImageIDPacketJvm
......
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() }
\ No newline at end of file
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() }
\ No newline at end of file
...@@ -14,6 +14,7 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv ...@@ -14,6 +14,7 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv
private val serverAddress: InetSocketAddress = InetSocketAddress(serverHost, serverPort.toInt()) private val serverAddress: InetSocketAddress = InetSocketAddress(serverHost, serverPort.toInt())
private val channel: DatagramChannel = DatagramChannel.open().connect(serverAddress) private val channel: DatagramChannel = DatagramChannel.open().connect(serverAddress)
@Throws(ReadPacketInternalException::class)
actual suspend fun read(buffer: IoBuffer) = withContext(Dispatchers.IO) { actual suspend fun read(buffer: IoBuffer) = withContext(Dispatchers.IO) {
try { try {
(channel as ReadableByteChannel).read(buffer) (channel as ReadableByteChannel).read(buffer)
...@@ -22,6 +23,7 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv ...@@ -22,6 +23,7 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv
} }
} }
@Throws(SendPacketInternalException::class)
actual suspend fun send(buffer: IoBuffer) = withContext(Dispatchers.IO) { actual suspend fun send(buffer: IoBuffer) = withContext(Dispatchers.IO) {
buffer.readDirect { buffer.readDirect {
try { try {
......
package net.mamoe.mirai.utils 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.net.InetAddress
import java.security.MessageDigest import java.security.MessageDigest
import java.util.zip.CRC32 import java.util.zip.CRC32
...@@ -16,3 +20,37 @@ actual fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5 ...@@ -16,3 +20,37 @@ actual fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5
actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress
actual fun localIpAddress(): String = InetAddress.getLocalHost().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
\ No newline at end of file
...@@ -76,7 +76,6 @@ object Main { ...@@ -76,7 +76,6 @@ object Main {
} }
} }
/** /**
* 可从 TIM 内存中读取 * 可从 TIM 内存中读取
* *
...@@ -89,7 +88,7 @@ object Main { ...@@ -89,7 +88,7 @@ object Main {
* 6. 运行到 `mov eax,dword ptr ss:[ebp+10]` * 6. 运行到 `mov eax,dword ptr ss:[ebp+10]`
* 7. 查看内存, 从 `eax` 开始的 16 bytes 便是 `sessionKey` * 7. 查看内存, 从 `eax` 开始的 16 bytes 便是 `sessionKey`
*/ */
val sessionKey: ByteArray = "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) { fun dataReceived(data: ByteArray) {
println("--------------") println("--------------")
......
package demo1 package demo1
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.event.events.FriendMessageEvent import net.mamoe.mirai.event.events.FriendMessageEvent
...@@ -17,14 +16,11 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult ...@@ -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.BotAccount
import net.mamoe.mirai.utils.Console import net.mamoe.mirai.utils.Console
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import kotlin.coroutines.coroutineContext
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
suspend fun main() { suspend fun main() {
val bot = Bot(BotAccount(//填写你的账号 val bot = Bot(BotAccount(//填写你的账号
account = 2903772581, account = 1994701021,
password = "zxc123456" password = "asdhim188666"
), Console()) ), Console())
bot.login { bot.login {
...@@ -79,7 +75,7 @@ suspend fun main() { ...@@ -79,7 +75,7 @@ suspend fun main() {
subscribeAll<FriendMessageEvent> { subscribeAll<FriendMessageEvent> {
always { always {
//获取第一个纯文本消息 //获取第一个纯文本消息
val firstText = it.message.first<PlainText>() val firstText = it.message.firstOrNull<PlainText>()
} }
} }
......
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