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