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 { internal fun IoBuffer.parseMessageImage0x06(): Image {
discardExact(1) discardExact(1)
this.debugPrint("好友的图片") with(this.debugPrint("好友的图片")) {
//MiraiLogger.logDebug(this.toUHexString())
val filenameLength = readShort() //MiraiLogger.logDebug(this.toUHexString())
val suffix = readString(filenameLength).substringAfter(".") val filenameLength = readShort()
discardExact(this@parseMessageImage0x06.readRemaining - 37 - 1 - filenameLength - 2) val suffix = readString(filenameLength).substringAfter(".")
val imageId = readString(36) discardExact(this.readRemaining - 37 - 1 - filenameLength - 2 - 8 - 4)
MiraiLogger.logDebug(imageId) val imageId = readString(36)
discardExact(1)//0x41 MiraiLogger.logDebug("imageId=$imageId")//todo ID似乎错了??
return Image("{$imageId}.$suffix") discardExact(1)//0x41
return Image("{$imageId}.$suffix")
}
} }
internal fun IoBuffer.parseMessageImage0x03(): Image { internal fun IoBuffer.parseMessageImage0x03(): Image {

View File

@ -1,6 +1,8 @@
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
* *
* 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 {
* - 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 {
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<*>)

View File

@ -1,3 +1,5 @@
@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(
* *
* 实现方法: * 实现方法:
* ```kotlin * ```kotlin
* session.expectPacket<ServerPacketXXX> { * session.sendAndExpect<ServerPacketXXX> {
* toSend { ClientPacketXXX(...) } * toSend { ClientPacketXXX(...) }
* onExpect {//it: ServerPacketXXX * onExpect {//it: ServerPacketXXX
* *
@ -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(
/** /**
* 发送一个数据包, 并期待接受一个特定的 [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)
} }

View File

@ -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) :
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) :
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) :
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) :
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) :
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) :
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) :
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) :
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
} }

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.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) {
sKeyRefresherJob = session.scope.launch { sKeyRefresherJob = session.scope.launch {
while (session.isOpen) { while (session.isOpen) {
delay(1800000) 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] 来获取服务器返回. * 可通过 hook 事件 [ServerPacketReceivedEvent] 来获取服务器返回.
* *
* @see [BotSession.expectPacket] kotlin DSL * @see [BotSession.sendAndExpect] kotlin DSL
*/ */
suspend fun sendPacket(packet: ClientPacket) suspend fun sendPacket(packet: ClientPacket)

View File

@ -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) {
} }
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()

View File

@ -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>(
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

View File

@ -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(
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")
} }
} }

View File

@ -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. 唯一

View File

@ -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
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
}.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
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
//刚刚的消息: 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() {

View File

@ -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 {
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

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 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(
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
/** /**
* 服务器未存有图片, 返回一个 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
* 服务器已经存有这个图片 * 服务器已经存有这个图片
*/ */
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()
} }

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.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")

View File

@ -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)

View File

@ -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
@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
@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
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)

View File

@ -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(
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

View File

@ -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 {
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 {
//@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())
} }

View File

@ -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())

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 解析 * 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) } fun blockingSendMessage(message: Message) = runBlocking { sendMessage(message) }
/** /**
* 阻塞发送一个消息. 仅应在 Java 使用 * 阻塞发送一个消息. 仅应在 Java 使用
*/ */
@ -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) }
}
} }
/** /**

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.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(
"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

View File

@ -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

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 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
} }
} }
@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 {

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 内存中读取 * 可从 TIM 内存中读取
* *
@ -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("--------------")

View File

@ -1,7 +1,6 @@
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
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() {
subscribeAll<FriendMessageEvent> { subscribeAll<FriendMessageEvent> {
always { always {
//获取第一个纯文本消息 //获取第一个纯文本消息
val firstText = it.message.first<PlainText>() val firstText = it.message.firstOrNull<PlainText>()
} }
} }