mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-30 01:40:29 +08:00
Updated network
This commit is contained in:
parent
d19216a65b
commit
4a319bf589
mirai-core/src/main/java/net/mamoe/mirai
@ -0,0 +1,428 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("BotNetworkHandler")
|
||||
|
||||
package net.mamoe.mirai.network
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.MiraiServer
|
||||
import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent
|
||||
import net.mamoe.mirai.event.events.network.BeforePacketSendEvent
|
||||
import net.mamoe.mirai.event.events.network.PacketSentEvent
|
||||
import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent
|
||||
import net.mamoe.mirai.event.hookWhile
|
||||
import net.mamoe.mirai.network.BotNetworkHandler.Login
|
||||
import net.mamoe.mirai.network.BotNetworkHandler.SocketHandler
|
||||
import net.mamoe.mirai.network.handler.*
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.network.packet.login.*
|
||||
import net.mamoe.mirai.task.MiraiThreadPool
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.io.Closeable
|
||||
import java.net.DatagramPacket
|
||||
import java.net.DatagramSocket
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.imageio.ImageIO
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
/**
|
||||
* Mirai 的网络处理器, 它处理所有数据包([Packet])的发送和接收.
|
||||
* [BotNetworkHandler] 是全程异步和线程安全的.
|
||||
*
|
||||
* [BotNetworkHandler] 由 2 个模块构成:
|
||||
* - [SocketHandler]: 处理数据包底层的发送([ByteArray])
|
||||
* - [PacketHandler]: 制作 [Packet] 并传递给 [SocketHandler] 继续处理; 分析来自服务器的数据包并处理
|
||||
*
|
||||
* 其中, [PacketHandler] 由 4 个子模块构成:
|
||||
* - [DebugHandler] 输出 [Packet.toString]
|
||||
* - [Login] 处理 touch/login/verification code 相关
|
||||
* - [MessageHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
|
||||
* - [ActionHandler] 处理动作相关(踢人/加入群/好友列表等)
|
||||
*
|
||||
* A BotNetworkHandler is used to connect with Tencent servers.
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
|
||||
class BotNetworkHandler(private val bot: Bot) : Closeable {
|
||||
private val socket: SocketHandler = SocketHandler()
|
||||
|
||||
fun getSocket(): DataPacketSocket {
|
||||
return socket
|
||||
}
|
||||
|
||||
|
||||
lateinit var debugHandler: DebugHandler
|
||||
lateinit var login: Login
|
||||
lateinit var messageHandler: MessageHandler
|
||||
lateinit var actionHandler: ActionHandler
|
||||
|
||||
val packetHandlers: PacketHandlerList = PacketHandlerList()
|
||||
|
||||
|
||||
//private | internal
|
||||
|
||||
/**
|
||||
* 尝试登录
|
||||
*
|
||||
* @param touchingTimeoutMillis 连接每个服务器的 timeout
|
||||
*/
|
||||
@JvmOverloads
|
||||
internal fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture<LoginState> {
|
||||
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
|
||||
val future = CompletableFuture<LoginState>()
|
||||
|
||||
fun login() {
|
||||
this.socket.close()
|
||||
val ip = ipQueue.poll()
|
||||
if (ip == null) {
|
||||
future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN
|
||||
return
|
||||
}
|
||||
|
||||
this@BotNetworkHandler.socket.touch(ip, touchingTimeoutMillis).get().let { state ->
|
||||
if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) {
|
||||
login()//超时或未知, 重试连接下一个服务器
|
||||
} else {
|
||||
future.complete(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
login()
|
||||
return future
|
||||
}
|
||||
|
||||
|
||||
private lateinit var sessionKey: ByteArray
|
||||
|
||||
override fun close() {
|
||||
this.packetHandlers.forEach {
|
||||
it.instance.close()
|
||||
}
|
||||
this.socket.close()
|
||||
}
|
||||
|
||||
|
||||
private inner class SocketHandler : Closeable, DataPacketSocket {
|
||||
override fun distributePacket(packet: ServerPacket) {
|
||||
try {
|
||||
packet.decode()
|
||||
} catch (e: java.lang.Exception) {
|
||||
e.printStackTrace()
|
||||
bot.debugPacket(packet)
|
||||
return
|
||||
}
|
||||
|
||||
if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) {
|
||||
debugHandler.onPacketReceived(packet)
|
||||
return
|
||||
}
|
||||
|
||||
login.onPacketReceived(packet)
|
||||
packetHandlers.forEach {
|
||||
it.instance.onPacketReceived(packet)
|
||||
}
|
||||
}
|
||||
|
||||
private var socket: DatagramSocket? = null
|
||||
|
||||
internal var serverIP: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
restartSocket()
|
||||
}
|
||||
|
||||
internal var loginFuture: CompletableFuture<LoginState>? = null
|
||||
|
||||
@Synchronized
|
||||
private fun restartSocket() {
|
||||
socket?.close()
|
||||
socket = DatagramSocket(0)
|
||||
socket!!.connect(InetSocketAddress(serverIP, 8000))
|
||||
Thread {
|
||||
while (socket!!.isConnected) {
|
||||
val packet = DatagramPacket(ByteArray(2048), 2048)
|
||||
kotlin.runCatching { socket?.receive(packet) }
|
||||
.onSuccess {
|
||||
MiraiThreadPool.getInstance().submit {
|
||||
try {
|
||||
distributePacket(ServerPacket.ofByteArray(packet.data.removeZeroTail()))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
if (it.message == "Socket closed" || it.message == "socket closed") {
|
||||
return@Thread
|
||||
}
|
||||
it.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
/**
|
||||
* Start network and touch the server
|
||||
*/
|
||||
fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture<LoginState> {
|
||||
bot.info("Connecting server: $serverAddress")
|
||||
if (this@BotNetworkHandler::login.isInitialized) {
|
||||
login.close()
|
||||
}
|
||||
login = Login()
|
||||
this.loginFuture = CompletableFuture()
|
||||
|
||||
serverIP = serverAddress
|
||||
waitForPacket(ServerPacket::class, timeoutMillis) {
|
||||
loginFuture!!.complete(LoginState.TIMEOUT)
|
||||
}
|
||||
sendPacket(ClientTouchPacket(bot.account.qqNumber, serverIP))
|
||||
|
||||
return this.loginFuture!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Not async
|
||||
*/
|
||||
@Synchronized
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun sendPacket(packet: ClientPacket) {
|
||||
checkNotNull(socket) { "network closed" }
|
||||
if (socket!!.isClosed) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
packet.encodePacket()
|
||||
|
||||
if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
val data = packet.toByteArray()
|
||||
socket!!.send(DatagramPacket(data, data.size))
|
||||
bot.cyanL("Packet sent: $packet")
|
||||
|
||||
PacketSentEvent(bot, packet).broadcast()
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <P : ServerPacket> waitForPacket(packetClass: KClass<P>, timeoutMillis: Long, timeout: () -> Unit) {
|
||||
var got = false
|
||||
ServerPacketReceivedEvent::class.hookWhile {
|
||||
if (packetClass.isInstance(it.packet) && it.bot === bot) {
|
||||
got = true
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MiraiThreadPool.getInstance().submit {
|
||||
val startingTime = System.currentTimeMillis()
|
||||
while (!got) {
|
||||
if (System.currentTimeMillis() - startingTime > timeoutMillis) {
|
||||
timeout.invoke()
|
||||
return@submit
|
||||
}
|
||||
Thread.sleep(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this.socket?.close()
|
||||
if (this.loginFuture != null) {
|
||||
if (!this.loginFuture!!.isDone) {
|
||||
this.loginFuture!!.cancel(true)
|
||||
}
|
||||
this.loginFuture = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun isClosed(): Boolean {
|
||||
return this.socket?.isClosed ?: true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录过程
|
||||
*/
|
||||
inner class Login : Closeable {
|
||||
private lateinit var token00BA: ByteArray
|
||||
private lateinit var token0825: ByteArray
|
||||
private var loginTime: Int = 0
|
||||
private lateinit var loginIP: String
|
||||
private var tgtgtKey: ByteArray = getRandomByteArray(16)
|
||||
|
||||
private var tlv0105: ByteArray = lazyEncode {
|
||||
it.writeHex("01 05 00 30")
|
||||
it.writeHex("00 01 01 02 00 14 01 01 00 10")
|
||||
it.writeRandom(16)
|
||||
it.writeHex("00 14 01 02 00 10")
|
||||
it.writeRandom(16)
|
||||
}
|
||||
|
||||
/**
|
||||
* 0828_decr_key
|
||||
*/
|
||||
private lateinit var sessionResponseDecryptionKey: ByteArray
|
||||
|
||||
private var captchaSectionId: Int = 1
|
||||
private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来
|
||||
|
||||
private var heartbeatFuture: ScheduledFuture<*>? = null
|
||||
|
||||
|
||||
fun onPacketReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerTouchResponsePacket -> {
|
||||
if (packet.serverIP != null) {//redirection
|
||||
socket.serverIP = packet.serverIP!!
|
||||
//connect(packet.serverIP!!)
|
||||
socket.sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber))
|
||||
} else {//password submission
|
||||
this.loginIP = packet.loginIP
|
||||
this.loginTime = packet.loginTime
|
||||
this.token0825 = packet.token0825
|
||||
socket.sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerLoginResponseFailedPacket -> {
|
||||
socket.loginFuture?.complete(packet.loginState)
|
||||
return
|
||||
}
|
||||
|
||||
is ServerVerificationCodeCorrectPacket -> {
|
||||
this.tgtgtKey = getRandomByteArray(16)
|
||||
this.token00BA = packet.token00BA
|
||||
socket.sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA))
|
||||
}
|
||||
|
||||
is ServerLoginResponseVerificationCodeInitPacket -> {
|
||||
//[token00BA]来源之一: 验证码
|
||||
this.token00BA = packet.token00BA
|
||||
this.captchaCache = packet.verifyCodePart1
|
||||
|
||||
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
|
||||
this.captchaSectionId = 1
|
||||
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerVerificationCodeTransmissionPacket -> {
|
||||
if (packet is ServerVerificationCodeWrongPacket) {
|
||||
bot.error("验证码错误, 请重新输入")
|
||||
captchaSectionId = 1
|
||||
this.captchaCache = byteArrayOf()
|
||||
}
|
||||
|
||||
this.captchaCache = this.captchaCache!! + packet.captchaSectionN
|
||||
this.token00BA = packet.token00BA
|
||||
|
||||
if (packet.transmissionCompleted) {
|
||||
bot.notice(CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream())))
|
||||
bot.notice("需要验证码登录, 验证码为 4 字母")
|
||||
try {
|
||||
(MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.captchaCache!!)
|
||||
bot.notice("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png")
|
||||
} catch (e: Exception) {
|
||||
bot.notice("无法写出验证码文件, 请尝试查看以上字符图片")
|
||||
}
|
||||
bot.notice("若要更换验证码, 请直接回车")
|
||||
val code = Scanner(System.`in`).nextLine()
|
||||
if (code.isEmpty() || code.length != 4) {
|
||||
this.captchaCache = byteArrayOf()
|
||||
this.captchaSectionId = 1
|
||||
socket.sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825))
|
||||
} else {
|
||||
socket.sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken))
|
||||
}
|
||||
} else {
|
||||
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerLoginResponseSuccessPacket -> {
|
||||
this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey
|
||||
socket.sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socket.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105))
|
||||
}
|
||||
|
||||
//是ClientPasswordSubmissionPacket之后服务器回复的
|
||||
is ServerLoginResponseKeyExchangePacket -> {
|
||||
//if (packet.tokenUnknown != null) {
|
||||
//this.token00BA = packet.token00BA!!
|
||||
//println("token00BA changed!!! to " + token00BA.toUByteArray())
|
||||
//}
|
||||
if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) {
|
||||
this.tgtgtKey = packet.tgtgtKey
|
||||
socket.sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
|
||||
?: this.token00BA, packet.tlv0006))
|
||||
} else {
|
||||
socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
|
||||
?: token00BA, packet.tlv0006))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerSessionKeyResponsePacket -> {
|
||||
sessionKey = packet.sessionKey
|
||||
heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
|
||||
socket.sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey))
|
||||
}, 90000, 90000, TimeUnit.MILLISECONDS)
|
||||
|
||||
BotLoginSucceedEvent(bot).broadcast()
|
||||
|
||||
//登录成功后会收到大量上次的消息, 忽略掉
|
||||
MiraiThreadPool.getInstance().schedule({
|
||||
messageHandler.ignoreMessage = false
|
||||
}, 3, TimeUnit.SECONDS)
|
||||
|
||||
this.tlv0105 = packet.tlv0105
|
||||
|
||||
socket.loginFuture!!.complete(LoginState.SUCCESS)
|
||||
}
|
||||
|
||||
|
||||
is ServerEventPacket.Raw -> socket.distributePacket(packet.distribute())
|
||||
|
||||
is ServerVerificationCodePacket.Encrypted -> socket.distributePacket(packet.decrypt())
|
||||
is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> socket.distributePacket(packet.decrypt())
|
||||
is ServerLoginResponseKeyExchangePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey))
|
||||
is ServerLoginResponseSuccessPacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey))
|
||||
is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
|
||||
is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt())
|
||||
is ServerAccountInfoResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(sessionKey))
|
||||
is ServerEventPacket.Raw.Encrypted -> socket.distributePacket(packet.decrypt(sessionKey))
|
||||
|
||||
|
||||
is ServerLoginSuccessPacket,
|
||||
is ServerHeartbeatResponsePacket,
|
||||
is UnknownServerPacket -> {
|
||||
//ignored
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this.captchaCache = null
|
||||
|
||||
this.heartbeatFuture?.cancel(true)
|
||||
|
||||
this.heartbeatFuture = null
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package net.mamoe.mirai.network
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.handler.DataPacketSocket
|
||||
import net.mamoe.mirai.utils.getGTK
|
||||
|
||||
/**
|
||||
* 一次会话. 当登录完成后, 客户端会拿到 sessionKey. 此时建立 session, 开始处理消息等事务
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
class LoginSession(
|
||||
val bot: Bot,
|
||||
val sessionKey: ByteArray,
|
||||
val socket: DataPacketSocket
|
||||
) {
|
||||
lateinit var cookies: String
|
||||
var sKey: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
gtk = getGTK(value)
|
||||
}
|
||||
var gtk: Int = 0
|
||||
}
|
@ -1,711 +0,0 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("BotNetworkHandler")
|
||||
|
||||
package net.mamoe.mirai.network
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.MiraiServer
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent
|
||||
import net.mamoe.mirai.event.events.network.BeforePacketSendEvent
|
||||
import net.mamoe.mirai.event.events.network.PacketSentEvent
|
||||
import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent
|
||||
import net.mamoe.mirai.event.events.qq.FriendMessageEvent
|
||||
import net.mamoe.mirai.event.hookWhile
|
||||
import net.mamoe.mirai.message.Message
|
||||
import net.mamoe.mirai.message.defaults.MessageChain
|
||||
import net.mamoe.mirai.network.BotNetworkHandler.*
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.network.packet.action.*
|
||||
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageFailedPacket
|
||||
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageResponsePacket
|
||||
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageSuccessPacket
|
||||
import net.mamoe.mirai.network.packet.login.*
|
||||
import net.mamoe.mirai.task.MiraiThreadPool
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.Closeable
|
||||
import java.net.DatagramPacket
|
||||
import java.net.DatagramSocket
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Supplier
|
||||
import javax.imageio.ImageIO
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
/**
|
||||
* Mirai 的网络处理器, 它处理所有数据包([Packet])的发送和接收.
|
||||
* [BotNetworkHandler] 是全程异步和线程安全的.
|
||||
*
|
||||
* [BotNetworkHandler] 由 2 个模块构成:
|
||||
* - [SocketHandler]: 处理数据包底层的发送([ByteArray])
|
||||
* - [PacketHandler]: 制作 [Packet] 并传递给 [SocketHandler] 继续处理; 分析来自服务器的数据包并处理
|
||||
*
|
||||
* 其中, [PacketHandler] 由 4 个子模块构成:
|
||||
* - [DebugHandler] 输出 [Packet.toString]
|
||||
* - [LoginHandler] 处理 touch/login/verification code 相关
|
||||
* - [MessageHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
|
||||
* - [ActionHandler] 处理动作相关(踢人/加入群/好友列表等)
|
||||
*
|
||||
* A BotNetworkHandler is used to connect with Tencent servers.
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
|
||||
class BotNetworkHandler(private val bot: Bot) : Closeable {
|
||||
private val socketHandler: SocketHandler = SocketHandler()
|
||||
|
||||
val debugHandler = DebugHandler()
|
||||
val loginHandler = LoginHandler()
|
||||
val messageHandler = MessageHandler()
|
||||
val actionHandler = ActionHandler()
|
||||
|
||||
private val packetHandlers: Map<KClass<out PacketHandler>, PacketHandler> = linkedMapOf(
|
||||
DebugHandler::class to debugHandler,
|
||||
LoginHandler::class to loginHandler,
|
||||
MessageHandler::class to messageHandler,
|
||||
ActionHandler::class to actionHandler
|
||||
)
|
||||
|
||||
/**
|
||||
* Not async
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
fun sendPacket(packet: ClientPacket) {
|
||||
socketHandler.sendPacket(packet)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this.packetHandlers.values.forEach {
|
||||
it.close()
|
||||
}
|
||||
this.socketHandler.close()
|
||||
}
|
||||
|
||||
|
||||
//private | internal
|
||||
|
||||
/**
|
||||
* 仅当 [LoginState] 非 [LoginState.UNKNOWN] 且非 [LoginState.TIMEOUT] 才会调用 [loginHook].
|
||||
* 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCESS] 调用 [loginHandler]
|
||||
*
|
||||
* @param touchingTimeoutMillis 连接每个服务器的 timeout
|
||||
*/
|
||||
@JvmOverloads
|
||||
internal fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture<LoginState> {
|
||||
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
|
||||
val future = CompletableFuture<LoginState>()
|
||||
|
||||
fun login() {
|
||||
this.socketHandler.close()
|
||||
val ip = ipQueue.poll()
|
||||
if (ip == null) {
|
||||
future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN
|
||||
return
|
||||
}
|
||||
|
||||
this@BotNetworkHandler.socketHandler.touch(ip, touchingTimeoutMillis).get().let { state ->
|
||||
if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) {
|
||||
login()
|
||||
} else {
|
||||
future.complete(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
login()
|
||||
return future
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配收到的数据包
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
internal fun distributePacket(packet: ServerPacket) {
|
||||
try {
|
||||
packet.decode()
|
||||
} catch (e: java.lang.Exception) {
|
||||
e.printStackTrace()
|
||||
bot.debug("Packet=$packet")
|
||||
bot.debug("Packet size=" + packet.input.goto(0).readAllBytes().size)
|
||||
bot.debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString())
|
||||
return
|
||||
}
|
||||
|
||||
if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) {
|
||||
debugHandler.onPacketReceived(packet)
|
||||
return
|
||||
}
|
||||
this.packetHandlers.values.forEach {
|
||||
it.onPacketReceived(packet)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private inner class SocketHandler : Closeable {
|
||||
private var socket: DatagramSocket? = null
|
||||
|
||||
internal var serverIP: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
restartSocket()
|
||||
}
|
||||
|
||||
internal var loginFuture: CompletableFuture<LoginState>? = null
|
||||
|
||||
@Synchronized
|
||||
private fun restartSocket() {
|
||||
socket?.close()
|
||||
socket = DatagramSocket(0)
|
||||
socket!!.connect(InetSocketAddress(serverIP, 8000))
|
||||
Thread {
|
||||
while (socket!!.isConnected) {
|
||||
val packet = DatagramPacket(ByteArray(2048), 2048)
|
||||
kotlin.runCatching { socket?.receive(packet) }
|
||||
.onSuccess {
|
||||
MiraiThreadPool.getInstance().submit {
|
||||
try {
|
||||
distributePacket(ServerPacket.ofByteArray(packet.data.removeZeroTail()))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
if (it.message == "Socket closed" || it.message == "socket closed") {
|
||||
return@Thread
|
||||
}
|
||||
it.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
/**
|
||||
* Start network and touch the server
|
||||
*/
|
||||
internal fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture<LoginState> {
|
||||
bot.info("Connecting server: $serverAddress")
|
||||
this.loginFuture = CompletableFuture()
|
||||
|
||||
socketHandler.serverIP = serverAddress
|
||||
waitForPacket(ServerPacket::class, timeoutMillis) {
|
||||
loginFuture!!.complete(LoginState.TIMEOUT)
|
||||
}
|
||||
sendPacket(ClientTouchPacket(bot.account.qqNumber, socketHandler.serverIP))
|
||||
|
||||
return this.loginFuture!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Not async
|
||||
*/
|
||||
@Synchronized
|
||||
@ExperimentalUnsignedTypes
|
||||
internal fun sendPacket(packet: ClientPacket) {
|
||||
checkNotNull(socket) { "network closed" }
|
||||
if (socket!!.isClosed) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
packet.encodePacket()
|
||||
|
||||
if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
val data = packet.toByteArray()
|
||||
socket!!.send(DatagramPacket(data, data.size))
|
||||
bot cyanL "Packet sent: $packet"
|
||||
|
||||
PacketSentEvent(bot, packet).broadcast()
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <P : ServerPacket> waitForPacket(packetClass: KClass<P>, timeoutMillis: Long, timeout: () -> Unit) {
|
||||
var got = false
|
||||
ServerPacketReceivedEvent::class.hookWhile {
|
||||
if (packetClass.isInstance(it.packet) && it.bot == bot) {
|
||||
got = true
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MiraiThreadPool.getInstance().submit {
|
||||
val startingTime = System.currentTimeMillis()
|
||||
while (!got) {
|
||||
if (System.currentTimeMillis() - startingTime > timeoutMillis) {
|
||||
timeout.invoke()
|
||||
return@submit
|
||||
}
|
||||
Thread.sleep(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this.socket?.close()
|
||||
if (this.loginFuture != null) {
|
||||
if (!this.loginFuture!!.isDone) {
|
||||
this.loginFuture!!.cancel(true)
|
||||
}
|
||||
this.loginFuture = null
|
||||
}
|
||||
}
|
||||
|
||||
fun isClosed(): Boolean {
|
||||
return this.socket?.isClosed ?: true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private lateinit var sessionKey: ByteArray
|
||||
|
||||
abstract inner class PacketHandler : Closeable {
|
||||
abstract fun onPacketReceived(packet: ServerPacket)
|
||||
|
||||
override fun close() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kind of [PacketHandler] that prints all packets received in the format of hex byte array.
|
||||
*/
|
||||
inner class DebugHandler : PacketHandler() {
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) {
|
||||
bot notice "Packet received: $packet"
|
||||
}
|
||||
if (packet is ServerEventPacket) {
|
||||
sendPacket(ClientMessageResponsePacket(bot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录过程
|
||||
*/
|
||||
inner class LoginHandler : PacketHandler() {
|
||||
private lateinit var token00BA: ByteArray
|
||||
private lateinit var token0825: ByteArray
|
||||
private var loginTime: Int = 0
|
||||
private lateinit var loginIP: String
|
||||
private var tgtgtKey: ByteArray = getRandomByteArray(16)
|
||||
|
||||
private var tlv0105: ByteArray = lazyEncode {
|
||||
it.writeHex("01 05 00 30")
|
||||
it.writeHex("00 01 01 02 00 14 01 01 00 10")
|
||||
it.writeRandom(16)
|
||||
it.writeHex("00 14 01 02 00 10")
|
||||
it.writeRandom(16)
|
||||
}
|
||||
|
||||
/**
|
||||
* 0828_decr_key
|
||||
*/
|
||||
private lateinit var sessionResponseDecryptionKey: ByteArray
|
||||
|
||||
private var captchaSectionId: Int = 1
|
||||
private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来
|
||||
|
||||
|
||||
private var heartbeatFuture: ScheduledFuture<*>? = null
|
||||
private var sKeyRefresherFuture: ScheduledFuture<*>? = null
|
||||
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerTouchResponsePacket -> {
|
||||
if (packet.serverIP != null) {//redirection
|
||||
socketHandler.serverIP = packet.serverIP!!
|
||||
//connect(packet.serverIP!!)
|
||||
sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber))
|
||||
} else {//password submission
|
||||
this.loginIP = packet.loginIP
|
||||
this.loginTime = packet.loginTime
|
||||
this.token0825 = packet.token0825
|
||||
sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerLoginResponseFailedPacket -> {
|
||||
socketHandler.loginFuture?.complete(packet.loginState)
|
||||
return
|
||||
}
|
||||
|
||||
is ServerVerificationCodeCorrectPacket -> {
|
||||
this.tgtgtKey = getRandomByteArray(16)
|
||||
this.token00BA = packet.token00BA
|
||||
sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA))
|
||||
}
|
||||
|
||||
is ServerLoginResponseVerificationCodeInitPacket -> {
|
||||
//[token00BA]来源之一: 验证码
|
||||
this.token00BA = packet.token00BA
|
||||
this.captchaCache = packet.verifyCodePart1
|
||||
|
||||
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
|
||||
this.captchaSectionId = 1
|
||||
sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerVerificationCodeTransmissionPacket -> {
|
||||
if (packet is ServerVerificationCodeWrongPacket) {
|
||||
bot error "验证码错误, 请重新输入"
|
||||
captchaSectionId = 1
|
||||
this.captchaCache = byteArrayOf()
|
||||
}
|
||||
|
||||
this.captchaCache = this.captchaCache!! + packet.captchaSectionN
|
||||
this.token00BA = packet.token00BA
|
||||
|
||||
if (packet.transmissionCompleted) {
|
||||
bot notice (CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream())))
|
||||
bot notice ("需要验证码登录, 验证码为 4 字母")
|
||||
try {
|
||||
(MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.captchaCache!!)
|
||||
bot notice ("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png")
|
||||
} catch (e: Exception) {
|
||||
bot notice "无法写出验证码文件, 请尝试查看以上字符图片"
|
||||
}
|
||||
bot notice ("若要更换验证码, 请直接回车")
|
||||
val code = Scanner(System.`in`).nextLine()
|
||||
if (code.isEmpty() || code.length != 4) {
|
||||
this.captchaCache = byteArrayOf()
|
||||
this.captchaSectionId = 1
|
||||
sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825))
|
||||
} else {
|
||||
sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken))
|
||||
}
|
||||
} else {
|
||||
sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerLoginResponseSuccessPacket -> {
|
||||
this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey
|
||||
sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socketHandler.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105))
|
||||
}
|
||||
|
||||
//是ClientPasswordSubmissionPacket之后服务器回复的
|
||||
is ServerLoginResponseKeyExchangePacket -> {
|
||||
//if (packet.tokenUnknown != null) {
|
||||
//this.token00BA = packet.token00BA!!
|
||||
//println("token00BA changed!!! to " + token00BA.toUByteArray())
|
||||
//}
|
||||
if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) {
|
||||
this.tgtgtKey = packet.tgtgtKey
|
||||
sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
|
||||
?: this.token00BA, packet.tlv0006))
|
||||
} else {
|
||||
sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
|
||||
?: token00BA, packet.tlv0006))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerSessionKeyResponsePacket -> {
|
||||
sessionKey = packet.sessionKey
|
||||
heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
|
||||
sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey))
|
||||
}, 90000, 90000, TimeUnit.MILLISECONDS)
|
||||
|
||||
BotLoginSucceedEvent(bot).broadcast()
|
||||
|
||||
//登录成功后会收到大量上次的消息, 忽略掉
|
||||
MiraiThreadPool.getInstance().schedule({
|
||||
messageHandler.ignoreMessage = false
|
||||
}, 3, TimeUnit.SECONDS)
|
||||
|
||||
this.tlv0105 = packet.tlv0105
|
||||
sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, ClientLoginStatus.ONLINE))
|
||||
}
|
||||
|
||||
is ServerLoginSuccessPacket -> {
|
||||
socketHandler.loginFuture!!.complete(LoginState.SUCCESS)
|
||||
sendPacket(ClientSKeyRequestPacket(bot.account.qqNumber, sessionKey))
|
||||
}
|
||||
|
||||
is ServerSKeyResponsePacket -> {
|
||||
actionHandler.sKey = packet.sKey
|
||||
actionHandler.cookies = "uin=o" + bot.account.qqNumber + ";skey=" + actionHandler.sKey + ";"
|
||||
|
||||
sKeyRefresherFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
|
||||
sendPacket(ClientSKeyRefreshmentRequestPacket(bot.account.qqNumber, sessionKey))
|
||||
}, 1800000, 1800000, TimeUnit.MILLISECONDS)
|
||||
|
||||
actionHandler.gtk = getGTK(actionHandler.sKey)
|
||||
sendPacket(ClientAccountInfoRequestPacket(bot.account.qqNumber, sessionKey))
|
||||
}
|
||||
|
||||
is ServerEventPacket.Raw -> distributePacket(packet.distribute())
|
||||
|
||||
is ServerVerificationCodePacket.Encrypted -> distributePacket(packet.decrypt())
|
||||
is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> distributePacket(packet.decrypt())
|
||||
is ServerLoginResponseKeyExchangePacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey))
|
||||
is ServerLoginResponseSuccessPacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey))
|
||||
is ServerSessionKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
|
||||
is ServerTouchResponsePacket.Encrypted -> distributePacket(packet.decrypt())
|
||||
is ServerSKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
|
||||
is ServerAccountInfoResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
|
||||
is ServerEventPacket.Raw.Encrypted -> distributePacket(packet.decrypt(sessionKey))
|
||||
|
||||
|
||||
is ServerAccountInfoResponsePacket,
|
||||
is ServerHeartbeatResponsePacket,
|
||||
is UnknownServerPacket -> {
|
||||
//ignored
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this.captchaCache = null
|
||||
|
||||
this.heartbeatFuture?.cancel(true)
|
||||
this.sKeyRefresherFuture?.cancel(true)
|
||||
|
||||
this.heartbeatFuture = null
|
||||
this.sKeyRefresherFuture = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息事件, 承担消息发送任务.
|
||||
*/
|
||||
inner class MessageHandler : PacketHandler() {
|
||||
internal var ignoreMessage: Boolean = true
|
||||
|
||||
init {
|
||||
//todo for test
|
||||
FriendMessageEvent::class.hookWhile {
|
||||
if (socketHandler.isClosed()) {
|
||||
return@hookWhile false
|
||||
}
|
||||
if (it.message() valueEquals "你好") {
|
||||
it.qq.sendMessage("你好!")
|
||||
} else if (it.message().toString().startsWith("复读")) {
|
||||
it.qq.sendMessage(it.message())
|
||||
}
|
||||
|
||||
return@hookWhile true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerGroupUploadFileEventPacket -> {
|
||||
//todo
|
||||
}
|
||||
|
||||
is ServerFriendMessageEventPacket -> {
|
||||
if (ignoreMessage) {
|
||||
return
|
||||
}
|
||||
|
||||
FriendMessageEvent(bot, bot.contacts.getQQ(packet.qq), packet.message).broadcast()
|
||||
}
|
||||
|
||||
is ServerGroupMessageEventPacket -> {
|
||||
//todo message chain
|
||||
//GroupMessageEvent(this.bot, bot.contacts.getGroupByNumber(packet.groupNumber), bot.contacts.getQQ(packet.qq), packet.message)
|
||||
}
|
||||
|
||||
is UnknownServerEventPacket -> {
|
||||
//todo
|
||||
}
|
||||
|
||||
is ServerSendFriendMessageResponsePacket,
|
||||
is ServerSendGroupMessageResponsePacket -> {
|
||||
//ignored
|
||||
}
|
||||
else -> {
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendFriendMessage(qq: QQ, message: MessageChain) {
|
||||
sendPacket(ClientSendFriendMessagePacket(bot.account.qqNumber, qq.number, sessionKey, message))
|
||||
}
|
||||
|
||||
fun sendGroupMessage(group: Group, message: Message): Unit {
|
||||
TODO()
|
||||
//sendPacket(ClientSendGroupMessagePacket(group.groupId, bot.account.qqNumber, sessionKey, message))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 动作: 获取好友列表, 点赞, 踢人等.
|
||||
* 处理动作事件, 承担动作任务.
|
||||
*/
|
||||
inner class ActionHandler : PacketHandler() {
|
||||
internal lateinit var cookies: String
|
||||
internal var sKey: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
gtk = getGTK(value)
|
||||
}
|
||||
internal var gtk: Int = 0
|
||||
|
||||
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>())
|
||||
private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf<UploadImageSession>())
|
||||
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerCanAddFriendResponsePacket -> {
|
||||
this.uploadImageSessions.forEach {
|
||||
it.onPacketReceived(packet)
|
||||
}
|
||||
}
|
||||
is ServerTryUploadGroupImageSuccessPacket -> {
|
||||
ImageNetworkUtils.postImage(packet.uKey.toUHexString(), )
|
||||
}
|
||||
|
||||
is ServerTryUploadGroupImageFailedPacket -> {
|
||||
|
||||
}
|
||||
|
||||
is ServerTryUploadGroupImageResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addFriend(qqNumber: Long, message: Supplier<String>) {
|
||||
addFriend(qqNumber, lazy { message.get() })
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
|
||||
val future = CompletableFuture<AddFriendResult>()
|
||||
val session = AddFriendSession(qqNumber, future, message)
|
||||
uploadImageSessions.add(session)
|
||||
session.sendAddRequest();
|
||||
return future
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
|
||||
}
|
||||
|
||||
private inner class UploadImageSession(
|
||||
private val group: Long,
|
||||
private val future: CompletableFuture<AddFriendResult>,
|
||||
private val image: BufferedImage
|
||||
) : Closeable {
|
||||
lateinit var id: ByteArray
|
||||
|
||||
fun onPacketReceived(packet: ServerPacket) {
|
||||
if (!::id.isInitialized) {
|
||||
return
|
||||
}
|
||||
|
||||
when (packet) {
|
||||
is ServerCanAddFriendResponsePacket -> {
|
||||
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
|
||||
return
|
||||
}
|
||||
|
||||
when (packet.state) {
|
||||
ServerCanAddFriendResponsePacket.State.FAILED -> {
|
||||
future.complete(AddFriendResult.FAILED)
|
||||
close()
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> {
|
||||
future.complete(AddFriendResult.ALREADY_ADDED)
|
||||
close()
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
|
||||
sendPacket(ClientAddFriendPacket(bot.account.qqNumber, qq, sessionKey))
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendRequest() {
|
||||
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
uploadImageSessions.remove(this)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class AddFriendSession(
|
||||
private val qq: Long,
|
||||
private val future: CompletableFuture<AddFriendResult>,
|
||||
private val message: Lazy<String>
|
||||
) : Closeable {
|
||||
lateinit var id: ByteArray
|
||||
|
||||
fun onPacketReceived(packet: ServerPacket) {
|
||||
if (!::id.isInitialized) {
|
||||
return
|
||||
}
|
||||
|
||||
when (packet) {
|
||||
is ServerCanAddFriendResponsePacket -> {
|
||||
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
|
||||
return
|
||||
}
|
||||
|
||||
when (packet.state) {
|
||||
ServerCanAddFriendResponsePacket.State.FAILED -> {
|
||||
future.complete(AddFriendResult.FAILED)
|
||||
close()
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> {
|
||||
future.complete(AddFriendResult.ALREADY_ADDED)
|
||||
close()
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
|
||||
sendPacket(ClientAddFriendPacket(bot.account.qqNumber, qq, sessionKey))
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun sendAddRequest() {
|
||||
sendPacket(ClientCanAddFriendPacket(bot.account.qqNumber, qq, sessionKey).also { this.id = it.packetIdLast })
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
uploadImageSessions.remove(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
package net.mamoe.mirai.network.handler
|
||||
|
||||
import net.mamoe.mirai.network.LoginSession
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.network.packet.action.AddFriendResult
|
||||
import net.mamoe.mirai.network.packet.action.ClientAddFriendPacket
|
||||
import net.mamoe.mirai.network.packet.action.ClientCanAddFriendPacket
|
||||
import net.mamoe.mirai.network.packet.action.ServerCanAddFriendResponsePacket
|
||||
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageFailedPacket
|
||||
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageResponsePacket
|
||||
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageSuccessPacket
|
||||
import net.mamoe.mirai.network.packet.login.ClientChangeOnlineStatusPacket
|
||||
import net.mamoe.mirai.task.MiraiThreadPool
|
||||
import net.mamoe.mirai.utils.ClientLoginStatus
|
||||
import net.mamoe.mirai.utils.ImageNetworkUtils
|
||||
import net.mamoe.mirai.utils.getGTK
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.Closeable
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* 动作: 获取好友列表, 点赞, 踢人等.
|
||||
* 处理动作事件, 承担动作任务.
|
||||
*/
|
||||
class ActionHandler(session: LoginSession) : PacketHandler(session) {
|
||||
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>())
|
||||
private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf<UploadImageSession>())
|
||||
|
||||
private var sKeyRefresherFuture: ScheduledFuture<*>? = null
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerCanAddFriendResponsePacket -> {
|
||||
this.uploadImageSessions.forEach {
|
||||
it.onPacketReceived(packet)
|
||||
}
|
||||
}
|
||||
is ServerTryUploadGroupImageSuccessPacket -> {
|
||||
ImageNetworkUtils.postImage(packet.uKey.toUHexString(), )
|
||||
}
|
||||
|
||||
is ServerTryUploadGroupImageFailedPacket -> {
|
||||
|
||||
}
|
||||
|
||||
is ServerTryUploadGroupImageResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
|
||||
|
||||
is ServerAccountInfoResponsePacket -> {
|
||||
|
||||
}
|
||||
|
||||
is ServerSKeyResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
|
||||
is ServerSKeyResponsePacket -> {
|
||||
session.sKey = packet.sKey
|
||||
session.cookies = "uin=o" + session.bot.account.qqNumber + ";skey=" + session.sKey + ";"
|
||||
|
||||
sKeyRefresherFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
|
||||
session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.qqNumber, session.sessionKey))
|
||||
}, 1800000, 1800000, TimeUnit.MILLISECONDS)
|
||||
|
||||
session.gtk = getGTK(session.sKey)
|
||||
}
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun addFriend(qqNumber: Long, message: Supplier<String>) {
|
||||
addFriend(qqNumber, lazy { message.get() })
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@JvmSynthetic
|
||||
fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
|
||||
val future = CompletableFuture<AddFriendResult>()
|
||||
val session = AddFriendSession(qqNumber, future, message)
|
||||
uploadImageSessions.add(session)
|
||||
session.sendAddRequest();
|
||||
return future
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun requestSKey() {
|
||||
session.socket.sendPacket(ClientSKeyRequestPacket(session.bot.account.qqNumber, session.sessionKey))
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun changeOnlineStatus(status: ClientLoginStatus) {
|
||||
session.socket.sendPacket(ClientChangeOnlineStatusPacket(session.bot.account.qqNumber, session.sessionKey, status))
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun requestAccountInfo() {
|
||||
session.socket.sendPacket(ClientAccountInfoRequestPacket(session.bot.account.qqNumber, session.sessionKey))
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this.sKeyRefresherFuture?.cancel(true)
|
||||
this.sKeyRefresherFuture = null
|
||||
}
|
||||
|
||||
private inner class UploadImageSession(
|
||||
private val group: Long,
|
||||
private val future: CompletableFuture<AddFriendResult>,
|
||||
private val image: BufferedImage
|
||||
) : Closeable {
|
||||
lateinit var id: ByteArray
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun onPacketReceived(packet: ServerPacket) {
|
||||
if (!::id.isInitialized) {
|
||||
return
|
||||
}
|
||||
|
||||
when (packet) {
|
||||
is ServerCanAddFriendResponsePacket -> {
|
||||
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
|
||||
return
|
||||
}
|
||||
|
||||
when (packet.state) {
|
||||
ServerCanAddFriendResponsePacket.State.FAILED -> {
|
||||
future.complete(AddFriendResult.FAILED)
|
||||
close()
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> {
|
||||
future.complete(AddFriendResult.ALREADY_ADDED)
|
||||
close()
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
|
||||
session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey))
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun sendRequest() {
|
||||
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
uploadImageSessions.remove(this)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class AddFriendSession(
|
||||
private val qq: Long,
|
||||
private val future: CompletableFuture<AddFriendResult>,
|
||||
private val message: Lazy<String>
|
||||
) : Closeable {
|
||||
lateinit var id: ByteArray
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun onPacketReceived(packet: ServerPacket) {
|
||||
if (!::id.isInitialized) {
|
||||
return
|
||||
}
|
||||
|
||||
when (packet) {
|
||||
is ServerCanAddFriendResponsePacket -> {
|
||||
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
|
||||
return
|
||||
}
|
||||
|
||||
when (packet.state) {
|
||||
ServerCanAddFriendResponsePacket.State.FAILED -> {
|
||||
future.complete(AddFriendResult.FAILED)
|
||||
close()
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> {
|
||||
future.complete(AddFriendResult.ALREADY_ADDED)
|
||||
close()
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
|
||||
session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey))
|
||||
}
|
||||
|
||||
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun sendAddRequest() {
|
||||
session.socket.sendPacket(ClientCanAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey).also { this.id = it.packetIdLast })
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
uploadImageSessions.remove(this)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package net.mamoe.mirai.network.handler
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
data class BotSession(
|
||||
val bot: Bot,
|
||||
val sessionKey: ByteArray,
|
||||
val networkHandler: BotNetworkHandler
|
||||
) {
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.mamoe.mirai.network.handler
|
||||
|
||||
import net.mamoe.mirai.network.packet.ClientPacket
|
||||
import net.mamoe.mirai.network.packet.ServerPacket
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
interface DataPacketSocket : Closeable {
|
||||
|
||||
fun distributePacket(packet: ServerPacket)
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun sendPacket(packet: ClientPacket)
|
||||
|
||||
fun isClosed(): Boolean
|
||||
|
||||
override fun close()
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package net.mamoe.mirai.network.handler
|
||||
|
||||
import net.mamoe.mirai.network.LoginSession
|
||||
import net.mamoe.mirai.network.packet.ClientMessageResponsePacket
|
||||
import net.mamoe.mirai.network.packet.ServerEventPacket
|
||||
import net.mamoe.mirai.network.packet.ServerPacket
|
||||
import net.mamoe.mirai.utils.notice
|
||||
|
||||
/**
|
||||
* Kind of [PacketHandler] that prints all packets received in the format of hex byte array.
|
||||
*/
|
||||
sealed class DebugHandler(session: LoginSession) : PacketHandler(session) {
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) {
|
||||
session.bot notice "Packet received: $packet"
|
||||
}
|
||||
|
||||
if (packet is ServerEventPacket) {
|
||||
session.socket.sendPacket(ClientMessageResponsePacket(session.bot.account.qqNumber, packet.packetId, session.sessionKey, packet.eventIdentity))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package net.mamoe.mirai.network.handler
|
||||
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.event.events.qq.FriendMessageEvent
|
||||
import net.mamoe.mirai.event.hookWhile
|
||||
import net.mamoe.mirai.message.Message
|
||||
import net.mamoe.mirai.message.defaults.MessageChain
|
||||
import net.mamoe.mirai.network.LoginSession
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.network.packet.action.ClientSendFriendMessagePacket
|
||||
import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket
|
||||
import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket
|
||||
|
||||
/**
|
||||
* 处理消息事件, 承担消息发送任务.
|
||||
*/
|
||||
class MessageHandler(session: LoginSession) : PacketHandler(session) {
|
||||
internal var ignoreMessage: Boolean = true
|
||||
|
||||
init {
|
||||
//todo for test
|
||||
FriendMessageEvent::class.hookWhile {
|
||||
if (session.socket.isClosed()) {
|
||||
return@hookWhile false
|
||||
}
|
||||
if (it.message() valueEquals "你好") {
|
||||
it.qq.sendMessage("你好!")
|
||||
} else if (it.message().toString().startsWith("复读")) {
|
||||
it.qq.sendMessage(it.message())
|
||||
}
|
||||
|
||||
return@hookWhile true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerGroupUploadFileEventPacket -> {
|
||||
//todo
|
||||
}
|
||||
|
||||
is ServerFriendMessageEventPacket -> {
|
||||
if (ignoreMessage) {
|
||||
return
|
||||
}
|
||||
|
||||
FriendMessageEvent(session.bot, session.bot.contacts.getQQ(packet.qq), packet.message).broadcast()
|
||||
}
|
||||
|
||||
is ServerGroupMessageEventPacket -> {
|
||||
//todo message chain
|
||||
//GroupMessageEvent(this.bot, bot.contacts.getGroupByNumber(packet.groupNumber), bot.contacts.getQQ(packet.qq), packet.message)
|
||||
}
|
||||
|
||||
is UnknownServerEventPacket -> {
|
||||
//todo
|
||||
}
|
||||
|
||||
is ServerSendFriendMessageResponsePacket,
|
||||
is ServerSendGroupMessageResponsePacket -> {
|
||||
//ignored
|
||||
}
|
||||
else -> {
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun sendFriendMessage(qq: QQ, message: MessageChain) {
|
||||
session.socket.sendPacket(ClientSendFriendMessagePacket(session.bot.account.qqNumber, qq.number, session.sessionKey, message))
|
||||
}
|
||||
|
||||
fun sendGroupMessage(group: Group, message: Message): Unit {
|
||||
TODO()
|
||||
//sendPacket(ClientSendGroupMessagePacket(group.groupId, bot.account.qqNumber, sessionKey, message))
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package net.mamoe.mirai.network.handler
|
||||
|
||||
import net.mamoe.mirai.network.LoginSession
|
||||
import net.mamoe.mirai.network.packet.ServerPacket
|
||||
import java.io.Closeable
|
||||
import java.util.*
|
||||
import kotlin.NoSuchElementException
|
||||
|
||||
/**
|
||||
* 数据包(接受/发送)处理器
|
||||
*/
|
||||
abstract class PacketHandler(
|
||||
val session: LoginSession
|
||||
) : Closeable {
|
||||
abstract fun onPacketReceived(packet: ServerPacket)
|
||||
|
||||
override fun close() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class PacketHandlerNode<T : PacketHandler>(
|
||||
val clazz: Class<T>,
|
||||
val instance: T
|
||||
)
|
||||
|
||||
infix fun PacketHandler.to(handler: PacketHandler): PacketHandlerNode<PacketHandler> {
|
||||
return PacketHandlerNode(handler.javaClass, handler)
|
||||
}
|
||||
|
||||
class PacketHandlerList : LinkedList<PacketHandlerNode<*>>() {
|
||||
|
||||
fun <T : PacketHandler> get(clazz: Class<T>): T {
|
||||
this.forEach {
|
||||
if (it.clazz == clazz) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return@get it.instance as T
|
||||
}
|
||||
}
|
||||
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
}
|
@ -6,6 +6,8 @@ import java.io.DataInputStream
|
||||
|
||||
|
||||
/**
|
||||
* SKey 用于 http api
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
|
@ -34,11 +34,6 @@ enum class LoginState {
|
||||
*/
|
||||
TAKEN_BACK,
|
||||
|
||||
/**
|
||||
* 需要验证码登录
|
||||
*/
|
||||
VERIFICATION_CODE,
|
||||
|
||||
/**
|
||||
* 未知. 更换服务器或等几分钟再登录可能解决.
|
||||
*/
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.packet.ServerPacket
|
||||
import net.mamoe.mirai.network.packet.goto
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@ -11,20 +13,20 @@ import java.util.*
|
||||
* @author NaturalHG
|
||||
*/
|
||||
object MiraiLogger {
|
||||
infix fun log(o: Any?) = info(o)
|
||||
infix fun println(o: Any?) = info(o)
|
||||
infix fun info(o: Any?) = this.print(o.toString(), LoggerTextFormat.RESET)
|
||||
fun log(o: Any?) = info(o)
|
||||
fun println(o: Any?) = info(o)
|
||||
fun info(o: Any?) = this.print(o.toString(), LoggerTextFormat.RESET)
|
||||
|
||||
|
||||
infix fun error(o: Any?) = this.print(o.toString(), LoggerTextFormat.RED)
|
||||
fun error(o: Any?) = this.print(o.toString(), LoggerTextFormat.RED)
|
||||
|
||||
infix fun notice(o: Any?) = this.print(o.toString(), LoggerTextFormat.LIGHT_BLUE)
|
||||
fun notice(o: Any?) = this.print(o.toString(), LoggerTextFormat.LIGHT_BLUE)
|
||||
|
||||
infix fun success(o: Any?) = this.print(o.toString(), LoggerTextFormat.GREEN)
|
||||
fun success(o: Any?) = this.print(o.toString(), LoggerTextFormat.GREEN)
|
||||
|
||||
infix fun debug(o: Any?) = this.print(o.toString(), LoggerTextFormat.YELLOW)
|
||||
fun debug(o: Any?) = this.print(o.toString(), LoggerTextFormat.YELLOW)
|
||||
|
||||
infix fun catching(e: Throwable) {
|
||||
fun catching(e: Throwable) {
|
||||
e.printStackTrace()
|
||||
/*
|
||||
this.print(e.message)
|
||||
@ -39,21 +41,26 @@ object MiraiLogger {
|
||||
}
|
||||
}
|
||||
|
||||
infix fun Bot.log(o: Any?) = info(o)
|
||||
infix fun Bot.println(o: Any?) = info(o)
|
||||
infix fun Bot.info(o: Any?) = print(this, o.toString(), LoggerTextFormat.RESET)
|
||||
fun Bot.log(o: Any?) = info(o)
|
||||
fun Bot.println(o: Any?) = info(o)
|
||||
fun Bot.info(o: Any?) = print(this, o.toString(), LoggerTextFormat.RESET)
|
||||
|
||||
infix fun Bot.error(o: Any?) = print(this, o.toString(), LoggerTextFormat.RED)
|
||||
fun Bot.error(o: Any?) = print(this, o.toString(), LoggerTextFormat.RED)
|
||||
|
||||
infix fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE)
|
||||
fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE)
|
||||
|
||||
infix fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE)
|
||||
fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE)
|
||||
|
||||
infix fun Bot.cyanL(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN)
|
||||
fun Bot.cyanL(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN)
|
||||
fun Bot.success(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN)
|
||||
|
||||
infix fun Bot.success(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN)
|
||||
fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW)
|
||||
|
||||
infix fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW)
|
||||
fun Bot.debugPacket(packet: ServerPacket) {
|
||||
debug("Packet=$packet")
|
||||
debug("Packet size=" + packet.input.goto(0).readAllBytes().size)
|
||||
debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString())
|
||||
}
|
||||
|
||||
|
||||
@Synchronized
|
||||
|
Loading…
Reference in New Issue
Block a user