mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-23 10:20:14 +08:00
Updated network
This commit is contained in:
parent
4a319bf589
commit
ef98f22d54
15
mirai-api/src/main/java/net/mamoe/mirai/Bot.java
Normal file
15
mirai-api/src/main/java/net/mamoe/mirai/Bot.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package net.mamoe.mirai;
|
||||||
|
|
||||||
|
import net.mamoe.mirai.utils.BotAccount;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Him188moe
|
||||||
|
*/
|
||||||
|
public interface Bot extends Closeable {
|
||||||
|
|
||||||
|
BotAccount getAccount();
|
||||||
|
|
||||||
|
// TODO: 2019/9/13 add more
|
||||||
|
}
|
@ -17,6 +17,13 @@
|
|||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.mamoe</groupId>
|
||||||
|
<artifactId>mirai-api</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.protobuf</groupId>
|
<groupId>com.google.protobuf</groupId>
|
||||||
<artifactId>protobuf-java</artifactId>
|
<artifactId>protobuf-java</artifactId>
|
||||||
|
@ -3,7 +3,7 @@ package net.mamoe.mirai;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.mamoe.mirai.contact.Group;
|
import net.mamoe.mirai.contact.Group;
|
||||||
import net.mamoe.mirai.contact.QQ;
|
import net.mamoe.mirai.contact.QQ;
|
||||||
import net.mamoe.mirai.network.BotNetworkHandler;
|
import net.mamoe.mirai.network.BotNetworkHandlerImpl;
|
||||||
import net.mamoe.mirai.utils.BotAccount;
|
import net.mamoe.mirai.utils.BotAccount;
|
||||||
import net.mamoe.mirai.utils.ContactList;
|
import net.mamoe.mirai.utils.ContactList;
|
||||||
import net.mamoe.mirai.utils.config.MiraiConfigSection;
|
import net.mamoe.mirai.utils.config.MiraiConfigSection;
|
||||||
@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
* <br>
|
* <br>
|
||||||
* {@link Bot} 由 3 个模块组成.
|
* {@link Bot} 由 3 个模块组成.
|
||||||
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问
|
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问
|
||||||
* {@linkplain BotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问
|
* {@linkplain BotNetworkHandlerImpl 网络处理器}: 可通过 {@link Bot#network} 访问
|
||||||
* {@linkplain BotAccount 机器人账号信息}: 可通过 {@link Bot#account} 访问
|
* {@linkplain BotAccount 机器人账号信息}: 可通过 {@link Bot#account} 访问
|
||||||
* <br>
|
* <br>
|
||||||
* 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account}
|
* 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account}
|
||||||
@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
* Bot that is the base of the whole program.
|
* Bot that is the base of the whole program.
|
||||||
* It consists of
|
* It consists of
|
||||||
* a {@link ContactSystem}, which manage contacts such as {@link QQ} and {@link Group};
|
* a {@link ContactSystem}, which manage contacts such as {@link QQ} and {@link Group};
|
||||||
* a {@link BotNetworkHandler}, which manages the connection to the server;
|
* a {@link BotNetworkHandlerImpl}, which manages the connection to the server;
|
||||||
* a {@link BotAccount}, which stores the account information(e.g. qq number the bot)
|
* a {@link BotAccount}, which stores the account information(e.g. qq number the bot)
|
||||||
* <br>
|
* <br>
|
||||||
* To get all the QQ contacts, access {@link Bot#account}
|
* To get all the QQ contacts, access {@link Bot#account}
|
||||||
@ -53,7 +53,7 @@ public final class Bot implements Closeable {
|
|||||||
|
|
||||||
public final ContactSystem contacts = new ContactSystem();
|
public final ContactSystem contacts = new ContactSystem();
|
||||||
|
|
||||||
public final BotNetworkHandler network;
|
public final BotNetworkHandlerImpl network;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
@ -118,7 +118,7 @@ public final class Bot implements Closeable {
|
|||||||
Objects.requireNonNull(owners);
|
Objects.requireNonNull(owners);
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.owners = Collections.unmodifiableList(owners);
|
this.owners = Collections.unmodifiableList(owners);
|
||||||
this.network = new BotNetworkHandler(this);
|
this.network = new BotNetworkHandlerImpl(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable {
|
|||||||
val members = ContactList<QQ>()
|
val members = ContactList<QQ>()
|
||||||
|
|
||||||
override fun sendMessage(message: MessageChain) {
|
override fun sendMessage(message: MessageChain) {
|
||||||
bot.network.messageHandler.sendGroupMessage(this, message)
|
bot.network.message.sendGroupMessage(this, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendXMLMessage(message: String) {
|
override fun sendXMLMessage(message: String) {
|
||||||
|
@ -19,7 +19,7 @@ import net.mamoe.mirai.message.defaults.MessageChain
|
|||||||
*/
|
*/
|
||||||
class QQ(bot: Bot, number: Long) : Contact(bot, number) {
|
class QQ(bot: Bot, number: Long) : Contact(bot, number) {
|
||||||
override fun sendMessage(message: MessageChain) {
|
override fun sendMessage(message: MessageChain) {
|
||||||
bot.network.messageHandler.sendFriendMessage(this, message)
|
bot.network.message.sendFriendMessage(this, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendXMLMessage(message: String) {
|
override fun sendXMLMessage(message: String) {
|
||||||
|
@ -1,428 +1,58 @@
|
|||||||
@file:JvmMultifileClass
|
|
||||||
@file:JvmName("BotNetworkHandler")
|
|
||||||
|
|
||||||
package net.mamoe.mirai.network
|
package net.mamoe.mirai.network
|
||||||
|
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.network.BotNetworkHandlerImpl.BotSocket
|
||||||
import net.mamoe.mirai.MiraiServer
|
import net.mamoe.mirai.network.BotNetworkHandlerImpl.Login
|
||||||
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.handler.*
|
||||||
import net.mamoe.mirai.network.packet.*
|
import net.mamoe.mirai.network.packet.ClientPacket
|
||||||
import net.mamoe.mirai.network.packet.login.*
|
import net.mamoe.mirai.network.packet.Packet
|
||||||
import net.mamoe.mirai.task.MiraiThreadPool
|
import net.mamoe.mirai.network.packet.ServerEventPacket
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.network.packet.ServerPacket
|
||||||
import java.io.Closeable
|
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])的发送和接收.
|
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
|
||||||
* [BotNetworkHandler] 是全程异步和线程安全的.
|
* [BotNetworkHandler] 是全异步和线程安全的.
|
||||||
*
|
*
|
||||||
* [BotNetworkHandler] 由 2 个模块构成:
|
* [BotNetworkHandler] 由 2 个模块构成:
|
||||||
* - [SocketHandler]: 处理数据包底层的发送([ByteArray])
|
* - [BotSocket]: 处理数据包底层的发送([ByteArray])
|
||||||
* - [PacketHandler]: 制作 [Packet] 并传递给 [SocketHandler] 继续处理; 分析来自服务器的数据包并处理
|
* - [PacketHandler]: 制作 [ClientPacket] 并传递给 [BotSocket] 发送; 分析 [ServerPacket] 并处理
|
||||||
*
|
*
|
||||||
* 其中, [PacketHandler] 由 4 个子模块构成:
|
* 其中, [PacketHandler] 由 4 个子模块构成:
|
||||||
* - [DebugHandler] 输出 [Packet.toString]
|
* - [DebugPacketHandler] 输出 [Packet.toString]
|
||||||
* - [Login] 处理 touch/login/verification code 相关
|
* - [Login] 处理 touch/login/verification code 相关
|
||||||
* - [MessageHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
|
* - [MessagePacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
|
||||||
* - [ActionHandler] 处理动作相关(踢人/加入群/好友列表等)
|
* - [ActionPacketHandler] 处理动作相关(踢人/加入群/好友列表等)
|
||||||
*
|
*
|
||||||
* A BotNetworkHandler is used to connect with Tencent servers.
|
* A BotNetworkHandler is used to connect with Tencent servers.
|
||||||
*
|
*
|
||||||
* @author Him188moe
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
|
interface BotNetworkHandler : Closeable {
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 尝试登录
|
* 网络层处理器. 用于编码/解码 [Packet], 发送/接受 [ByteArray]
|
||||||
*
|
*
|
||||||
* @param touchingTimeoutMillis 连接每个服务器的 timeout
|
* java 调用方式: `botNetWorkHandler.getSocket()`
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
val socket: DataPacketSocket
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理登录过程
|
* Debug 包处理器. 仅输出包的信息. 调试阶段使用
|
||||||
|
*
|
||||||
|
* java 调用方式: `botNetWorkHandler.getDebug()`
|
||||||
*/
|
*/
|
||||||
inner class Login : Closeable {
|
var debug: DebugPacketHandler
|
||||||
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)
|
* java 调用方式: `botNetWorkHandler.getMessage()`
|
||||||
it.writeHex("00 14 01 02 00 10")
|
*/
|
||||||
it.writeRandom(16)
|
var message: MessagePacketHandler
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 0828_decr_key
|
* 动作处理. 如发送好友请求, 处理别人发来的好友请求等
|
||||||
*/
|
*
|
||||||
private lateinit var sessionResponseDecryptionKey: ByteArray
|
* java 调用方式: `botNetWorkHandler.getAction()`
|
||||||
|
*/
|
||||||
private var captchaSectionId: Int = 1
|
var action: ActionPacketHandler
|
||||||
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,413 @@
|
|||||||
|
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.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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [BotNetworkHandler] 的内部实现, 该类不会有帮助理解的注解, 请查看 [BotNetworkHandler] 以获取帮助
|
||||||
|
*
|
||||||
|
* @see BotNetworkHandler
|
||||||
|
* @author Him188moe
|
||||||
|
*/
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
|
||||||
|
internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
|
||||||
|
override val socket: BotSocket = BotSocket()
|
||||||
|
|
||||||
|
lateinit var login: Login
|
||||||
|
|
||||||
|
override lateinit var debug: DebugPacketHandler
|
||||||
|
override lateinit var message: MessagePacketHandler
|
||||||
|
override lateinit var action: ActionPacketHandler
|
||||||
|
|
||||||
|
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.socket.touch(ip, touchingTimeoutMillis).get().let { state ->
|
||||||
|
if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) {
|
||||||
|
login()//超时或未知, 重试连接下一个服务器
|
||||||
|
} else {
|
||||||
|
future.complete(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
login()
|
||||||
|
return future
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onLoggedIn(sessionKey: ByteArray) {
|
||||||
|
val session = LoginSession(bot, sessionKey, socket)
|
||||||
|
debug = DebugPacketHandler(session)
|
||||||
|
message = MessagePacketHandler(session)
|
||||||
|
action = ActionPacketHandler(session)
|
||||||
|
|
||||||
|
packetHandlers.add(debug.asNode())
|
||||||
|
packetHandlers.add(message.asNode())
|
||||||
|
packetHandlers.add(action.asNode())
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var sessionKey: ByteArray
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
this.packetHandlers.forEach {
|
||||||
|
it.instance.close()
|
||||||
|
}
|
||||||
|
this.socket.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal inner class BotSocket : 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) {
|
||||||
|
debug.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@BotNetworkHandlerImpl::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({
|
||||||
|
message.ignoreMessage = false
|
||||||
|
}, 3, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
this.tlv0105 = packet.tlv0105
|
||||||
|
|
||||||
|
socket.loginFuture!!.complete(LoginState.SUCCESS)
|
||||||
|
|
||||||
|
onLoggedIn(sessionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 ServerLoginSuccessPacket,
|
||||||
|
is ServerHeartbeatResponsePacket,
|
||||||
|
is UnknownServerPacket -> {
|
||||||
|
//ignored
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
this.captchaCache = null
|
||||||
|
|
||||||
|
this.heartbeatFuture?.cancel(true)
|
||||||
|
|
||||||
|
this.heartbeatFuture = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,8 @@ import net.mamoe.mirai.network.handler.DataPacketSocket
|
|||||||
import net.mamoe.mirai.utils.getGTK
|
import net.mamoe.mirai.utils.getGTK
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一次会话. 当登录完成后, 客户端会拿到 sessionKey. 此时建立 session, 开始处理消息等事务
|
* 登录会话. 当登录完成后, 客户端会拿到 sessionKey.
|
||||||
|
* 此时建立 session, 然后开始处理事务.
|
||||||
*
|
*
|
||||||
* @author Him188moe
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
|
@ -12,9 +12,7 @@ import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageSuccessPack
|
|||||||
import net.mamoe.mirai.network.packet.login.ClientChangeOnlineStatusPacket
|
import net.mamoe.mirai.network.packet.login.ClientChangeOnlineStatusPacket
|
||||||
import net.mamoe.mirai.task.MiraiThreadPool
|
import net.mamoe.mirai.task.MiraiThreadPool
|
||||||
import net.mamoe.mirai.utils.ClientLoginStatus
|
import net.mamoe.mirai.utils.ClientLoginStatus
|
||||||
import net.mamoe.mirai.utils.ImageNetworkUtils
|
|
||||||
import net.mamoe.mirai.utils.getGTK
|
import net.mamoe.mirai.utils.getGTK
|
||||||
import net.mamoe.mirai.utils.toUHexString
|
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -26,8 +24,10 @@ import java.util.function.Supplier
|
|||||||
/**
|
/**
|
||||||
* 动作: 获取好友列表, 点赞, 踢人等.
|
* 动作: 获取好友列表, 点赞, 踢人等.
|
||||||
* 处理动作事件, 承担动作任务.
|
* 处理动作事件, 承担动作任务.
|
||||||
|
*
|
||||||
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
class ActionHandler(session: LoginSession) : PacketHandler(session) {
|
class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
|
||||||
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>())
|
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>())
|
||||||
private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf<UploadImageSession>())
|
private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf<UploadImageSession>())
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ServerTryUploadGroupImageSuccessPacket -> {
|
is ServerTryUploadGroupImageSuccessPacket -> {
|
||||||
ImageNetworkUtils.postImage(packet.uKey.toUHexString(), )
|
// ImageNetworkUtils.postImage(packet.uKey.toUHexString(), )
|
||||||
}
|
}
|
||||||
|
|
||||||
is ServerTryUploadGroupImageFailedPacket -> {
|
is ServerTryUploadGroupImageFailedPacket -> {
|
||||||
@ -51,6 +51,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) {
|
|||||||
|
|
||||||
is ServerTryUploadGroupImageResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
|
is ServerTryUploadGroupImageResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
|
||||||
|
|
||||||
|
is ServerAccountInfoResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
|
||||||
is ServerAccountInfoResponsePacket -> {
|
is ServerAccountInfoResponsePacket -> {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -67,6 +68,9 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) {
|
|||||||
session.gtk = getGTK(session.sKey)
|
session.gtk = getGTK(session.sKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is ServerEventPacket.Raw.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
|
||||||
|
is ServerEventPacket.Raw -> session.socket.distributePacket(packet.distribute())
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +86,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) {
|
|||||||
fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
|
fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
|
||||||
val future = CompletableFuture<AddFriendResult>()
|
val future = CompletableFuture<AddFriendResult>()
|
||||||
val session = AddFriendSession(qqNumber, future, message)
|
val session = AddFriendSession(qqNumber, future, message)
|
||||||
uploadImageSessions.add(session)
|
// uploadImageSessions.add(session)
|
||||||
session.sendAddRequest();
|
session.sendAddRequest();
|
||||||
return future
|
return future
|
||||||
}
|
}
|
||||||
@ -138,7 +142,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
|
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
|
||||||
session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey))
|
// session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
|
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
|
||||||
@ -210,7 +214,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
uploadImageSessions.remove(this)
|
// uploadImageSessions.remove(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,15 @@
|
|||||||
package net.mamoe.mirai.network.handler
|
package net.mamoe.mirai.network.handler
|
||||||
|
|
||||||
|
import net.mamoe.mirai.network.BotNetworkHandlerImpl
|
||||||
import net.mamoe.mirai.network.packet.ClientPacket
|
import net.mamoe.mirai.network.packet.ClientPacket
|
||||||
import net.mamoe.mirai.network.packet.ServerPacket
|
import net.mamoe.mirai.network.packet.ServerPacket
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 网络接口.
|
||||||
|
* 发包 / 处理包.
|
||||||
|
* 仅可通过 [BotNetworkHandlerImpl.socket] 得到实例.
|
||||||
|
*
|
||||||
* @author Him188moe
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
interface DataPacketSocket : Closeable {
|
interface DataPacketSocket : Closeable {
|
||||||
|
@ -8,13 +8,15 @@ import net.mamoe.mirai.utils.notice
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Kind of [PacketHandler] that prints all packets received in the format of hex byte array.
|
* Kind of [PacketHandler] that prints all packets received in the format of hex byte array.
|
||||||
|
*
|
||||||
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
sealed class DebugHandler(session: LoginSession) : PacketHandler(session) {
|
class DebugPacketHandler(session: LoginSession) : PacketHandler(session) {
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
@ExperimentalUnsignedTypes
|
||||||
override fun onPacketReceived(packet: ServerPacket) {
|
override fun onPacketReceived(packet: ServerPacket) {
|
||||||
if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) {
|
if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) {
|
||||||
session.bot notice "Packet received: $packet"
|
session.bot.notice("Packet received: $packet")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is ServerEventPacket) {
|
if (packet is ServerEventPacket) {
|
@ -14,8 +14,11 @@ import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacke
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理消息事件, 承担消息发送任务.
|
* 处理消息事件, 承担消息发送任务.
|
||||||
|
*
|
||||||
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
class MessageHandler(session: LoginSession) : PacketHandler(session) {
|
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||||
|
class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
|
||||||
internal var ignoreMessage: Boolean = true
|
internal var ignoreMessage: Boolean = true
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -67,7 +70,6 @@ class MessageHandler(session: LoginSession) : PacketHandler(session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
|
||||||
fun sendFriendMessage(qq: QQ, message: MessageChain) {
|
fun sendFriendMessage(qq: QQ, message: MessageChain) {
|
||||||
session.socket.sendPacket(ClientSendFriendMessagePacket(session.bot.account.qqNumber, qq.number, session.sessionKey, message))
|
session.socket.sendPacket(ClientSendFriendMessagePacket(session.bot.account.qqNumber, qq.number, session.sessionKey, message))
|
||||||
}
|
}
|
@ -24,8 +24,8 @@ class PacketHandlerNode<T : PacketHandler>(
|
|||||||
val instance: T
|
val instance: T
|
||||||
)
|
)
|
||||||
|
|
||||||
infix fun PacketHandler.to(handler: PacketHandler): PacketHandlerNode<PacketHandler> {
|
fun PacketHandler.asNode(): PacketHandlerNode<PacketHandler> {
|
||||||
return PacketHandlerNode(handler.javaClass, handler)
|
return PacketHandlerNode(this.javaClass, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
class PacketHandlerList : LinkedList<PacketHandlerNode<*>>() {
|
class PacketHandlerList : LinkedList<PacketHandlerNode<*>>() {
|
||||||
|
@ -119,7 +119,7 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
|
|||||||
0x19 -> MessageType.ANONYMOUS
|
0x19 -> MessageType.ANONYMOUS
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
MiraiLogger debug ("ServerGroupMessageEventPacket id=$id")
|
MiraiLogger.debug("ServerGroupMessageEventPacket id=$id")
|
||||||
MessageType.OTHER
|
MessageType.OTHER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ class ClientSessionRequestPacket(
|
|||||||
/**
|
/**
|
||||||
* @author Him188moe
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
|
@PacketId("08 28 04 34")
|
||||||
class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val dataLength: Int) : ServerPacket(inputStream) {
|
class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val dataLength: Int) : ServerPacket(inputStream) {
|
||||||
lateinit var sessionKey: ByteArray
|
lateinit var sessionKey: ByteArray
|
||||||
lateinit var tlv0105: ByteArray
|
lateinit var tlv0105: ByteArray
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package net.mamoe.mirai.network.packet
|
package net.mamoe.mirai.network.packet
|
||||||
|
|
||||||
import net.mamoe.mirai.network.Protocol
|
import net.mamoe.mirai.network.Protocol
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.TEA
|
||||||
|
import net.mamoe.mirai.utils.TestedSuccessfully
|
||||||
|
import net.mamoe.mirai.utils.hexToBytes
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,9 +20,6 @@ class ClientVerificationCodeTransmissionRequestPacket(
|
|||||||
) : ClientPacket() {
|
) : ClientPacket() {
|
||||||
@TestedSuccessfully
|
@TestedSuccessfully
|
||||||
override fun encode() {
|
override fun encode() {
|
||||||
MiraiLogger debug "packetId=$packetId"
|
|
||||||
MiraiLogger debug "verificationSequence=$verificationSequence"
|
|
||||||
|
|
||||||
this.writeByte(packetId)//part of packet id
|
this.writeByte(packetId)//part of packet id
|
||||||
|
|
||||||
this.writeQQ(qq)
|
this.writeQQ(qq)
|
||||||
@ -90,34 +89,6 @@ class ClientVerificationCodeSubmitPacket(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
|
||||||
fun main() {
|
|
||||||
val token0825 = "6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2".hexToBytes()
|
|
||||||
val verificationCode = "AAAA"
|
|
||||||
val verificationToken = "84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94".hexToBytes()
|
|
||||||
//00 02 00 00 08 04 01 E0 00 00 04 53 00 00 00 01 00 00 15 85 01 00 38 6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2 01 03 00 19 02 6D 28 41 D2 A5 6F D2 FC 3E 2A 1F 03 75 DE 6E 28 8F A8 19 3E 5F 16 49 D3 14 00 05 00 00 00 00 00 04 58 51 4E 44 00 38 84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94 00 10 69 20 D1 14 74 F5 B3 93 E4 D5 02 B3 71 1A CD 2A
|
|
||||||
ByteArrayDataOutputStream().let {
|
|
||||||
it.writeHex("00 02 00 00 08 04 01 E0")
|
|
||||||
it.writeHex(Protocol.constantData2)
|
|
||||||
it.writeHex("01 00 38")
|
|
||||||
it.write(token0825)
|
|
||||||
it.writeHex("01 03")
|
|
||||||
|
|
||||||
it.writeShort(25)
|
|
||||||
it.writeHex(Protocol.publicKey)
|
|
||||||
|
|
||||||
it.writeHex("14 00 05 00 00 00 00 00 04")
|
|
||||||
it.write(verificationCode.substring(0..3).toByteArray())
|
|
||||||
it.writeHex("00 38")
|
|
||||||
it.write(verificationToken)
|
|
||||||
|
|
||||||
it.writeHex("00 10")
|
|
||||||
it.writeHex(Protocol.key00BAFix)
|
|
||||||
|
|
||||||
println(it.toByteArray().toUHexString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新验证码
|
* 刷新验证码
|
||||||
*/
|
*/
|
||||||
@ -152,7 +123,7 @@ class ClientVerificationCodeRefreshPacket(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证码输入错误
|
* 验证码输入错误, 同时也会给一部分验证码
|
||||||
*/
|
*/
|
||||||
@PacketId("00 BA 32")
|
@PacketId("00 BA 32")
|
||||||
class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, packetId: ByteArray) : ServerVerificationCodeTransmissionPacket(input, dataSize, packetId)
|
class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, packetId: ByteArray) : ServerVerificationCodeTransmissionPacket(input, dataSize, packetId)
|
||||||
@ -163,7 +134,7 @@ class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, p
|
|||||||
* @author Him188moe
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
@PacketId("00 BA 31")
|
@PacketId("00 BA 31")
|
||||||
abstract class ServerVerificationCodeTransmissionPacket(input: DataInputStream, private val dataSize: Int, private val packetId: ByteArray) : ServerVerificationCodePacket(input) {
|
open class ServerVerificationCodeTransmissionPacket(input: DataInputStream, private val dataSize: Int, private val packetId: ByteArray) : ServerVerificationCodePacket(input) {
|
||||||
|
|
||||||
lateinit var captchaSectionN: ByteArray
|
lateinit var captchaSectionN: ByteArray
|
||||||
lateinit var verificationToken: ByteArray//56bytes
|
lateinit var verificationToken: ByteArray//56bytes
|
||||||
|
Loading…
Reference in New Issue
Block a user