Updated robot & network structure

This commit is contained in:
Him188moe 2019-09-04 20:37:10 +08:00
parent b2ec40e195
commit bde4610f7b
13 changed files with 130 additions and 70 deletions

View File

@ -5,6 +5,7 @@ import net.mamoe.mirai.event.MiraiEventManager;
import net.mamoe.mirai.event.events.server.ServerDisableEvent;
import net.mamoe.mirai.event.events.server.ServerEnableEvent;
import net.mamoe.mirai.network.RobotNetworkHandler;
import net.mamoe.mirai.network.packet.login.LoginState;
import net.mamoe.mirai.task.MiraiTaskManager;
import net.mamoe.mirai.utils.LoggerTextFormat;
import net.mamoe.mirai.utils.MiraiLogger;
@ -120,11 +121,16 @@ public class MiraiServer {
this.qqs.keySet().stream().map(key -> this.qqs.getSection(key)).forEach(section -> {
try {
Robot robot = new Robot(section);
RobotNetworkHandler robotNetworkHandler = robot.getHandler();
robotNetworkHandler.setServerIP("14.116.136.106");
robotNetworkHandler.touch$mirai_core();
RobotNetworkHandler robotNetworkHandler = robot.getNetworkHandler();
robotNetworkHandler.tryLogin$mirai_core(state -> {
if (state == LoginState.SUCCEED) {
Robot.instances.add(robot);
} else {
robot.close();
}
return null;
});
Robot.instances.add(robot);
} catch (Throwable e) {
e.printStackTrace();
getLogger().error("Could not load QQ robots config!");

View File

@ -7,18 +7,19 @@ import net.mamoe.mirai.network.RobotNetworkHandler;
import net.mamoe.mirai.utils.ContactList;
import net.mamoe.mirai.utils.config.MiraiConfigSection;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class Robot {
public class Robot implements Closeable {
public static final List<Robot> instances = Collections.synchronizedList(new LinkedList<>());
private final long qqNumber;
private final String password;
@Getter
private final RobotNetworkHandler handler;
private final RobotNetworkHandler networkHandler;
/**
* Ref list
@ -29,6 +30,14 @@ public class Robot {
private final ContactList<Group> groups = new ContactList<>();
private final ContactList<QQ> qqs = new ContactList<>();
public void close() {
this.networkHandler.close();
this.owners.clear();
this.groups.values().forEach(Group::close);
this.groups.clear();
this.qqs.clear();
}
public boolean isOwnBy(String ownerName) {
return owners.contains(ownerName);
}
@ -50,7 +59,7 @@ public class Robot {
this.qqNumber = qqNumber;
this.password = password;
this.owners = Collections.unmodifiableList(owners);
this.handler = new RobotNetworkHandler(this, this.qqNumber, this.password);
this.networkHandler = new RobotNetworkHandler(this, this.qqNumber, this.password);
}
public QQ getQQ(long qqNumber) {

View File

@ -2,8 +2,9 @@ package net.mamoe.mirai.contact
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.utils.ContactList
import java.io.Closeable
class Group(number: Long) : Contact(number) {
class Group(number: Long) : Contact(number), Closeable {
val groupId = groupNumberToId(number)
val members = ContactList<QQ>()
@ -15,6 +16,10 @@ class Group(number: Long) : Contact(number) {
}
override fun close() {
this.members.clear()
}
companion object {
fun groupNumberToId(number: Long): Long {
val left: Long = number.toString().let {
@ -53,11 +58,6 @@ class Group(number: Long) : Contact(number) {
}
}
@JvmStatic
fun main(args: Array<String>) {
groupNumberToId(580266363)
}
fun groupIdToNumber(id: Long): Long {
var left: Long = id.toString().let {
if (it.length < 6) {

View File

@ -1,16 +1,16 @@
package net.mamoe.mirai.event.events.robot
import net.mamoe.mirai.Robot
import net.mamoe.mirai.event.events.MiraiEvent
import net.mamoe.mirai.network.RobotNetworkHandler
/**
* @author Him188moe
*/
class RobotLoginEvent(val robotNetworkHandler: RobotNetworkHandler) : MiraiEvent()
class RobotLoginEvent(val robot: Robot) : MiraiEvent()
class RobotLogoutEvent(val robotNetworkHandler: RobotNetworkHandler) : MiraiEvent()
class RobotLogoutEvent(val robot: Robot) : MiraiEvent()
class RobotMessageReceivedEvent(val robotNetworkHandler: RobotNetworkHandler, val type: Type, val message: String) : MiraiEvent() {
class RobotMessageReceivedEvent(val robot: Robot, val type: Type, val message: String) : MiraiEvent() {
enum class Type {
FRIEND,
GROUP

View File

@ -26,6 +26,7 @@ public final class At extends Message {
@Override
public String toString() {
return null;
// TODO: 2019/9/4 At.toString
throw new UnsupportedOperationException();
}
}

View File

@ -11,6 +11,7 @@ import net.mamoe.mirai.network.packet.message.ServerSendFriendMessageResponsePac
import net.mamoe.mirai.network.packet.message.ServerSendGroupMessageResponsePacket
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
@ -23,7 +24,7 @@ import java.util.concurrent.TimeUnit
* @author Him188moe
*/
@ExperimentalUnsignedTypes
class RobotNetworkHandler(val robot: Robot, val number: Long, private val password: String) {
internal class RobotNetworkHandler(val robot: Robot, val number: Long, private val password: String) : Closeable {
var socket: DatagramSocket = DatagramSocket((15314 + Math.random() * 100).toInt())
@ -36,6 +37,7 @@ class RobotNetworkHandler(val robot: Robot, val number: Long, private val passwo
}
private lateinit var serverAddress: InetSocketAddress
private var closed: Boolean = false
private lateinit var token00BA: ByteArray //这些数据全部是login用的
private lateinit var token0825: ByteArray
@ -60,6 +62,16 @@ class RobotNetworkHandler(val robot: Robot, val number: Long, private val passwo
private var gtk: Int = 0
private var ignoreMessage: Boolean = false
private var loginState: LoginState? = null
set(value) {
field = value
if (value != null) {
loginHook?.invoke(value)
}
}
private var loginHook: ((LoginState) -> Unit)? = null
init {
tlv0105 = lazyEncode {
it.writeHex("01 05 00 30")
@ -74,15 +86,37 @@ class RobotNetworkHandler(val robot: Robot, val number: Long, private val passwo
@ExperimentalUnsignedTypes
private var md5_32: ByteArray = getRandomKey(32)
/**
* Try to login to server
*/
internal fun tryLogin(loginHook: ((LoginState) -> Unit)? = null) {
//"14.116.136.106",
tryLogin()
}
internal fun touch() {
/**
* Try to login to server
*/
private fun tryLogin(serverAddress: String, loginHook: ((LoginState) -> Unit)? = null) {
touch(serverAddress, loginHook)
}
/**
* Start network
*/
private fun touch(serverAddress: String, loginHook: ((LoginState) -> Unit)? = null) {
serverIP = serverAddress
if (loginHook != null) {
this.loginHook = loginHook
}
this.sendPacket(ClientTouchPacket(this.number, this.serverIP))
}
private fun restartSocket() {
socket.close()
socket = DatagramSocket((15314 + Math.random() * 100).toInt())
socket.connect(this.serverAddress)
socket.connect(this.serverAddress).runCatching { }
val zeroByte: Byte = 0
Thread {
while (true) {
@ -91,11 +125,14 @@ class RobotNetworkHandler(val robot: Robot, val number: Long, private val passwo
socket.receive(dp1)
} catch (e: Exception) {
if (e.message == "socket closed") {
if (!closed) {
restartSocket()
}
return@Thread
}
}
MiraiThreadPool.getInstance().submit {
var i = dp1.data.size - 1;
var i = dp1.data.size - 1
while (dp1.data[i] == zeroByte) {
--i
}
@ -132,7 +169,8 @@ class RobotNetworkHandler(val robot: Robot, val number: Long, private val passwo
}
is ServerLoginResponseFailedPacket -> {
MiraiLogger error "Login failed: " + packet.state.toString()
this.loginState = packet.loginState
MiraiLogger error "Login failed: " + packet.loginState.toString()
return
}
@ -157,7 +195,7 @@ class RobotNetworkHandler(val robot: Robot, val number: Long, private val passwo
is ServerVerificationCodeTransmissionPacket -> {
this.verificationCodeSequence++
this.verificationCodeCache = this.verificationCodeCache!! + packet.verificationCodePart2
this.verificationCodeCache = this.verificationCodeCache!! + packet.verificationCodePartN
this.verificationToken = packet.verificationToken
this.verificationCodeCacheCount++
@ -236,6 +274,7 @@ class RobotNetworkHandler(val robot: Robot, val number: Long, private val passwo
}
is ServerLoginSuccessPacket -> {
loginState = LoginState.SUCCEED
sendPacket(ClientSKeyRequestPacket(this.number, this.sessionKey))
}
@ -328,4 +367,12 @@ class RobotNetworkHandler(val robot: Robot, val number: Long, private val passwo
}
}
}
override fun close() {
this.socket.close()
this.loginState = null
this.loginHook = null
this.verificationCodeCache = null
this.tgtgtKey = null
}
}

View File

@ -140,8 +140,6 @@ fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, lo
val md5_1 = md5(password);
val md5_2 = md5(md5_1 + "00 00 00 00".hexToBytes() + qq.toUInt().toByteArray())
println(md5_1.toUByteArray().toUHexString())
println(md5_2.toUByteArray().toUHexString())
it.write(md5_1)
it.writeInt(loginTime)
it.writeByte(0);
@ -151,8 +149,6 @@ fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, lo
it.writeHex("00 10")
it.writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")
it.write(tgtgtKey)
println()
println(it.toByteArray().toUHexString())
this.write(TEACryptor.encrypt(it.toByteArray(), md5_2))
}
}

View File

@ -2,6 +2,7 @@ package net.mamoe.mirai.network.packet
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.toUHexString
import java.io.ByteArrayOutputStream
import java.io.DataInputStream
@ -77,7 +78,7 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
25 -> MessageType.ANONYMOUS
else -> {
println("id=$id")
MiraiLogger debug ("ServerGroupMessageEventPacket id=$id")
MessageType.OTHER
}
}

View File

@ -3,6 +3,7 @@ package net.mamoe.mirai.network.packet
import net.mamoe.mirai.network.packet.login.*
import net.mamoe.mirai.network.packet.message.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.packet.message.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.getAllDeclaredFields
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
@ -36,7 +37,7 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
271, 207 -> return ServerLoginResponseResendPacketEncrypted(stream, when (idHex) {
"08 36 31 03" -> ServerLoginResponseResendPacket.Flag.`08 36 31 03`
else -> {
println("flag=$idHex"); ServerLoginResponseResendPacket.Flag.OTHER
MiraiLogger debug ("ServerLoginResponseResendPacketEncrypted: flag=$idHex"); ServerLoginResponseResendPacket.Flag.OTHER
}
})
871 -> return ServerLoginResponseVerificationCodePacketEncrypted(stream)
@ -47,16 +48,16 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
}
return ServerLoginResponseFailedPacket(when (bytes.size) {
319 -> ServerLoginResponseFailedPacket.State.WRONG_PASSWORD
135 -> ServerLoginResponseFailedPacket.State.RETYPE_PASSWORD
279 -> ServerLoginResponseFailedPacket.State.BLOCKED
263 -> ServerLoginResponseFailedPacket.State.UNKNOWN_QQ_NUMBER
551, 487 -> ServerLoginResponseFailedPacket.State.DEVICE_LOCK
359 -> ServerLoginResponseFailedPacket.State.TAKEN_BACK
319 -> LoginState.WRONG_PASSWORD
135 -> LoginState.RETYPE_PASSWORD
279 -> LoginState.BLOCKED
263 -> LoginState.UNKNOWN_QQ_NUMBER
551, 487 -> LoginState.DEVICE_LOCK
359 -> LoginState.TAKEN_BACK
//unknown
63 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown error)")//可能是已经完成登录, 服务器拒绝第二次登录
351 -> throw IllegalArgumentException(bytes.size.toString() + " (Illegal package data)")//包数据有误
63 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown error)")
351 -> throw IllegalArgumentException(bytes.size.toString() + " (Illegal package data or Unknown error)")//包数据有误
else -> throw IllegalArgumentException(bytes.size.toString())
}, stream)
@ -88,6 +89,7 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
}
}
@ExperimentalUnsignedTypes
override fun toString(): String {
return this.javaClass.simpleName + this.getAllDeclaredFields().joinToString(", ", "{", "}") {
it.trySetAccessible(); it.name + "=" + it.get(this).let { value ->

View File

@ -6,6 +6,9 @@ import net.mamoe.mirai.utils.*
import java.io.DataInputStream
/**
* 客户端请求验证码图片数据的第几部分
*/
@ExperimentalUnsignedTypes
@PacketId("00 BA 31")
class ClientVerificationCodeTransmissionRequestPacket(
@ -40,11 +43,13 @@ class ClientVerificationCodeTransmissionRequestPacket(
}
/**
* 服务器发送验证码图片文件一部分过来
*
* @author Him188moe
*/
class ServerVerificationCodeTransmissionPacket(input: DataInputStream, val dataSize: Int, val packetId: ByteArray) : ServerVerificationCodePacket(input) {
class ServerVerificationCodeTransmissionPacket(input: DataInputStream, private val dataSize: Int, private val packetId: ByteArray) : ServerVerificationCodePacket(input) {
lateinit var verificationCodePart2: ByteArray
lateinit var verificationCodePartN: ByteArray
lateinit var verificationToken: ByteArray//56bytes
var transmissionCompleted: Boolean = false//验证码是否已经传输完成
lateinit var token00BA: ByteArray//40 bytes
@ -55,10 +60,10 @@ class ServerVerificationCodeTransmissionPacket(input: DataInputStream, val dataS
this.verificationToken = this.input.readNBytesAt(10, 56)
val length = this.input.readShortAt(66)
this.verificationCodePart2 = this.input.readNBytes(length)
this.verificationCodePartN = this.input.readNBytes(length)
this.input.skip(2)
this.transmissionCompleted = this.input.readBoolean()
this.transmissionCompleted = this.input.readBoolean().not()
this.token00BA = this.input.readNBytesAt(dataSize - 57, 40)
this.count = byteArrayOf(0, 0, packetId[2], packetId[3]).toUHexString().hexToInt()
@ -66,6 +71,8 @@ class ServerVerificationCodeTransmissionPacket(input: DataInputStream, val dataS
}
/**
* 暂不了解意义
*
* @author Him188moe
*/
class ServerVerificationCodeRepeatPacket(input: DataInputStream) : ServerVerificationCodePacket(input) {

View File

@ -0,0 +1,18 @@
package net.mamoe.mirai.network.packet.login
/**
* @author Him188moe
*/
enum class LoginState {
SUCCEED,
WRONG_PASSWORD,
// UNKNOWN,//? 要再次发送某数据包
RETYPE_PASSWORD,//similar to [WRONG_PASSWORD]
BLOCKED,//你的帐号存在被盗风险,已进入保护模式
UNKNOWN_QQ_NUMBER,//你输入的帐号不存在
DEVICE_LOCK,//设备锁
TAKEN_BACK,//被回收
// VERIFICATION_CODE,//需要验证码
// SUCCEED,
}

View File

@ -6,19 +6,7 @@ import java.io.DataInputStream
/**
* @author Him188moe
*/
class ServerLoginResponseFailedPacket(val state: State, input: DataInputStream) : ServerPacket(input) {
enum class State {
WRONG_PASSWORD,
// UNKNOWN,//? 要再次发送某数据包
RETYPE_PASSWORD,//similar to [WRONG_PASSWORD]
BLOCKED,//你的帐号存在被盗风险,已进入保护模式
UNKNOWN_QQ_NUMBER,//你输入的帐号不存在
DEVICE_LOCK,//设备锁
TAKEN_BACK,//被回收
// VERIFICATION_CODE,//需要验证码
// SUCCEED,
}
class ServerLoginResponseFailedPacket(val loginState: LoginState, input: DataInputStream) : ServerPacket(input) {
override fun decode() {
}
}

View File

@ -6,8 +6,6 @@ import net.mamoe.mirai.network.packet.dataInputStream
import net.mamoe.mirai.network.packet.goto
import net.mamoe.mirai.util.TestedSuccessfully
import net.mamoe.mirai.utils.TEACryptor
import net.mamoe.mirai.utils.hexToUBytes
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
/**
@ -61,17 +59,4 @@ class ServerLoginResponseResendPacketEncrypted(input: DataInputStream, private v
data = TEACryptor.decrypt(data, tgtgtKey)
return ServerLoginResponseResendPacket(data.dataInputStream(), flag)
}
}
fun main() {
val tgtgtkey = "9E 83 61 FF 18 61 4B 77 34 FE 1C 9C E2 03 B4 F2".hexToUBytes()
ServerLoginResponseResendPacketEncrypted("02 37 13 08 36 31 03 76 E4 B8 DD 00 00 00 94 9B 87 00 87 7F 9E D0 E5 6A F6 17 41 02 0C AA F3 AC C8 CF 4E C6 9D EC FA 6C BD F8 7C 4B A5 28 80 CC DE B5 0A 41 8E 63 CE 5E 30 D8 A6 83 92 0E 2E 5C 35 E5 6E 62 3D FE 17 DD 7C 47 9A AD EF F0 F7 2A 6F 21 32 99 1B 6D E1 DA BE 68 2F 26 A9 93 DE 1B 4F 11 F0 AF A1 06 7B 85 53 46 D2 A3 DD A6 BE F2 76 8A 61 BF 15 FD 17 C4 45 DB EC 05 51 56 46 63 48 87 49 79 0D 40 DF 9D D9 99 93 EC D0 44 7B 4A 79 EB BD 08 10 18 29 0E 85 EE 26 A0 CD 40 00 2F 3E ED F4 A4 C3 01 5E 82 F5 A8 02 FA 70 EB F2 07 AD FF 0E DA 08 7A 3A FE B6 F4 5D 98 18 F7 58 C2 19 21 AF 29 D2 95 16 CE C4 A3 5F B0 E6 23 C2 B2 C6 5F 03 42 C2 44 C2 B0 A0 3F 95 8E 89 EF FC EC E4 BF 03 CB DA 9C D3 84 3F 9B A0 F1 B4 14 6E 23 D5 74 79 6F 89 DA B8 33 DB EF 0B 21 E1 27 27 57 8B 56 CB D9 BF C2 A8 25 6E 48 23 EB 31 9D 03".hexToUBytes().toByteArray().dataInputStream(), ServerLoginResponseResendPacket.Flag.`08 36 31 03`).decrypt(tgtgtkey.toByteArray()).let { it.decode();println(it._0836_tlv0006_encr.toUHexString()) }
val data = "94 9B 87 00 87 7F 9E D0 E5 6A F6 17 41 02 0C AA F3 AC C8 CF 4E C6 9D EC FA 6C BD F8 7C 4B A5 28 80 CC DE B5 0A 41 8E 63 CE 5E 30 D8 A6 83 92 0E 2E 5C 35 E5 6E 62 3D FE 17 DD 7C 47 9A AD EF F0 F7 2A 6F 21 32 99 1B 6D E1 DA BE 68 2F 26 A9 93 DE 1B 4F 11 F0 AF A1 06 7B 85 53 46 D2 A3 DD A6 BE F2 76 8A 61 BF 15 FD 17 C4 45 DB EC 05 51 56 46 63 48 87 49 79 0D 40 DF 9D D9 99 93 EC D0 44 7B 4A 79 EB BD 08 10 18 29 0E 85 EE 26 A0 CD 40 00 2F 3E ED F4 A4 C3 01 5E 82 F5 A8 02 FA 70 EB F2 07 AD FF 0E DA 08 7A 3A FE B6 F4 5D 98 18 F7 58 C2 19 21 AF 29 D2 95 16 CE C4 A3 5F B0 E6 23 C2 B2 C6 5F 03 42 C2 44 C2 B0 A0 3F 95 8E 89 EF FC EC E4 BF 03 CB DA 9C D3 84 3F 9B A0 F1 B4 14 6E 23 D5 74 79 6F 89 DA B8 33 DB EF 0B 21 E1 27 27 57 8B 56 CB D9 BF C2 A8 25 6E 48 23 EB 31 9D".hexToUBytes()
val d1 = TEACryptor.CRYPTOR_SHARE_KEY.decrypt(data.toByteArray())
ServerLoginResponseResendPacket(TEACryptor.decrypt(d1, tgtgtkey.toByteArray()).dataInputStream(), ServerLoginResponseResendPacket.Flag.`08 36 31 03`).let { it.decode();println(it._0836_tlv0006_encr.toUHexString()) }
}