diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt new file mode 100644 index 000000000..52adfbd8f --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt @@ -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 + } + } +} diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt new file mode 100644 index 000000000..a5bab9af8 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt @@ -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 +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/RobotNetworkHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/RobotNetworkHandler.kt deleted file mode 100644 index f6c0fe8de..000000000 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/RobotNetworkHandler.kt +++ /dev/null @@ -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) - } - } - } -} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionHandler.kt new file mode 100644 index 000000000..2a45138ff --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionHandler.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/BotSession.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/BotSession.kt deleted file mode 100644 index ae872e2bb..000000000 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/BotSession.kt +++ /dev/null @@ -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 -) { -} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt new file mode 100644 index 000000000..9c5aac23d --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt @@ -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() +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DebugHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DebugHandler.kt new file mode 100644 index 000000000..5be246db3 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DebugHandler.kt @@ -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)) + } + } +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessageHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessageHandler.kt new file mode 100644 index 000000000..e542cb7a3 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessageHandler.kt @@ -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)) + } +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt new file mode 100644 index 000000000..f2dd3e4a2 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt @@ -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() + } +} diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/SKey.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/SKey.kt index 30f8c16b3..1e4d515ab 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/SKey.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/SKey.kt @@ -6,6 +6,8 @@ import java.io.DataInputStream /** + * SKey 用于 http api + * * @author Him188moe */ @ExperimentalUnsignedTypes diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/LoginState.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/LoginState.kt index 5f3f8b66f..f5ccba7d6 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/LoginState.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/LoginState.kt @@ -34,11 +34,6 @@ enum class LoginState { */ TAKEN_BACK, - /** - * 需要验证码登录 - */ - VERIFICATION_CODE, - /** * 未知. 更换服务器或等几分钟再登录可能解决. */ diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiLogger.kt b/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiLogger.kt index 349817f16..04b245e70 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiLogger.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/MiraiLogger.kt @@ -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