Commit 4a319bf5 authored by Him188moe's avatar Him188moe

Updated network

parent d19216a6
......@@ -5,26 +5,18 @@ 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.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.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
......@@ -33,7 +25,6 @@ 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
......@@ -48,7 +39,7 @@ import kotlin.reflect.KClass
*
* 其中, [PacketHandler] 由 4 个子模块构成:
* - [DebugHandler] 输出 [Packet.toString]
* - [LoginHandler] 处理 touch/login/verification code 相关
* - [Login] 处理 touch/login/verification code 相关
* - [MessageHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* - [ActionHandler] 处理动作相关(踢人/加入群/好友列表等)
*
......@@ -58,41 +49,25 @@ import kotlin.reflect.KClass
*/
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
class BotNetworkHandler(private val bot: Bot) : Closeable {
private val socketHandler: SocketHandler = SocketHandler()
private val socket: SocketHandler = SocketHandler()
val debugHandler = DebugHandler()
val loginHandler = LoginHandler()
val messageHandler = MessageHandler()
val actionHandler = ActionHandler()
fun getSocket(): DataPacketSocket {
return socket
}
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)
}
lateinit var debugHandler: DebugHandler
lateinit var login: Login
lateinit var messageHandler: MessageHandler
lateinit var actionHandler: ActionHandler
override fun close() {
this.packetHandlers.values.forEach {
it.close()
}
this.socketHandler.close()
}
val packetHandlers: PacketHandlerList = PacketHandlerList()
//private | internal
/**
* 仅当 [LoginState] 非 [LoginState.UNKNOWN] 且非 [LoginState.TIMEOUT] 才会调用 [loginHook].
* 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCESS] 调用 [loginHandler]
* 尝试登录
*
* @param touchingTimeoutMillis 连接每个服务器的 timeout
*/
......@@ -102,16 +77,16 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
val future = CompletableFuture<LoginState>()
fun login() {
this.socketHandler.close()
this.socket.close()
val ip = ipQueue.poll()
if (ip == null) {
future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN
return
}
this@BotNetworkHandler.socketHandler.touch(ip, touchingTimeoutMillis).get().let { state ->
this@BotNetworkHandler.socket.touch(ip, touchingTimeoutMillis).get().let { state ->
if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) {
login()
login()//超时或未知, 重试连接下一个服务器
} else {
future.complete(state)
}
......@@ -121,32 +96,38 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
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 lateinit var sessionKey: ByteArray
override fun close() {
this.packetHandlers.forEach {
it.instance.close()
}
this.socket.close()
}
private inner class SocketHandler : Closeable {
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 = ""
......@@ -189,15 +170,19 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
/**
* Start network and touch the server
*/
internal fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture<LoginState> {
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()
socketHandler.serverIP = serverAddress
serverIP = serverAddress
waitForPacket(ServerPacket::class, timeoutMillis) {
loginFuture!!.complete(LoginState.TIMEOUT)
}
sendPacket(ClientTouchPacket(bot.account.qqNumber, socketHandler.serverIP))
sendPacket(ClientTouchPacket(bot.account.qqNumber, serverIP))
return this.loginFuture!!
}
......@@ -207,7 +192,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
*/
@Synchronized
@ExperimentalUnsignedTypes
internal fun sendPacket(packet: ClientPacket) {
override fun sendPacket(packet: ClientPacket) {
checkNotNull(socket) { "network closed" }
if (socket!!.isClosed) {
return
......@@ -222,7 +207,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
val data = packet.toByteArray()
socket!!.send(DatagramPacket(data, data.size))
bot cyanL "Packet sent: $packet"
bot.cyanL("Packet sent: $packet")
PacketSentEvent(bot, packet).broadcast()
} catch (e: Throwable) {
......@@ -234,7 +219,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
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) {
if (packetClass.isInstance(it.packet) && it.bot === bot) {
got = true
true
} else {
......@@ -265,40 +250,15 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
}
}
fun isClosed(): Boolean {
override 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() {
inner class Login : Closeable {
private lateinit var token00BA: ByteArray
private lateinit var token0825: ByteArray
private var loginTime: Int = 0
......@@ -321,34 +281,33 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
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) {
fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerTouchResponsePacket -> {
if (packet.serverIP != null) {//redirection
socketHandler.serverIP = packet.serverIP!!
socket.serverIP = packet.serverIP!!
//connect(packet.serverIP!!)
sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber))
socket.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))
socket.sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825))
}
}
is ServerLoginResponseFailedPacket -> {
socketHandler.loginFuture?.complete(packet.loginState)
socket.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))
socket.sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA))
}
is ServerLoginResponseVerificationCodeInitPacket -> {
......@@ -358,13 +317,13 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
this.captchaSectionId = 1
sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA))
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA))
}
}
is ServerVerificationCodeTransmissionPacket -> {
if (packet is ServerVerificationCodeWrongPacket) {
bot error "验证码错误, 请重新输入"
bot.error("验证码错误, 请重新输入")
captchaSectionId = 1
this.captchaCache = byteArrayOf()
}
......@@ -373,31 +332,31 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
this.token00BA = packet.token00BA
if (packet.transmissionCompleted) {
bot notice (CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream())))
bot notice ("需要验证码登录, 验证码为 4 字母")
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")
bot.notice("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png")
} catch (e: Exception) {
bot notice "无法写出验证码文件, 请尝试查看以上字符图片"
bot.notice("无法写出验证码文件, 请尝试查看以上字符图片")
}
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))
socket.sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825))
} else {
sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken))
socket.sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken))
}
} else {
sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA))
socket.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))
socket.sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socket.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105))
}
//是ClientPasswordSubmissionPacket之后服务器回复的
......@@ -408,10 +367,10 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
//}
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
socket.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
socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
?: token00BA, packet.tlv0006))
}
}
......@@ -419,7 +378,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
is ServerSessionKeyResponsePacket -> {
sessionKey = packet.sessionKey
heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey))
socket.sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey))
}, 90000, 90000, TimeUnit.MILLISECONDS)
BotLoginSucceedEvent(bot).broadcast()
......@@ -430,40 +389,24 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
}, 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))
socket.loginFuture!!.complete(LoginState.SUCCESS)
}
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 ServerEventPacket.Raw -> socket.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 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 ServerAccountInfoResponsePacket,
is ServerLoginSuccessPacket,
is ServerHeartbeatResponsePacket,
is UnknownServerPacket -> {
//ignored
......@@ -478,234 +421,8 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
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
}
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
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
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
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
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
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
package net.mamoe.mirai.network.handler
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.packet.ServerPacket
import java.io.Closeable
import java.util.*
import kotlin.NoSuchElementException
/**
* 数据包(接受/发送)处理器
*/
abstract class PacketHandler(
val session: LoginSession
) : Closeable {
abstract fun onPacketReceived(packet: ServerPacket)
override fun close() {
}
}
class PacketHandlerNode<T : PacketHandler>(
val clazz: Class<T>,
val instance: T
)
infix fun PacketHandler.to(handler: PacketHandler): PacketHandlerNode<PacketHandler> {
return PacketHandlerNode(handler.javaClass, handler)
}
class PacketHandlerList : LinkedList<PacketHandlerNode<*>>() {
fun <T : PacketHandler> get(clazz: Class<T>): T {
this.forEach {
if (it.clazz == clazz) {
@Suppress("UNCHECKED_CAST")
return@get it.instance as T
}
}
throw NoSuchElementException()
}
}
......@@ -6,6 +6,8 @@ import java.io.DataInputStream
/**
* SKey 用于 http api
*
* @author Him188moe
*/
@ExperimentalUnsignedTypes
......
......@@ -34,11 +34,6 @@ enum class LoginState {
*/
TAKEN_BACK,
/**
* 需要验证码登录
*/
VERIFICATION_CODE,
/**
* 未知. 更换服务器或等几分钟再登录可能解决.
*/
......
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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment