mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-19 17:39:17 +08:00
Merge remote-tracking branch 'origin/master'
# Conflicts: # mirai-core/src/main/java/net/mamoe/mirai/Robot.java
This commit is contained in:
commit
93dc8e589e
@ -15,23 +15,25 @@ The project is all for <b>learning proposes</b> and still in <b>developing stage
|
||||
|
||||
### 代码结构
|
||||
Network部分使用 Kotlin 完成(因为kt有对 unsigned byte 的支持).
|
||||
与插件相关性强(或其他在二次开发中容易接触的部分)均使用 Java 完成,
|
||||
与插件相关性强(或其他在二次开发中容易接触)的部分使用 Java 完成,
|
||||
同时也会针对kotlin提供优化的方法调用. 例如对'+'操作符的重载: `String+BufferedImage+QQ.At+Face+URL+String+File` 将会被自动处理为String消息.
|
||||
|
||||
|
||||
### TODO
|
||||
- [x] 事件(Event)模块
|
||||
- [ ] 插件(Plugin)模块 **(Working on)**
|
||||
- [ ] 插件(Plugin)模块
|
||||
- [x] Network - Touch
|
||||
- [X] Network - Login
|
||||
- [X] Network - Session
|
||||
- [ ] Network - Verification Code (Low priority)
|
||||
- [ ] Network - Verification Code **(Working on)**
|
||||
- [X] Network - Message Receiving
|
||||
- [X] Network - Message Sending
|
||||
- [ ] Network - Events **(Working on)**
|
||||
- [ ] Robot - Friend/group list
|
||||
- [ ] Robot - Actions(joining group, adding friend, etc.)
|
||||
- [ ] Message Section **(Working on)**
|
||||
- [ ] Contact
|
||||
- [ ] UI **(Working on)**
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -56,6 +56,12 @@
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.18</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-reflect</artifactId>
|
||||
<version>1.3.41</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
@ -1,28 +1,14 @@
|
||||
package net.mamoe.mirai;
|
||||
|
||||
|
||||
import net.mamoe.mirai.utils.config.MiraiConfig;
|
||||
import net.mamoe.mirai.utils.config.MiraiConfigSection;
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
public final class MiraiMain {
|
||||
private static MiraiServer server;
|
||||
|
||||
public static void main(String[] args) {
|
||||
server = new MiraiServer();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
server.shutdown();
|
||||
}));
|
||||
MiraiConfig config = new MiraiConfig("QQ.yml");
|
||||
MiraiConfigSection<Object> data = config.getSection("123123");
|
||||
data.put("account","123123a");
|
||||
try {
|
||||
Robot robot = new Robot(data);
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> server.shutdown()));
|
||||
}
|
||||
}
|
||||
|
@ -2,24 +2,25 @@ package net.mamoe.mirai;
|
||||
|
||||
import lombok.Getter;
|
||||
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.ClientTouchPacket;
|
||||
import net.mamoe.mirai.event.events.server.ServerDisabledEvent;
|
||||
import net.mamoe.mirai.event.events.server.ServerEnabledEvent;
|
||||
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;
|
||||
import net.mamoe.mirai.utils.config.MiraiConfig;
|
||||
import net.mamoe.mirai.utils.config.MiraiConfigSection;
|
||||
import net.mamoe.mirai.utils.setting.MiraiSetting;
|
||||
import net.mamoe.mirai.utils.setting.MiraiSettingListSection;
|
||||
import net.mamoe.mirai.utils.setting.MiraiSettingMapSection;
|
||||
import net.mamoe.mirai.utils.setting.MiraiSettings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class MiraiServer {
|
||||
private static MiraiServer instance;
|
||||
|
||||
@ -27,17 +28,15 @@ public class MiraiServer {
|
||||
return instance;
|
||||
}
|
||||
|
||||
//mirai version
|
||||
private final static String MIRAI_VERSION = "1.0.0";
|
||||
|
||||
//qq version
|
||||
private final static String QQ_VERSION = "4.9.0";
|
||||
|
||||
|
||||
@Getter //is running under UNIX
|
||||
private boolean unix;
|
||||
|
||||
@Getter//file path
|
||||
@Getter//file pathq
|
||||
public File parentFolder;
|
||||
|
||||
@Getter
|
||||
@ -48,30 +47,30 @@ public class MiraiServer {
|
||||
@Getter
|
||||
MiraiLogger logger;
|
||||
|
||||
MiraiSetting setting;
|
||||
MiraiSettings settings;
|
||||
|
||||
MiraiConfig qqs;
|
||||
|
||||
|
||||
protected MiraiServer() {
|
||||
MiraiServer() {
|
||||
instance = this;
|
||||
this.onLoad();
|
||||
this.onEnable();
|
||||
this.onLoaded();
|
||||
this.onEnabled();
|
||||
}
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
protected void shutdown() {
|
||||
void shutdown() {
|
||||
if (this.enabled) {
|
||||
getLogger().info("About to shutdown Mirai");
|
||||
this.getEventManager().broadcastEvent(new ServerDisableEvent());
|
||||
this.eventManager.broadcastEventAsync(new ServerDisabledEvent());
|
||||
getLogger().info("Data have been saved");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void onLoad() {
|
||||
private void onLoaded() {
|
||||
this.parentFolder = new File(System.getProperty("user.dir"));
|
||||
this.unix = !System.getProperties().getProperty("os.name").toUpperCase().contains("WINDOWS");
|
||||
|
||||
@ -88,7 +87,7 @@ public class MiraiServer {
|
||||
if (!setting.exists()) {
|
||||
this.initSetting(setting);
|
||||
} else {
|
||||
this.setting = new MiraiSetting(setting);
|
||||
this.settings = new MiraiSettings(setting);
|
||||
}
|
||||
|
||||
File qqs = new File(this.parentFolder + "/QQ.yml");
|
||||
@ -116,65 +115,6 @@ public class MiraiServer {
|
||||
});
|
||||
*/
|
||||
|
||||
getLogger().info("ready to connect");
|
||||
|
||||
|
||||
/*
|
||||
MiraiConfigSection section = new MiraiConfigSection<MiraiConfigSection<String>>(){{
|
||||
put("1",new MiraiConfigSection<>(){{
|
||||
put("1","0");
|
||||
}});
|
||||
}};
|
||||
|
||||
this.qqs.put("test",section);
|
||||
this.qqs.save();
|
||||
*/
|
||||
|
||||
|
||||
MiraiConfigSection<MiraiConfigSection> x = this.qqs.getTypedSection("test");
|
||||
//System.out.println(x.getSection("1").getInt("1"));
|
||||
|
||||
/*
|
||||
System.out.println(v);
|
||||
|
||||
System.out.println(v.get("1111"));
|
||||
*/
|
||||
|
||||
|
||||
Robot robot = new Robot(1994701021, "xiaoqqq", new LinkedList<>());
|
||||
RobotNetworkHandler robotNetworkHandler = robot.getHandler();
|
||||
try {
|
||||
//System.out.println(Protocol.Companion.getSERVER_IP().get(3));
|
||||
//System.out.println(Protocol.Companion.getSERVER_IP().toString());
|
||||
|
||||
robotNetworkHandler.setServerIP("14.116.136.106");
|
||||
robotNetworkHandler.sendPacket(new ClientTouchPacket(1994701021, "14.116.136.106"));
|
||||
while (true) ;
|
||||
//robotNetworkHandler.connect("14.116.136.106");
|
||||
//robotNetworkHandler.connect(Protocol.Companion.getSERVER_IP().get(2));
|
||||
//robotNetworkHandler.connect("125.39.132.242");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
/*
|
||||
System.out.println("network test");
|
||||
try {
|
||||
|
||||
|
||||
MiraiUDPServer server = new MiraiUDPServer();
|
||||
MiraiUDPClient client = new MiraiUDPClient(InetAddress.getLocalHost(),9999,MiraiNetwork.getAvailablePort());
|
||||
this.getTaskManager().repeatingTask(() -> {
|
||||
byte[] sendInfo = "test test".getBytes(StandardCharsets.UTF_8);
|
||||
try {
|
||||
client.send(new DatagramPacket(sendInfo,sendInfo.length));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
},300);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}*/
|
||||
}
|
||||
|
||||
private void initSetting(File setting) {
|
||||
@ -187,21 +127,21 @@ public class MiraiServer {
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.setting = new MiraiSetting(setting);
|
||||
MiraiSettingMapSection network = this.setting.getMapSection("network");
|
||||
this.settings = new MiraiSettings(setting);
|
||||
MiraiSettingMapSection network = this.settings.getMapSection("network");
|
||||
network.set("enable_proxy", "not supporting yet");
|
||||
|
||||
MiraiSettingListSection proxy = this.setting.getListSection("proxy");
|
||||
MiraiSettingListSection proxy = this.settings.getListSection("proxy");
|
||||
proxy.add("1.2.3.4:95");
|
||||
proxy.add("1.2.3.4:100");
|
||||
|
||||
MiraiSettingMapSection worker = this.setting.getMapSection("worker");
|
||||
MiraiSettingMapSection worker = this.settings.getMapSection("worker");
|
||||
worker.set("core_task_pool_worker_amount", 5);
|
||||
|
||||
MiraiSettingMapSection plugin = this.setting.getMapSection("plugin");
|
||||
MiraiSettingMapSection plugin = this.settings.getMapSection("plugin");
|
||||
plugin.set("debug", false);
|
||||
|
||||
this.setting.save();
|
||||
this.settings.save();
|
||||
getLogger().info("initialized; changing can be made in setting file: " + setting.toString());
|
||||
}
|
||||
|
||||
@ -227,11 +167,35 @@ public class MiraiServer {
|
||||
getLogger().info("QQ account initialized; changing can be made in Config file: " + qqConfig.toString());
|
||||
}
|
||||
|
||||
private void onEnable() {
|
||||
this.eventManager.broadcastEvent(new ServerEnableEvent());
|
||||
private void onEnabled() {
|
||||
this.enabled = true;
|
||||
this.eventManager.broadcastEventAsync(new ServerEnabledEvent());
|
||||
getLogger().info(LoggerTextFormat.GREEN + "Server enabled; Welcome to Mirai");
|
||||
getLogger().info("Mirai Version=" + MiraiServer.MIRAI_VERSION + " QQ Version=" + MiraiServer.QQ_VERSION);
|
||||
|
||||
getLogger().info("Initializing [Robot]s");
|
||||
|
||||
this.qqs.keySet().stream().map(key -> this.qqs.getSection(key)).forEach(section -> {
|
||||
getLogger().info("Initializing [Robot] " + section.getString("account"));
|
||||
try {
|
||||
Robot robot = new Robot(section);
|
||||
var state = robot.network.tryLogin$mirai_core().get();
|
||||
//robot.network.tryLogin$mirai_core().whenComplete((state, e) -> {
|
||||
if (state == LoginState.SUCCEED) {
|
||||
Robot.instances.add(robot);
|
||||
getLogger().info(" Succeed");
|
||||
} else {
|
||||
getLogger().error(" Failed with error " + state);
|
||||
robot.close();
|
||||
}
|
||||
// }).get();
|
||||
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
getLogger().error("Could not load QQ robots config!");
|
||||
System.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,24 +1,77 @@
|
||||
package net.mamoe.mirai;
|
||||
|
||||
import kotlin.jvm.internal.MagicApiIntrinsics;
|
||||
import lombok.Getter;
|
||||
import net.mamoe.mirai.contact.Group;
|
||||
import net.mamoe.mirai.contact.QQ;
|
||||
import net.mamoe.mirai.network.RobotNetworkHandler;
|
||||
import net.mamoe.mirai.utils.ContactList;
|
||||
import net.mamoe.mirai.utils.config.MiraiConfig;
|
||||
import net.mamoe.mirai.utils.RobotAccount;
|
||||
import net.mamoe.mirai.utils.config.MiraiConfigSection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.io.Closeable;
|
||||
import java.util.*;
|
||||
|
||||
public class Robot {
|
||||
/**
|
||||
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
|
||||
* Mirai 为多账号设计, 可同时维护多个机器人账号.
|
||||
* <br>
|
||||
* {@link Robot} 由 2 个模块组成.
|
||||
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Robot#contacts} 访问
|
||||
* {@linkplain RobotNetworkHandler 网络处理器}: 可通过 {@link Robot#network} 访问
|
||||
* <br>
|
||||
* 另外地, 若你需要得到机器人的 QQ 账号, 请访问 {@link Robot#account}
|
||||
* 若你需要得到服务器上所有机器人列表, 请访问 {@link Robot#instances}
|
||||
*
|
||||
* @author Him188moe
|
||||
* @author NatrualHG
|
||||
* @see net.mamoe.mirai.contact.Contact
|
||||
*
|
||||
* <p>
|
||||
* Robot that is the base of the whole program.
|
||||
* It contains a {@link ContactSystem}, which manage contacts such as {@link QQ} and {@link Group}.
|
||||
*/
|
||||
public final class Robot implements Closeable {
|
||||
public static final List<Robot> instances = Collections.synchronizedList(new LinkedList<>());
|
||||
|
||||
public final RobotAccount account;
|
||||
|
||||
public final ContactSystem contacts = new ContactSystem();
|
||||
|
||||
public final RobotNetworkHandler network;
|
||||
|
||||
/**
|
||||
* Robot 联系人管理.
|
||||
*
|
||||
* @see Robot#contacts
|
||||
*/
|
||||
public final class ContactSystem {
|
||||
private final ContactList<Group> groups = new ContactList<>();
|
||||
private final ContactList<QQ> qqs = new ContactList<>();
|
||||
|
||||
private ContactSystem() {
|
||||
|
||||
}
|
||||
|
||||
public QQ getQQ(long qqNumber) {
|
||||
if (!this.qqs.containsKey(qqNumber)) {
|
||||
this.qqs.put(qqNumber, new QQ(Robot.this, qqNumber));
|
||||
}
|
||||
return this.qqs.get(qqNumber);
|
||||
}
|
||||
|
||||
public Group getGroupByNumber(long groupNumber) {
|
||||
if (!this.groups.containsKey(groupNumber)) {
|
||||
this.groups.put(groupNumber, new Group(Robot.this, groupNumber));
|
||||
}
|
||||
return groups.get(groupNumber);
|
||||
}
|
||||
|
||||
public Group getGroupById(long groupId) {
|
||||
return getGroupByNumber(Group.Companion.groupIdToNumber(groupId));
|
||||
}
|
||||
}
|
||||
|
||||
private final int qqNumber;
|
||||
private final String password;
|
||||
@Getter
|
||||
private final RobotNetworkHandler handler;
|
||||
|
||||
/**
|
||||
* Ref list
|
||||
@ -26,66 +79,35 @@ public class Robot {
|
||||
@Getter
|
||||
private final List<String> owners;
|
||||
|
||||
private final ContactList<Group> groups = new ContactList<>();
|
||||
private final ContactList<QQ> qqs = new ContactList<>();
|
||||
|
||||
public boolean isOwnBy(String ownerName) {
|
||||
return owners.contains(ownerName);
|
||||
}
|
||||
|
||||
|
||||
public Robot(MiraiConfigSection<Object> data) throws Throwable {
|
||||
this(
|
||||
data.getIntOrThrow("account", () -> new IllegalArgumentException("account")),
|
||||
data.getStringOrThrow("password", () -> new IllegalArgumentException("password")),
|
||||
new RobotAccount(
|
||||
data.getLongOrThrow("account", () -> new IllegalArgumentException("account")),
|
||||
data.getStringOrThrow("password", () -> new IllegalArgumentException("password"))
|
||||
),
|
||||
data.getAsOrDefault("owners", ArrayList::new)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public Robot(int qqNumber, String password, List<String> owners) {
|
||||
this.qqNumber = qqNumber;
|
||||
this.password = password;
|
||||
public Robot(@NotNull RobotAccount account, @NotNull List<String> owners) {
|
||||
Objects.requireNonNull(account);
|
||||
Objects.requireNonNull(owners);
|
||||
this.account = account;
|
||||
this.owners = Collections.unmodifiableList(owners);
|
||||
this.handler = new RobotNetworkHandler(this, this.qqNumber, this.password);
|
||||
this.network = new RobotNetworkHandler(this);
|
||||
}
|
||||
|
||||
public QQ getQQ(int qqNumber) {
|
||||
if (!this.qqs.containsKey(qqNumber)) {
|
||||
this.qqs.put(qqNumber, new QQ(qqNumber));
|
||||
}
|
||||
return this.qqs.get(qqNumber);
|
||||
}
|
||||
|
||||
public Group getGroup(int groupNumber) {
|
||||
if (!this.groups.containsKey(groupNumber)) {
|
||||
this.groups.put(groupNumber, new Group(groupNumber));
|
||||
}
|
||||
return groups.get(groupNumber);
|
||||
public void close() {
|
||||
this.network.close();
|
||||
this.contacts.groups.values().forEach(Group::close);
|
||||
this.contacts.groups.clear();
|
||||
this.contacts.qqs.clear();
|
||||
}
|
||||
|
||||
public Group getGroupByGroupId(int groupId) {
|
||||
return getGroup(Group.Companion.groupIdToNumber(groupId));
|
||||
}
|
||||
|
||||
/* Attribute
|
||||
* Attribute will be SAVED and LOAD automatically as long as the QQ account is same
|
||||
* {Attributes} is in the format of Map<String, Object>, keeping thread-safe
|
||||
* {Attributes} is a KEY-VALUE typed Data.
|
||||
* *
|
||||
**/
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
class RobotAttribute extends MiraiConfigSection<Object>{
|
||||
|
||||
static RobotAttribute load(Robot robot){
|
||||
|
||||
}
|
||||
|
||||
private MiraiConfigSection<Object> data;//late init
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
|
@ -1,14 +1,16 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.Robot
|
||||
import net.mamoe.mirai.message.Message
|
||||
import net.mamoe.mirai.message.defaults.PlainText
|
||||
|
||||
/**
|
||||
* A contact is a [QQ] or a [Group] for one particular [Robot] instance only.
|
||||
*
|
||||
* @param robot Owner [Robot]
|
||||
* @author Him188moe
|
||||
*/
|
||||
abstract class Contact(val number: Int) {
|
||||
abstract class Contact(val robot: Robot, val number: Long) {
|
||||
|
||||
/**
|
||||
* Async
|
||||
|
@ -1,121 +1,118 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.Robot
|
||||
import net.mamoe.mirai.message.Message
|
||||
import net.mamoe.mirai.utils.ContactList
|
||||
import java.io.Closeable
|
||||
|
||||
class Group(number: Int) : Contact(number) {
|
||||
class Group(robot: Robot, number: Long) : Contact(robot, number), Closeable {
|
||||
val groupId = groupNumberToId(number)
|
||||
val members = ContactList<QQ>()
|
||||
|
||||
init {
|
||||
Instances.groups.add(this)
|
||||
}
|
||||
|
||||
override fun sendMessage(message: Message) {
|
||||
|
||||
robot.network.messageHandler.sendGroupMessage(this, message)
|
||||
}
|
||||
|
||||
override fun sendXMLMessage(message: String) {
|
||||
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this.members.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun groupNumberToId(number: Int): Int {
|
||||
val left: Int = number.toString().let {
|
||||
fun groupNumberToId(number: Long): Long {
|
||||
val left: Long = number.toString().let {
|
||||
if (it.length < 6) {
|
||||
return@groupNumberToId number
|
||||
}
|
||||
it.substring(0, it.length - 6).toInt()
|
||||
it.substring(0, it.length - 6).toLong()
|
||||
}
|
||||
val right: Int = number.toString().let {
|
||||
it.substring(it.length - 6).toInt()
|
||||
val right: Long = number.toString().let {
|
||||
it.substring(it.length - 6).toLong()
|
||||
}
|
||||
|
||||
return when (left) {
|
||||
in 1..10 -> {
|
||||
((left + 202).toString() + right.toString()).toInt()
|
||||
((left + 202).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 11..19 -> {
|
||||
((left + 469).toString() + right.toString()).toInt()
|
||||
((left + 469).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 20..66 -> {
|
||||
((left + 208).toString() + right.toString()).toInt()
|
||||
((left + 208).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 67..156 -> {
|
||||
((left + 1943).toString() + right.toString()).toInt()
|
||||
((left + 1943).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 157..209 -> {
|
||||
((left + 199).toString() + right.toString()).toInt()
|
||||
((left + 199).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 210..309 -> {
|
||||
((left + 389).toString() + right.toString()).toInt()
|
||||
((left + 389).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 310..499 -> {
|
||||
((left + 349).toString() + right.toString()).toInt()
|
||||
((left + 349).toString() + right.toString()).toLong()
|
||||
}
|
||||
else -> number
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
groupNumberToId(580266363)
|
||||
}
|
||||
|
||||
fun groupIdToNumber(id: Int): Int {
|
||||
var left: Int = id.toString().let {
|
||||
fun groupIdToNumber(id: Long): Long {
|
||||
var left: Long = id.toString().let {
|
||||
if (it.length < 6) {
|
||||
return@groupIdToNumber id
|
||||
}
|
||||
it.substring(0 until it.length - 6).toInt()
|
||||
it.substring(0 until it.length - 6).toLong()
|
||||
}
|
||||
|
||||
return when (left) {
|
||||
in 203..212 -> {
|
||||
val right: Int = id.toString().let {
|
||||
it.substring(it.length - 6).toInt()
|
||||
val right: Long = id.toString().let {
|
||||
it.substring(it.length - 6).toLong()
|
||||
}
|
||||
((left - 202).toString() + right.toString()).toInt()
|
||||
((left - 202).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 480..488 -> {
|
||||
val right: Int = id.toString().let {
|
||||
it.substring(it.length - 6).toInt()
|
||||
val right: Long = id.toString().let {
|
||||
it.substring(it.length - 6).toLong()
|
||||
}
|
||||
((left - 469).toString() + right.toString()).toInt()
|
||||
((left - 469).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 2100..2146 -> {
|
||||
val right: Int = id.toString().let {
|
||||
it.substring(it.length - 7).toInt()
|
||||
val right: Long = id.toString().let {
|
||||
it.substring(it.length - 7).toLong()
|
||||
}
|
||||
left = left.toString().substring(0 until 3).toInt()
|
||||
((left - 208).toString() + right.toString()).toInt()
|
||||
left = left.toString().substring(0 until 3).toLong()
|
||||
((left - 208).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 2010..2099 -> {
|
||||
val right: Int = id.toString().let {
|
||||
it.substring(it.length - 6).toInt()
|
||||
val right: Long = id.toString().let {
|
||||
it.substring(it.length - 6).toLong()
|
||||
}
|
||||
((left - 1943).toString() + right.toString()).toInt()
|
||||
((left - 1943).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 2147..2199 -> {
|
||||
val right: Int = id.toString().let {
|
||||
it.substring(it.length - 7).toInt()
|
||||
val right: Long = id.toString().let {
|
||||
it.substring(it.length - 7).toLong()
|
||||
}
|
||||
left = left.toString().substring(0 until 3).toInt()
|
||||
((left - 199).toString() + right.toString()).toInt()
|
||||
left = left.toString().substring(0 until 3).toLong()
|
||||
((left - 199).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 4100..4199 -> {
|
||||
val right: Int = id.toString().let {
|
||||
it.substring(it.length - 7).toInt()
|
||||
val right: Long = id.toString().let {
|
||||
it.substring(it.length - 7).toLong()
|
||||
}
|
||||
left = left.toString().substring(0 until 3).toInt()
|
||||
((left - 389).toString() + right.toString()).toInt()
|
||||
left = left.toString().substring(0 until 3).toLong()
|
||||
((left - 389).toString() + right.toString()).toLong()
|
||||
}
|
||||
in 3800..3989 -> {
|
||||
val right: Int = id.toString().let {
|
||||
it.substring(it.length - 7).toInt()
|
||||
val right: Long = id.toString().let {
|
||||
it.substring(it.length - 7).toLong()
|
||||
}
|
||||
left = left.toString().substring(0 until 3).toInt()
|
||||
((left - 349).toString() + right.toString()).toInt()
|
||||
left = left.toString().substring(0 until 3).toLong()
|
||||
((left - 349).toString() + right.toString()).toLong()
|
||||
}
|
||||
else -> id
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
|
||||
fun Int.asQQ(): QQ = Instances.qqs.stream().filter { t: QQ? -> t?.number?.equals(this)!! }.findAny().orElse(QQ(this))!!
|
||||
|
||||
fun Int.asGroup(): Group = Instances.groups.stream().filter { t: Group? -> t?.number?.equals(this)!! }.findAny().orElse(Group(this))!!
|
||||
|
||||
fun String.withImage(id: String, type: String) = "{$id}.$type"
|
||||
|
||||
fun String.withAt(qq: Int) = qq.asQQ().at()
|
||||
|
||||
fun String.withAt(qq: QQ) = qq.at()
|
||||
|
||||
object Instances {
|
||||
var qqs = arrayListOf<QQ>()
|
||||
var groups = arrayListOf<Group>()
|
||||
}
|
@ -1,18 +1,21 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.Robot
|
||||
import net.mamoe.mirai.message.Message
|
||||
import net.mamoe.mirai.message.defaults.At
|
||||
|
||||
/**
|
||||
* QQ 账号.
|
||||
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Robot].
|
||||
*
|
||||
* A QQ instance helps you to receive message from or send message to.
|
||||
* Notice that, one QQ instance belong to one [Robot], that is, QQ instances from different [Robot] are NOT the same.
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
class QQ(number: Int) : Contact(number) {
|
||||
init {
|
||||
Instances.qqs.add(this)
|
||||
}
|
||||
|
||||
class QQ(robot: Robot, number: Long) : Contact(robot, number) {
|
||||
override fun sendMessage(message: Message) {
|
||||
|
||||
robot.network.messageHandler.sendFriendMessage(this, message)
|
||||
}
|
||||
|
||||
override fun sendXMLMessage(message: String) {
|
||||
@ -21,6 +24,8 @@ class QQ(number: Int) : Contact(number) {
|
||||
|
||||
/**
|
||||
* At(@) this account.
|
||||
*
|
||||
* @return an instance of [Message].
|
||||
*/
|
||||
fun at(): At {
|
||||
return At(this)
|
||||
|
@ -0,0 +1,27 @@
|
||||
package net.mamoe.mirai.event;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 实现这个接口的事件可以被异步执行或阻塞执行
|
||||
*
|
||||
* @author Him188moe
|
||||
* @see AsyncEventKt 若你使用 kotlin, 请查看针对 kotlin 的优化实现
|
||||
*/
|
||||
public interface AsyncEvent {
|
||||
|
||||
default CompletableFuture<? extends AsyncEvent> broadcastAsync() {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <E extends AsyncEvent> CompletableFuture<E> broadcastEventAsync(Consumer<E> callback) {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync((E) this, callback);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <E extends AsyncEvent> CompletableFuture<E> broadcastEventAsync(Runnable callback) {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync((E) this, callback);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
@file:JvmName("AsyncEventKt")
|
||||
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.function.Consumer
|
||||
|
||||
fun <E : AsyncEvent> E.broadcastAsync(callback: Consumer<E>): CompletableFuture<E> {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync(this, callback)
|
||||
}
|
||||
|
||||
fun <E : AsyncEvent> E.broadcastAsync(callback: Runnable): CompletableFuture<E> {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync(this, callback)
|
||||
}
|
||||
|
||||
fun <E : AsyncEvent> E.broadcastAsyncSmart(): CompletableFuture<E> {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync(this)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.mamoe.mirai.event.events;
|
||||
package net.mamoe.mirai.event;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
@ -0,0 +1,35 @@
|
||||
package net.mamoe.mirai.event;
|
||||
|
||||
import net.mamoe.mirai.utils.EventException;
|
||||
|
||||
/**
|
||||
* @author NatrualHG
|
||||
* @see AsyncEvent
|
||||
*/
|
||||
public abstract class MiraiEvent {
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public boolean isCancelled() {
|
||||
if (!(this instanceof Cancellable)) {
|
||||
return false;
|
||||
}
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancel(true);
|
||||
}
|
||||
|
||||
public void cancel(boolean value) {
|
||||
if (!(this instanceof Cancellable)) {
|
||||
throw new EventException("Event is not Cancellable");
|
||||
}
|
||||
this.cancelled = value;
|
||||
}
|
||||
|
||||
public final MiraiEvent broadcast() {
|
||||
MiraiEventManager.getInstance().broadcastEvent(this);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -2,20 +2,21 @@ package net.mamoe.mirai.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.mamoe.mirai.event.events.Cancellable;
|
||||
import net.mamoe.mirai.event.events.MiraiEvent;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author NatrualHG
|
||||
*/
|
||||
public class MiraiEventHook<T extends MiraiEvent> implements Closeable {
|
||||
|
||||
@Getter
|
||||
Class<T> eventClass;
|
||||
|
||||
@Getter
|
||||
private volatile Consumer<T> handler;
|
||||
protected volatile Consumer<T> handler;
|
||||
|
||||
@Getter
|
||||
private volatile int priority = 0;
|
||||
@ -31,7 +32,7 @@ public class MiraiEventHook<T extends MiraiEvent> implements Closeable {
|
||||
* return true -> this hook need to be removed
|
||||
*/
|
||||
@Getter
|
||||
private Predicate<T> valid;
|
||||
protected Predicate<T> validChecker;
|
||||
|
||||
public MiraiEventHook(Class<T> eventClass) {
|
||||
this(eventClass,null);
|
||||
@ -58,26 +59,26 @@ public class MiraiEventHook<T extends MiraiEvent> implements Closeable {
|
||||
}
|
||||
|
||||
|
||||
private MiraiEventHook<T> setValid(Predicate<T> valid) {
|
||||
this.valid = valid;
|
||||
private MiraiEventHook<T> setValidChecker(Predicate<T> validChecker) {
|
||||
this.validChecker = validChecker;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MiraiEventHook<T> setValidUntil(Predicate<T> valid) {
|
||||
return this.setValid(valid);
|
||||
return this.setValidChecker(valid);
|
||||
}
|
||||
|
||||
public MiraiEventHook<T> setValidWhile(Predicate<T> valid) {
|
||||
return this.setValid(valid.negate());
|
||||
return this.setValidChecker(valid.negate());
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean accept(MiraiEvent event) {
|
||||
if(!(event instanceof Cancellable && event.isCancelled() && this.isIgnoreCancelled())){
|
||||
this.getHandler().accept((T) event);
|
||||
this.getHandler().accept((T) event);
|
||||
}
|
||||
return this.valid == null || this.valid.test((T) event);
|
||||
return this.validChecker == null || this.validChecker.test((T) event);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,6 +104,6 @@ public class MiraiEventHook<T extends MiraiEvent> implements Closeable {
|
||||
@Override
|
||||
public void close(){
|
||||
this.handler = null;
|
||||
this.valid = null;
|
||||
this.validChecker = null;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Predicate
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
class MiraiEventHookKt<E : MiraiEvent>(eventClass: Class<E>) : MiraiEventHook<E>(eventClass) {
|
||||
fun onEvent(handler: (E) -> Unit) {
|
||||
this@MiraiEventHookKt.handler = Consumer(handler)
|
||||
}
|
||||
|
||||
fun validChecker(predicate: (E) -> Boolean) {
|
||||
this@MiraiEventHookKt.validChecker = Predicate(predicate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Kotlin 风格回调
|
||||
* 你的代码可以这样(并且 validChecker 是可选的):
|
||||
*
|
||||
* event.hook {
|
||||
* onEvent {}
|
||||
* validChecker {}
|
||||
* }
|
||||
*/
|
||||
fun <E : MiraiEvent> E.hook(handler: MiraiEventHookKt<E>.() -> Unit): MiraiEventHookKt<E> {
|
||||
return MiraiEventHookKt(this.javaClass).apply(handler)
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
@file:JvmName("MiraiEventKt")
|
||||
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
fun <E : MiraiEvent> E.broadcastSmart(): E {
|
||||
MiraiEventManager.getInstance().broadcastEvent(this as MiraiEvent)
|
||||
return this
|
||||
}
|
@ -1,23 +1,25 @@
|
||||
package net.mamoe.mirai.event;
|
||||
|
||||
import net.mamoe.mirai.MiraiServer;
|
||||
import net.mamoe.mirai.event.events.MiraiEvent;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 线程安全的事件管理器.
|
||||
*
|
||||
* @author NaturalHG
|
||||
* @see MiraiEventManagerKt 若你使用 kotlin, 请查看针对 kotlin 的优化实现
|
||||
*/
|
||||
public class MiraiEventManager {
|
||||
private MiraiEventManager() {
|
||||
MiraiEventManager() {
|
||||
|
||||
}
|
||||
|
||||
private static MiraiEventManager instance = new MiraiEventManager();
|
||||
|
||||
public static MiraiEventManager getInstance() {
|
||||
return MiraiEventManager.instance;
|
||||
return EventManager.INSTANCE;//实例来自 kotlin 的 singleton
|
||||
}
|
||||
|
||||
private final ReentrantReadWriteLock hooksLock = new ReentrantReadWriteLock();
|
||||
@ -117,23 +119,40 @@ public class MiraiEventManager {
|
||||
}
|
||||
|
||||
|
||||
public void asyncBroadcastEvent(MiraiEvent event) {
|
||||
this.asyncBroadcastEvent(event, a -> {
|
||||
});
|
||||
}
|
||||
public <E extends AsyncEvent> CompletableFuture<E> broadcastEventAsync(E event) {
|
||||
Objects.requireNonNull(event);
|
||||
if (!(event instanceof MiraiEvent)) {
|
||||
throw new IllegalArgumentException("event must be instanceof MiraiEvent");
|
||||
}
|
||||
|
||||
public <D extends MiraiEvent> void asyncBroadcastEvent(D event, Consumer<D> callback) {
|
||||
MiraiServer.getInstance().getTaskManager().ansycTask(() -> {
|
||||
MiraiEventManager.this.broadcastEvent(event);
|
||||
CompletableFuture<E> future = new CompletableFuture<>();
|
||||
future.completeAsync(() -> {
|
||||
MiraiEventManager.this.broadcastEvent((MiraiEvent) event);
|
||||
return event;
|
||||
}, callback);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
public <E extends AsyncEvent> CompletableFuture<E> broadcastEventAsync(E event, Consumer<E> callback) {
|
||||
Objects.requireNonNull(event);
|
||||
Objects.requireNonNull(callback);
|
||||
if (!(event instanceof MiraiEvent)) {
|
||||
throw new IllegalArgumentException("event must be instanceof MiraiEvent");
|
||||
}
|
||||
|
||||
CompletableFuture<E> future = new CompletableFuture<>();
|
||||
future.whenComplete((a, b) -> callback.accept(event));
|
||||
future.completeAsync(() -> {
|
||||
MiraiEventManager.this.broadcastEvent((MiraiEvent) event);
|
||||
return event;
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
|
||||
public <D extends MiraiEvent> void asyncBroadcastEvent(D event, Runnable callback) {
|
||||
asyncBroadcastEvent(event, t -> callback.run());
|
||||
public <D extends AsyncEvent> CompletableFuture<D> broadcastEventAsync(D event, Runnable callback) {
|
||||
return broadcastEventAsync(event, t -> callback.run());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,30 +1,59 @@
|
||||
@file:JvmName("MiraiEventManagerKt")
|
||||
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import net.mamoe.mirai.event.events.MiraiEvent
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* [MiraiEventManager] 的 kotlin 简易化实现.
|
||||
* 若要 hook 一个事件, 你可以:
|
||||
* FriendMessageEvent::class.hookOnce {}
|
||||
* FriendMessageEvent::class.hookAlways {}
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
|
||||
object EventManager : MiraiEventManager()
|
||||
|
||||
/**
|
||||
* 每次事件触发时都会调用 hook
|
||||
*/
|
||||
fun <C : Class<E>, E : MiraiEvent> C.hookAlways(hook: (E) -> Unit) {
|
||||
MiraiEventManager.getInstance().hookAlways(MiraiEventHook<E>(this, hook))
|
||||
}
|
||||
|
||||
/**
|
||||
* 当下一次事件触发时调用 hook
|
||||
*/
|
||||
fun <C : Class<E>, E : MiraiEvent> C.hookOnce(hook: (E) -> Unit) {
|
||||
MiraiEventManager.getInstance().hookOnce(MiraiEventHook<E>(this, hook))
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次事件触发时都会调用 hook, 直到 hook 返回 false 时停止 hook
|
||||
*/
|
||||
fun <C : Class<E>, E : MiraiEvent> C.hookWhile(hook: (E) -> Boolean) {
|
||||
MiraiEventManager.getInstance().hookAlways(MiraiEventHookSimple<E>(this, hook))
|
||||
MiraiEventManager.getInstance().hookAlways(MiraiEventHookSimple(this, hook))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 每次事件触发时都会调用 hook
|
||||
*/
|
||||
fun <C : KClass<E>, E : MiraiEvent> C.hookAlways(hook: (E) -> Unit) {
|
||||
this.java.hookAlways(hook)
|
||||
}
|
||||
|
||||
/**
|
||||
* 当下一次事件触发时调用 hook
|
||||
*/
|
||||
fun <C : KClass<E>, E : MiraiEvent> C.hookOnce(hook: (E) -> Unit) {
|
||||
this.java.hookOnce(hook)
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次事件触发时都会调用 hook, 直到 hook 返回 false 时停止 hook
|
||||
*/
|
||||
fun <C : KClass<E>, E : MiraiEvent> C.hookWhile(hook: (E) -> Boolean) {
|
||||
this.java.hookWhile(hook)
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
package net.mamoe.mirai.event.events;
|
||||
|
||||
import net.mamoe.mirai.event.MiraiEventManager;
|
||||
import net.mamoe.mirai.utils.EventException;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class MiraiEvent {
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public boolean isCancelled() {
|
||||
if (!(this instanceof Cancellable)) {
|
||||
throw new EventException("Event is not Cancellable");
|
||||
}
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancel(true);
|
||||
}
|
||||
|
||||
public void cancel(boolean value) {
|
||||
if (!(this instanceof Cancellable)) {
|
||||
throw new EventException("Event is not Cancellable");
|
||||
}
|
||||
this.cancelled = value;
|
||||
}
|
||||
|
||||
protected String eventName;
|
||||
|
||||
public String getEventName() {
|
||||
if (this.eventName == null) {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
return this.eventName;
|
||||
}
|
||||
|
||||
public final MiraiEvent broadcast() {
|
||||
MiraiEventManager.getInstance().broadcastEvent(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public final <D extends MiraiEvent> void asyncBroadcast(Consumer<D> callback) {
|
||||
MiraiEventManager.getInstance().asyncBroadcastEvent((D) this, callback);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public final <D extends MiraiEvent> void asyncBroadcast(Runnable callback) {
|
||||
MiraiEventManager.getInstance().asyncBroadcastEvent((D) this, callback);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package net.mamoe.mirai.event.events.network;
|
||||
|
||||
import net.mamoe.mirai.event.Cancellable;
|
||||
import net.mamoe.mirai.network.packet.ClientPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Packet 已经 {@link ClientPacket#encode()}, 即将被发送
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
public final class BeforePacketSendEvent extends ClientPacketEvent implements Cancellable {
|
||||
public BeforePacketSendEvent(@NotNull ClientPacket packet) {
|
||||
super(packet);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package net.mamoe.mirai.event.events.network;
|
||||
|
||||
import net.mamoe.mirai.network.packet.ClientPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
public abstract class ClientPacketEvent extends PacketEvent {
|
||||
public ClientPacketEvent(@NotNull ClientPacket packet) {
|
||||
super(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientPacket getPacket() {
|
||||
return (ClientPacket) super.getPacket();
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package net.mamoe.mirai.event.events.network;
|
||||
|
||||
import net.mamoe.mirai.event.MiraiEvent;
|
||||
import net.mamoe.mirai.network.packet.Packet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
public abstract class PacketEvent extends MiraiEvent {
|
||||
private final Packet packet;
|
||||
|
||||
public PacketEvent(@NotNull Packet packet) {
|
||||
this.packet = Objects.requireNonNull(packet);
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package net.mamoe.mirai.event.events.network;
|
||||
|
||||
import net.mamoe.mirai.network.packet.ClientPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Packet 已经发出
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
public final class PacketSentEvent extends ClientPacketEvent {
|
||||
public PacketSentEvent(@NotNull ClientPacket packet) {
|
||||
super(packet);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.mamoe.mirai.event.events.network;
|
||||
|
||||
import net.mamoe.mirai.network.packet.ServerPacket;
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
public abstract class ServerPacketEvent extends PacketEvent {
|
||||
public ServerPacketEvent(ServerPacket packet) {
|
||||
super(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerPacket getPacket() {
|
||||
return (ServerPacket) super.getPacket();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.mamoe.mirai.event.events.network;
|
||||
|
||||
import net.mamoe.mirai.event.Cancellable;
|
||||
import net.mamoe.mirai.network.packet.ServerPacket;
|
||||
import net.mamoe.mirai.network.packet.ServerVerificationCodePacket;
|
||||
|
||||
/**
|
||||
* 服务器接到某数据包时触发这个事件.
|
||||
* 注意, 当接收到数据包的加密包(如 {@link ServerVerificationCodePacket.Encrypted})也会触发这个事件, 随后才会
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
public final class ServerPacketReceivedEvent extends ServerPacketEvent implements Cancellable {
|
||||
public ServerPacketReceivedEvent(ServerPacket packet) {
|
||||
super(packet);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package net.mamoe.mirai.event.events.robot;
|
||||
|
||||
import net.mamoe.mirai.Robot;
|
||||
import net.mamoe.mirai.event.events.MiraiEvent;
|
||||
import net.mamoe.mirai.event.MiraiEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -1,19 +1,18 @@
|
||||
package net.mamoe.mirai.event.events.robot
|
||||
|
||||
import net.mamoe.mirai.event.events.MiraiEvent
|
||||
import net.mamoe.mirai.network.RobotNetworkHandler
|
||||
import net.mamoe.mirai.Robot
|
||||
import net.mamoe.mirai.event.MiraiEvent
|
||||
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,5 +7,4 @@ public final class RobotLoginSucceedEvent extends RobotEvent {
|
||||
public RobotLoginSucceedEvent(Robot robot) {
|
||||
super(robot);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
package net.mamoe.mirai.event.events.server;
|
||||
|
||||
import net.mamoe.mirai.event.events.MiraiEvent;
|
||||
|
||||
public final class ServerDisableEvent extends MiraiEvent {
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package net.mamoe.mirai.event.events.server;
|
||||
|
||||
import net.mamoe.mirai.event.AsyncEvent;
|
||||
import net.mamoe.mirai.event.MiraiEvent;
|
||||
|
||||
public final class ServerDisabledEvent extends MiraiEvent implements AsyncEvent {
|
||||
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package net.mamoe.mirai.event.events.server;
|
||||
|
||||
import net.mamoe.mirai.event.events.MiraiEvent;
|
||||
|
||||
public final class ServerEnableEvent extends MiraiEvent {
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package net.mamoe.mirai.event.events.server;
|
||||
|
||||
import net.mamoe.mirai.event.AsyncEvent;
|
||||
import net.mamoe.mirai.event.MiraiEvent;
|
||||
|
||||
public final class ServerEnabledEvent extends MiraiEvent implements AsyncEvent {
|
||||
|
||||
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package net.mamoe.mirai.message;
|
||||
|
||||
import net.mamoe.mirai.contact.Contact;
|
||||
import net.mamoe.mirai.contact.QQ;
|
||||
import net.mamoe.mirai.message.defaults.At;
|
||||
import net.mamoe.mirai.message.defaults.Image;
|
||||
import net.mamoe.mirai.message.defaults.MessageChain;
|
||||
import net.mamoe.mirai.message.defaults.PlainText;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -11,7 +13,12 @@ import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 可发送的或从服务器接收的消息.
|
||||
* 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 {@linkplain PlainText 纯文本}, {@linkplain Image 图片} 等.
|
||||
*
|
||||
* @author Him188moe
|
||||
* @see Contact#sendMessage(Message) 发送这个消息
|
||||
* @see MessageKt 若你使用 kotlin, 请查看针对 kotlin 的优化实现
|
||||
*/
|
||||
public abstract class Message {
|
||||
@Override
|
||||
@ -38,7 +45,7 @@ public abstract class Message {
|
||||
return new MessageChain(this, Objects.requireNonNull(tail));
|
||||
}
|
||||
|
||||
public Message concat(String tail) {
|
||||
public final Message concat(String tail) {
|
||||
return concat(new PlainText(tail));
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
@file:JvmName("MessageKt")
|
||||
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
import net.mamoe.mirai.message.defaults.PlainText
|
||||
@ -12,4 +14,7 @@ infix operator fun Message.plus(another: Message): Message = this.concat(another
|
||||
*/
|
||||
infix operator fun Message.plus(another: String): Message = this.concat(another)
|
||||
|
||||
/**
|
||||
* 连接 [String] 与 [Message]
|
||||
*/
|
||||
infix fun String.concat(another: Message): Message = PlainText(this).concat(another)
|
@ -7,25 +7,28 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* At 一个人的消息.
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
public final class At extends Message {
|
||||
private final int target;
|
||||
private final long target;
|
||||
|
||||
public At(@NotNull QQ target) {
|
||||
this(Objects.requireNonNull(target).getNumber());
|
||||
}
|
||||
|
||||
public At(int target) {
|
||||
public At(long target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public int getTarget() {
|
||||
public long getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return null;
|
||||
// TODO: 2019/9/4 At.toString
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import net.mamoe.mirai.message.FaceID;
|
||||
import net.mamoe.mirai.message.Message;
|
||||
|
||||
/**
|
||||
* QQ 自带表情
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
public final class Face extends Message {
|
||||
|
@ -29,6 +29,9 @@ public final class MessageChain extends Message {
|
||||
list.add(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An unmodifiable list
|
||||
*/
|
||||
public List<Message> toList() {
|
||||
return List.copyOf(list);
|
||||
}
|
||||
|
@ -7,70 +7,84 @@ import java.util.stream.Collectors
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
interface Protocol {
|
||||
companion object {
|
||||
val SERVER_IP: ArrayList<String> = object : ArrayList<String>() {
|
||||
init {
|
||||
add("183.60.56.29")
|
||||
object Protocol {
|
||||
val SERVER_IP: List<String> = object : ArrayList<String>() {
|
||||
init {
|
||||
add("183.60.56.29")
|
||||
|
||||
arrayOf(
|
||||
"sz2.tencent.com",
|
||||
"sz3.tencent.com",
|
||||
"sz4.tencent.com",
|
||||
"sz5.tencent.com",
|
||||
"sz6.tencent.com",
|
||||
"sz8.tencent.com",
|
||||
"sz9.tencent.com"
|
||||
).forEach { this.add(InetAddress.getByName(it).hostAddress) }
|
||||
arrayOf(
|
||||
"sz3.tencent.com",
|
||||
"sz4.tencent.com",
|
||||
"sz5.tencent.com",
|
||||
"sz6.tencent.com",
|
||||
"sz8.tencent.com",
|
||||
"sz9.tencent.com",
|
||||
"sz2.tencent.com"
|
||||
).forEach { this.add(InetAddress.getByName(it).hostAddress) }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
get() = Collections.unmodifiableList(field)
|
||||
|
||||
const val head = "02"
|
||||
const val ver = "37 13"
|
||||
const val fixVer = "03 00 00 00 01 2E 01 00 00 68 52 00 00 00 00"
|
||||
const val tail = "03"
|
||||
/**
|
||||
* _fixVer
|
||||
*/
|
||||
const val fixVer2 = "02 00 00 00 01 01 01 00 00 68 20"
|
||||
/**
|
||||
* 0825data1
|
||||
*/
|
||||
const val constantData0 = "00 18 00 16 00 01 "
|
||||
/**
|
||||
* 0825data2
|
||||
*/
|
||||
const val constantData1 = "00 00 04 53 00 00 00 01 00 00 15 85 "
|
||||
const val key0825 = "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D"
|
||||
const val redirectionKey = "A8 F2 14 5F 58 12 60 AF 07 63 97 D6 76 B2 1A 3B"
|
||||
const val publicKey = "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"
|
||||
const val shareKey = "1A E9 7F 7D C9 73 75 98 AC 02 E0 80 5F A9 C6 AF"
|
||||
const val fix0836 = "06 A9 12 97 B7 F8 76 25 AF AF D3 EA B4 C8 BC E7 "
|
||||
|
||||
const val head = "02"
|
||||
const val ver = "37 13 "
|
||||
const val fixVer = "03 00 00 00 01 2E 01 00 00 68 52 00 00 00 00 "
|
||||
const val tail = " 03"
|
||||
const val _fixVer = "02 00 00 00 01 01 01 00 00 68 20 "
|
||||
const val _0825data0 = "00 18 00 16 00 01 "
|
||||
const val _0825data2 = "00 00 04 53 00 00 00 01 00 00 15 85 "
|
||||
const val _0825key = "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D"
|
||||
const val redirectionKey = "A8 F2 14 5F 58 12 60 AF 07 63 97 D6 76 B2 1A 3B"
|
||||
const val publicKey = "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"
|
||||
const val shareKey = "1A E9 7F 7D C9 73 75 98 AC 02 E0 80 5F A9 C6 AF"
|
||||
const val _0836fix = "06 A9 12 97 B7 F8 76 25 AF AF D3 EA B4 C8 BC E7 "
|
||||
const val key00BA = "C1 9C B8 C8 7B 8C 81 BA 9E 9E 7A 89 E1 7A EC 94"
|
||||
const val key00BAFix = "69 20 D1 14 74 F5 B3 93 E4 D5 02 B3 71 1A CD 2A"
|
||||
|
||||
const val _00BaKey = "C1 9C B8 C8 7B 8C 81 BA 9E 9E 7A 89 E1 7A EC 94"
|
||||
const val _00BaFixKey = "69 20 D1 14 74 F5 B3 93 E4 D5 02 B3 71 1A CD 2A"
|
||||
/**
|
||||
* 0836_622_fix2
|
||||
*/
|
||||
const val passwordSubmissionKey2 = "00 15 00 30 00 01 01 27 9B C7 F5 00 10 65 03 FD 8B 00 00 00 00 00 00 00 00 00 00 00 00 02 90 49 55 33 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B";
|
||||
/**
|
||||
* 0836_622_fix1
|
||||
*/
|
||||
const val passwordSubmissionKey1 = "03 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 01 01 03 00 19";
|
||||
/**
|
||||
* fix_0836_1
|
||||
*/
|
||||
const val key0836 = "EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA"
|
||||
|
||||
const val encryptKey = "“BA 42 FF 01 CF B4 FF D2 12 F0 6E A7 1B 7C B3 08”"
|
||||
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
|
||||
|
||||
const val _0836_622_fix2 = "00 15 00 30 00 01 01 27 9B C7 F5 00 10 65 03 FD 8B 00 00 00 00 00 00 00 00 00 00 00 00 02 90 49 55 33 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B";
|
||||
const val _0836_622_fix1 = "03 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 01 01 03 00 19";
|
||||
const val _0836key1 = "EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA"
|
||||
|
||||
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun hexToBytes(hex: String): ByteArray {
|
||||
hex.hashCode().let { id ->
|
||||
if (hexToByteArrayCacheMap.containsKey(id)) {
|
||||
return hexToByteArrayCacheMap[id]!!.clone()
|
||||
} else {
|
||||
hexToUBytes(hex).toByteArray().let {
|
||||
hexToByteArrayCacheMap[id] = it.clone();
|
||||
return it
|
||||
}
|
||||
@ExperimentalUnsignedTypes
|
||||
fun hexToBytes(hex: String): ByteArray {
|
||||
hex.hashCode().let { id ->
|
||||
if (hexToByteArrayCacheMap.containsKey(id)) {
|
||||
return hexToByteArrayCacheMap[id]!!.clone()
|
||||
} else {
|
||||
hexToUBytes(hex).toByteArray().let {
|
||||
hexToByteArrayCacheMap[id] = it.clone();
|
||||
return it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun hexToUBytes(hex: String): UByteArray = Arrays
|
||||
.stream(hex.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
|
||||
.map { value -> value.trim { it <= ' ' } }
|
||||
.map { s -> s.toUByte(16) }
|
||||
.collect(Collectors.toList()).toUByteArray()
|
||||
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun hexToUBytes(hex: String): UByteArray = Arrays
|
||||
.stream(hex.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
|
||||
.map { value -> value.trim { it <= ' ' } }
|
||||
.map { s -> s.toUByte(16) }
|
||||
.collect(Collectors.toList()).toUByteArray()
|
||||
|
||||
}
|
||||
|
@ -1,306 +1,544 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("RobotNetworkHandler")
|
||||
package net.mamoe.mirai.network
|
||||
|
||||
import net.mamoe.mirai.MiraiServer
|
||||
import net.mamoe.mirai.Robot
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
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.events.robot.RobotLoginSucceedEvent
|
||||
import net.mamoe.mirai.event.hookWhile
|
||||
import net.mamoe.mirai.message.Message
|
||||
import net.mamoe.mirai.network.RobotNetworkHandler.*
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket
|
||||
import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket
|
||||
import net.mamoe.mirai.network.packet.login.*
|
||||
import net.mamoe.mirai.network.packet.message.ClientSendGroupMessagePacket
|
||||
import net.mamoe.mirai.network.packet.message.ServerSendFriendMessageResponsePacket
|
||||
import net.mamoe.mirai.network.packet.message.ServerSendGroupMessageResponsePacket
|
||||
import net.mamoe.mirai.network.packet.verification.ServerVerificationCodePacket
|
||||
import net.mamoe.mirai.network.packet.verification.ServerVerificationCodePacketEncrypted
|
||||
import net.mamoe.mirai.task.MiraiThreadPool
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.Closeable
|
||||
import java.net.DatagramPacket
|
||||
import java.net.DatagramSocket
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
/**
|
||||
* Mirai 的网络处理器, 它处理所有数据包([Packet])的发送和接收.
|
||||
* [RobotNetworkHandler] 是全程异步和线程安全的.
|
||||
*
|
||||
* [RobotNetworkHandler] 由 2 个模块构成:
|
||||
* - [SocketHandler]: 处理数据包底层的发送([ByteArray])
|
||||
* - [PacketHandler]: 制作 [Packet] 并传递给 [SocketHandler] 继续处理; 分析来自服务器的数据包并处理
|
||||
*
|
||||
* 其中, [PacketHandler] 由 4 个子模块构成:
|
||||
* - [DebugHandler] 输出 [Packet.toString]
|
||||
* - [LoginHandler] 处理 touch/login/verification code 相关
|
||||
* - [MessageHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
|
||||
* - [ActionHandler] 处理动作相关(踢人/加入群/好友列表等)
|
||||
*
|
||||
* A RobotNetworkHandler is used to connect with Tencent servers.
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
class RobotNetworkHandler(val robot: Robot, val number: Int, private val password: String) {
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
|
||||
class RobotNetworkHandler(private val robot: Robot) : Closeable {
|
||||
private val socketHandler: SocketHandler = SocketHandler()
|
||||
|
||||
private var sequence: Int = 0
|
||||
val debugHandler = DebugHandler()
|
||||
val loginHandler = LoginHandler()
|
||||
val messageHandler = MessageHandler()
|
||||
val actionHandler = ActionHandler()
|
||||
|
||||
var socket: DatagramSocket = DatagramSocket((15314 + Math.random() * 100).toInt())
|
||||
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
|
||||
)
|
||||
|
||||
var serverIP: String = ""
|
||||
set(value) {
|
||||
serverAddress = InetSocketAddress(value, 8000)
|
||||
field = value
|
||||
/**
|
||||
* Not async
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
fun sendPacket(packet: ClientPacket) {
|
||||
socketHandler.sendPacket(packet)
|
||||
}
|
||||
|
||||
socket.close()
|
||||
socket = DatagramSocket((15314 + Math.random() * 100).toInt())
|
||||
socket.connect(this.serverAddress)
|
||||
val zeroByte: Byte = 0
|
||||
override fun close() {
|
||||
this.packetHandlers.values.forEach {
|
||||
it.close()
|
||||
}
|
||||
this.socketHandler.close()
|
||||
}
|
||||
|
||||
|
||||
//private | internal
|
||||
|
||||
internal fun tryLogin(): CompletableFuture<LoginState> = this.tryLogin(500, TimeUnit.MILLISECONDS)
|
||||
|
||||
|
||||
/**
|
||||
* 仅当 [LoginState] 非 [LoginState.UNKNOWN] 且非 [LoginState.TIMEOUT] 才会调用 [loginHook].
|
||||
* 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCEED] 调用 [loginHandler]
|
||||
*
|
||||
* @param connectingTimeout 连接每个服务器的 timeout
|
||||
*/
|
||||
internal fun tryLogin(connectingTimeout: Long, unit: TimeUnit = TimeUnit.MILLISECONDS): CompletableFuture<LoginState> {
|
||||
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
|
||||
val future = CompletableFuture<LoginState>()
|
||||
|
||||
fun login() {
|
||||
val ip = ipQueue.poll()
|
||||
if (ip != null) {
|
||||
// val future = this@RobotNetworkHandler.socketHandler.touch(ip)
|
||||
|
||||
this@RobotNetworkHandler.socketHandler.touch(ip).runCatching {
|
||||
this@runCatching.get(connectingTimeout, unit).let { state ->
|
||||
if (state == LoginState.UNKNOWN) {
|
||||
login()
|
||||
} else {
|
||||
future.complete(state)
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
when (it) {
|
||||
is TimeoutException -> login()
|
||||
else -> throw it
|
||||
}
|
||||
}
|
||||
} else {
|
||||
future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN
|
||||
}
|
||||
}
|
||||
login()
|
||||
return future
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配收到的数据包
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
internal fun distributePacket(packet: ServerPacket) {
|
||||
packet.decode()
|
||||
if (ServerPacketReceivedEvent(packet).broadcast().isCancelled) {
|
||||
debugHandler.onPacketReceived(packet)
|
||||
return
|
||||
}
|
||||
this.packetHandlers.values.forEach {
|
||||
it.onPacketReceived(packet)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private inner class SocketHandler : Closeable {
|
||||
private var socket: DatagramSocket? = null
|
||||
|
||||
internal var serverIP: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
restartSocket()
|
||||
}
|
||||
|
||||
internal var loginFuture: CompletableFuture<LoginState>? = null
|
||||
|
||||
private fun restartSocket() {
|
||||
socket?.close()
|
||||
socket = DatagramSocket(0)
|
||||
socket!!.connect(InetSocketAddress(serverIP, 8000))
|
||||
Thread {
|
||||
while (true) {
|
||||
val dp1 = DatagramPacket(ByteArray(2048), 2048)
|
||||
try {
|
||||
socket.receive(dp1)
|
||||
} catch (e: Exception) {
|
||||
if (e.message == "socket closed") {
|
||||
return@Thread
|
||||
}
|
||||
}
|
||||
MiraiThreadPool.getInstance().submit {
|
||||
var i = dp1.data.size - 1;
|
||||
while (dp1.data[i] == zeroByte) {
|
||||
--i
|
||||
}
|
||||
try {
|
||||
onPacketReceived(ServerPacket.ofByteArray(dp1.data.copyOfRange(0, i + 1)))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
private lateinit var serverAddress: InetSocketAddress
|
||||
/**
|
||||
* Start network and touch the server
|
||||
*/
|
||||
internal fun touch(serverAddress: String): CompletableFuture<LoginState> {
|
||||
MiraiLogger.info("Connecting server: $serverAddress")
|
||||
this.loginFuture = CompletableFuture()
|
||||
|
||||
private lateinit var token00BA: ByteArray //这些数据全部是login用的
|
||||
private lateinit var token0825: ByteArray
|
||||
private var loginTime: Int = 0
|
||||
private lateinit var loginIP: String
|
||||
private var tgtgtKey: ByteArray? = null
|
||||
private var tlv0105: ByteArray
|
||||
private lateinit var _0828_rec_decr_key: ByteArray
|
||||
socketHandler.serverIP = serverAddress
|
||||
sendPacket(ClientTouchPacket(robot.account.qqNumber, socketHandler.serverIP))
|
||||
|
||||
return this.loginFuture!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Not async
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
internal fun sendPacket(packet: ClientPacket) {
|
||||
checkNotNull(socket) { "socket closed" }
|
||||
|
||||
try {
|
||||
packet.encodePacket()
|
||||
|
||||
if (BeforePacketSendEvent(packet).broadcast().isCancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
val data = packet.toByteArray()
|
||||
socket!!.send(DatagramPacket(data, data.size))
|
||||
MiraiLogger info "Packet sent: $packet"
|
||||
|
||||
PacketSentEvent(packet).broadcast()
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <P : ServerPacket> waitForPacket(packetClass: KClass<P>, timeoutMillis: Long, timeout: () -> Unit) {
|
||||
var got = false
|
||||
ServerPacketReceivedEvent::class.hookWhile {
|
||||
if (packetClass.isInstance(it.packet)) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private lateinit var sessionKey: ByteArray//这两个是登录成功后得到的
|
||||
private lateinit var sKey: String
|
||||
private lateinit var sessionKey: ByteArray
|
||||
|
||||
abstract inner class PacketHandler : Closeable {
|
||||
abstract fun onPacketReceived(packet: ServerPacket)
|
||||
|
||||
override fun close() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to access web API(for friends list etc.)
|
||||
* Kind of [PacketHandler] that prints all packets received in the format of hex byte array.
|
||||
*/
|
||||
private lateinit var cookies: String
|
||||
private var gtk: Int = 0
|
||||
private var ignoreMessage: Boolean = false
|
||||
inner class DebugHandler : PacketHandler() {
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
MiraiLogger info "Packet received: $packet"
|
||||
if (packet is ServerEventPacket) {
|
||||
sendPacket(ClientMessageResponsePacket(robot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
tlv0105 = lazyEncode {
|
||||
/**
|
||||
* 处理登录过程
|
||||
*/
|
||||
inner class LoginHandler : PacketHandler() {
|
||||
private lateinit var token00BA: ByteArray
|
||||
private lateinit var token0825: ByteArray
|
||||
private var loginTime: Int = 0
|
||||
private lateinit var loginIP: String
|
||||
private var tgtgtKey: ByteArray? = null
|
||||
|
||||
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 verificationCodeSequence: Int = 0//这两个验证码使用
|
||||
private var verificationCodeCache: ByteArray? = null//每次包只发一部分验证码来
|
||||
private var verificationCodeCacheCount: Int = 1//
|
||||
private lateinit var verificationToken: ByteArray
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
private var md5_32: ByteArray = getRandomKey(32)
|
||||
private var heartbeatFuture: ScheduledFuture<*>? = null
|
||||
private var sKeyRefresherFuture: ScheduledFuture<*>? = null
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
internal fun onPacketReceived(packet: ServerPacket) {
|
||||
packet.decode()
|
||||
MiraiLogger info "Packet received: $packet"
|
||||
if (packet is ServerEventPacket) {
|
||||
sendPacket(ClientMessageResponsePacket(this.number, packet.packetId, this.sessionKey, packet.eventIdentity))
|
||||
}
|
||||
when (packet) {
|
||||
is ServerTouchResponsePacket -> {
|
||||
if (packet.serverIP != null) {//redirection
|
||||
serverIP = packet.serverIP!!
|
||||
//connect(packet.serverIP!!)
|
||||
sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, number))
|
||||
} else {//password submission
|
||||
this.loginIP = packet.loginIP
|
||||
this.loginTime = packet.loginTime
|
||||
this.token0825 = packet.token0825
|
||||
this.tgtgtKey = packet.tgtgtKey
|
||||
sendPacket(ClientPasswordSubmissionPacket(this.number, this.password, packet.loginTime, packet.loginIP, packet.tgtgtKey, packet.token0825))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerLoginResponseFailedPacket -> {
|
||||
MiraiLogger error "Login failed: " + packet.state.toString()
|
||||
return
|
||||
}
|
||||
|
||||
is ServerLoginResponseVerificationCodePacket -> {
|
||||
//[token00BA]来源之一: 验证码
|
||||
this.token00BA = packet.token00BA
|
||||
|
||||
with(MiraiServer.getInstance().parentFolder + "verifyCode.png") {
|
||||
ByteArrayInputStream(packet.verifyCode).transferTo(FileOutputStream(this))
|
||||
println("验证码已写入到 " + this.path)
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerTouchResponsePacket -> {
|
||||
if (packet.serverIP != null) {//redirection
|
||||
socketHandler.serverIP = packet.serverIP!!
|
||||
//connect(packet.serverIP!!)
|
||||
sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, robot.account.qqNumber))
|
||||
} else {//password submission
|
||||
this.loginIP = packet.loginIP
|
||||
this.loginTime = packet.loginTime
|
||||
this.token0825 = packet.token0825
|
||||
this.tgtgtKey = packet.tgtgtKey
|
||||
sendPacket(ClientPasswordSubmissionPacket(robot.account.qqNumber, robot.account.password, packet.loginTime, packet.loginIP, packet.tgtgtKey, packet.token0825))
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
|
||||
this.sequence = 1
|
||||
sendPacket(ClientLoginVerificationCodePacket(this.number, this.token0825, this.sequence, this.token00BA))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
is ServerLoginResponseSuccessPacket -> {
|
||||
this._0828_rec_decr_key = packet._0828_rec_decr_key
|
||||
sendPacket(ClientSessionRequestPacket(this.number, this.serverIP, this.loginIP, this.md5_32, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105))
|
||||
}
|
||||
|
||||
//是ClientPasswordSubmissionPacket之后服务器回复的
|
||||
is ServerLoginResponseResendPacket -> {
|
||||
if (packet.tokenUnknown != null) {
|
||||
//this.token00BA = packet.token00BA!!
|
||||
//println("token00BA changed!!! to " + token00BA.toUByteArray())
|
||||
}
|
||||
if (packet.flag == ServerLoginResponseResendPacket.Flag.`08 36 31 03`) {
|
||||
this.tgtgtKey = packet.tgtgtKey
|
||||
sendPacket(ClientLoginResendPacket3104(
|
||||
this.number,
|
||||
this.password,
|
||||
this.loginTime,
|
||||
this.loginIP,
|
||||
this.tgtgtKey!!,
|
||||
this.token0825,
|
||||
when (packet.tokenUnknown != null) {
|
||||
true -> packet.tokenUnknown!!
|
||||
false -> this.token00BA
|
||||
},
|
||||
packet._0836_tlv0006_encr
|
||||
))
|
||||
} else {
|
||||
sendPacket(ClientLoginResendPacket3106(
|
||||
this.number,
|
||||
this.password,
|
||||
this.loginTime,
|
||||
this.loginIP,
|
||||
this.tgtgtKey!!,
|
||||
this.token0825,
|
||||
when (packet.tokenUnknown != null) {
|
||||
true -> packet.tokenUnknown!!
|
||||
false -> this.token00BA
|
||||
},
|
||||
packet._0836_tlv0006_encr
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
is ServerVerificationCodePacket -> {
|
||||
this.sequence++
|
||||
|
||||
}
|
||||
|
||||
is ServerSessionKeyResponsePacket -> {
|
||||
this.sessionKey = packet.sessionKey
|
||||
MiraiThreadPool.getInstance().scheduleWithFixedDelay({
|
||||
sendPacket(ClientHeartbeatPacket(this.number, this.sessionKey))
|
||||
}, 90000, 90000, TimeUnit.MILLISECONDS)
|
||||
RobotLoginSucceedEvent(robot).broadcast()
|
||||
|
||||
MiraiThreadPool.getInstance().schedule({
|
||||
ignoreMessage = false
|
||||
}, 2, TimeUnit.SECONDS)
|
||||
|
||||
this.tlv0105 = packet.tlv0105
|
||||
sendPacket(ClientLoginStatusPacket(this.number, this.sessionKey, ClientLoginStatus.ONLINE))
|
||||
}
|
||||
|
||||
is ServerLoginSuccessPacket -> {
|
||||
sendPacket(ClientSKeyRequestPacket(this.number, this.sessionKey))
|
||||
}
|
||||
|
||||
is ServerSKeyResponsePacket -> {
|
||||
this.sKey = packet.sKey
|
||||
this.cookies = "uin=o" + this.number + ";skey=" + this.sKey + ";"
|
||||
|
||||
MiraiThreadPool.getInstance().scheduleWithFixedDelay({
|
||||
sendPacket(ClientSKeyRefreshmentRequestPacket(this.number, this.sessionKey))
|
||||
}, 1800000, 1800000, TimeUnit.MILLISECONDS)
|
||||
|
||||
this.gtk = getGTK(sKey)
|
||||
sendPacket(ClientAccountInfoRequestPacket(this.number, this.sessionKey))
|
||||
}
|
||||
|
||||
is ServerHeartbeatResponsePacket -> {
|
||||
|
||||
}
|
||||
|
||||
is ServerAccountInfoResponsePacket -> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
is ServerFriendMessageEventPacket -> {
|
||||
if (ignoreMessage) {
|
||||
is ServerLoginResponseFailedPacket -> {
|
||||
socketHandler.loginFuture!!.complete(packet.loginState)
|
||||
return
|
||||
}
|
||||
|
||||
FriendMessageEvent(this.robot, this.robot.getQQ(packet.qq), packet.message)
|
||||
}
|
||||
is ServerLoginResponseVerificationCodeInitPacket -> {
|
||||
//[token00BA]来源之一: 验证码
|
||||
this.token00BA = packet.token00BA
|
||||
this.verificationCodeCache = packet.verifyCodePart1
|
||||
|
||||
is ServerGroupMessageEventPacket -> {
|
||||
//group message
|
||||
if (packet.message == "牛逼") {
|
||||
sendPacket(ClientSendGroupMessagePacket(Group.groupNumberToId(packet.groupNumber), this.number, this.sessionKey, "牛逼!"))
|
||||
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
|
||||
this.verificationCodeSequence = 1
|
||||
sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, robot.account.qqNumber, this.token0825, this.verificationCodeSequence, this.token00BA))
|
||||
}
|
||||
}
|
||||
|
||||
//todo
|
||||
//GroupMessageEvent(this.robot, this.robot.getGroup(packet.groupNumber), this.robot.getQQ(packet.qq), packet.message)
|
||||
}
|
||||
is ServerVerificationCodeRepeatPacket -> {//todo 这个名字正确么
|
||||
this.tgtgtKey = packet.tgtgtKeyUpdate
|
||||
this.token00BA = packet.token00BA
|
||||
sendPacket(ClientLoginResendPacket3105(robot.account.qqNumber, robot.account.password, this.loginTime, this.loginIP, this.tgtgtKey!!, this.token0825, this.token00BA))
|
||||
}
|
||||
|
||||
is UnknownServerEventPacket -> {
|
||||
//unknown message event
|
||||
}
|
||||
is ServerVerificationCodeTransmissionPacket -> {
|
||||
this.verificationCodeSequence++
|
||||
this.verificationCodeCache = this.verificationCodeCache!! + packet.verificationCodePartN
|
||||
|
||||
is UnknownServerPacket -> {
|
||||
this.verificationToken = packet.verificationToken
|
||||
this.verificationCodeCacheCount++
|
||||
|
||||
}
|
||||
|
||||
is ServerGroupUploadFileEventPacket -> {
|
||||
|
||||
}
|
||||
|
||||
is ServerMessageEventPacketRaw -> onPacketReceived(packet.analyze())
|
||||
|
||||
is ServerVerificationCodePacketEncrypted -> onPacketReceived(packet.decrypt(this.token00BA))
|
||||
is ServerLoginResponseVerificationCodePacketEncrypted -> onPacketReceived(packet.decrypt())
|
||||
is ServerLoginResponseResendPacketEncrypted -> onPacketReceived(packet.decrypt(this.tgtgtKey!!))
|
||||
is ServerLoginResponseSuccessPacketEncrypted -> onPacketReceived(packet.decrypt(this.tgtgtKey!!))
|
||||
is ServerSessionKeyResponsePacketEncrypted -> onPacketReceived(packet.decrypt(this._0828_rec_decr_key))
|
||||
is ServerTouchResponsePacketEncrypted -> onPacketReceived(packet.decrypt())
|
||||
is ServerSKeyResponsePacketEncrypted -> onPacketReceived(packet.decrypt(this.sessionKey))
|
||||
is ServerAccountInfoResponsePacketEncrypted -> onPacketReceived(packet.decrypt(this.sessionKey))
|
||||
is ServerMessageEventPacketRawEncoded -> onPacketReceived(packet.decrypt(this.sessionKey))
|
||||
this.token00BA = packet.token00BA
|
||||
|
||||
|
||||
is ServerSendFriendMessageResponsePacket,
|
||||
is ServerSendGroupMessageResponsePacket -> {
|
||||
//todo 看易语言 count 和 sequence 是怎样变化的
|
||||
|
||||
}
|
||||
if (packet.transmissionCompleted) {
|
||||
this.verificationCodeCache
|
||||
TODO("验证码好了")
|
||||
} else {
|
||||
sendPacket(ClientVerificationCodeTransmissionRequestPacket(this.verificationCodeCacheCount, robot.account.qqNumber, this.token0825, this.verificationCodeSequence, this.token00BA))
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException(packet.toString())
|
||||
}
|
||||
is ServerLoginResponseSuccessPacket -> {
|
||||
this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey
|
||||
sendPacket(ClientSessionRequestPacket(robot.account.qqNumber, socketHandler.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105))
|
||||
}
|
||||
|
||||
}
|
||||
//是ClientPasswordSubmissionPacket之后服务器回复的
|
||||
is ServerLoginResponseResendPacket -> {
|
||||
//if (packet.tokenUnknown != null) {
|
||||
//this.token00BA = packet.token00BA!!
|
||||
//println("token00BA changed!!! to " + token00BA.toUByteArray())
|
||||
//}
|
||||
if (packet.flag == ServerLoginResponseResendPacket.Flag.`08 36 31 03`) {
|
||||
this.tgtgtKey = packet.tgtgtKey
|
||||
sendPacket(ClientLoginResendPacket3104(
|
||||
robot.account.qqNumber,
|
||||
robot.account.password,
|
||||
this.loginTime,
|
||||
this.loginIP,
|
||||
this.tgtgtKey!!,
|
||||
this.token0825,
|
||||
when (packet.tokenUnknown != null) {
|
||||
true -> packet.tokenUnknown!!
|
||||
false -> this.token00BA
|
||||
},
|
||||
packet._0836_tlv0006_encr
|
||||
))
|
||||
} else {
|
||||
sendPacket(ClientLoginResendPacket3106(
|
||||
robot.account.qqNumber,
|
||||
robot.account.password,
|
||||
this.loginTime,
|
||||
this.loginIP,
|
||||
this.tgtgtKey!!,
|
||||
this.token0825,
|
||||
when (packet.tokenUnknown != null) {
|
||||
true -> packet.tokenUnknown!!
|
||||
false -> this.token00BA
|
||||
},
|
||||
packet._0836_tlv0006_encr
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun sendPacket(packet: ClientPacket) {
|
||||
MiraiThreadPool.getInstance().submit {
|
||||
try {
|
||||
packet.encode()
|
||||
packet.writeHex(Protocol.tail)
|
||||
is ServerSessionKeyResponsePacket -> {
|
||||
sessionKey = packet.sessionKey
|
||||
heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
|
||||
sendPacket(ClientHeartbeatPacket(robot.account.qqNumber, sessionKey))
|
||||
}, 90000, 90000, TimeUnit.MILLISECONDS)
|
||||
|
||||
val data = packet.toByteArray()
|
||||
socket.send(DatagramPacket(data, data.size))
|
||||
MiraiLogger info "Packet sent: $packet"
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
RobotLoginSucceedEvent(robot).broadcast()
|
||||
|
||||
//登录成功后会收到大量上次的消息, 忽略掉
|
||||
MiraiThreadPool.getInstance().schedule({
|
||||
(packetHandlers[MessageHandler::class] as MessageHandler).ignoreMessage = false
|
||||
}, 2, TimeUnit.SECONDS)
|
||||
|
||||
this.tlv0105 = packet.tlv0105
|
||||
sendPacket(ClientChangeOnlineStatusPacket(robot.account.qqNumber, sessionKey, ClientLoginStatus.ONLINE))
|
||||
}
|
||||
|
||||
is ServerLoginSuccessPacket -> {
|
||||
socketHandler.loginFuture!!.complete(LoginState.SUCCEED)
|
||||
sendPacket(ClientSKeyRequestPacket(robot.account.qqNumber, sessionKey))
|
||||
}
|
||||
|
||||
is ServerSKeyResponsePacket -> {
|
||||
val actionHandler = packetHandlers[ActionHandler::class] as ActionHandler
|
||||
actionHandler.sKey = packet.sKey
|
||||
actionHandler.cookies = "uin=o" + robot.account.qqNumber + ";skey=" + actionHandler.sKey + ";"
|
||||
|
||||
sKeyRefresherFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
|
||||
sendPacket(ClientSKeyRefreshmentRequestPacket(robot.account.qqNumber, sessionKey))
|
||||
}, 1800000, 1800000, TimeUnit.MILLISECONDS)
|
||||
|
||||
actionHandler.gtk = getGTK(actionHandler.sKey)
|
||||
sendPacket(ClientAccountInfoRequestPacket(robot.account.qqNumber, sessionKey))
|
||||
}
|
||||
|
||||
is ServerEventPacket.Raw -> distributePacket(packet.distribute())
|
||||
|
||||
is ServerVerificationCodePacket.Encrypted -> distributePacket(packet.decrypt())
|
||||
is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> distributePacket(packet.decrypt())
|
||||
is ServerLoginResponseResendPacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey!!))
|
||||
is ServerLoginResponseSuccessPacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey!!))
|
||||
is ServerSessionKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
|
||||
is ServerTouchResponsePacket.Encrypted -> distributePacket(packet.decrypt())
|
||||
is ServerSKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
|
||||
is ServerAccountInfoResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
|
||||
is ServerEventPacket.Raw.Encrypted -> distributePacket(packet.decrypt(sessionKey))
|
||||
|
||||
|
||||
is ServerAccountInfoResponsePacket,
|
||||
is ServerHeartbeatResponsePacket,
|
||||
is UnknownServerPacket -> {
|
||||
//ignored
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this.verificationCodeCache = null
|
||||
this.tgtgtKey = null
|
||||
|
||||
this.heartbeatFuture?.cancel(true)
|
||||
this.sKeyRefresherFuture?.cancel(true)
|
||||
|
||||
this.heartbeatFuture = null
|
||||
this.sKeyRefresherFuture = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息事件, 承担消息发送任务.
|
||||
*/
|
||||
inner class MessageHandler : PacketHandler() {
|
||||
internal var ignoreMessage: Boolean = false
|
||||
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerGroupUploadFileEventPacket -> {
|
||||
//todo
|
||||
}
|
||||
|
||||
is ServerFriendMessageEventPacket -> {
|
||||
if (ignoreMessage) {
|
||||
return
|
||||
}
|
||||
|
||||
FriendMessageEvent(robot, robot.contacts.getQQ(packet.qq), packet.message)
|
||||
}
|
||||
|
||||
is ServerGroupMessageEventPacket -> {
|
||||
//todo message chain
|
||||
//GroupMessageEvent(this.robot, robot.contacts.getGroupByNumber(packet.groupNumber), robot.contacts.getQQ(packet.qq), packet.message)
|
||||
}
|
||||
|
||||
is UnknownServerEventPacket,
|
||||
is ServerSendFriendMessageResponsePacket,
|
||||
is ServerSendGroupMessageResponsePacket -> {
|
||||
//ignored
|
||||
}
|
||||
else -> {
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendFriendMessage(qq: QQ, message: Message) {
|
||||
TODO()
|
||||
//sendPacket(ClientSendFriendMessagePacket(robot.account.qqNumber, qq.number, sessionKey, message))
|
||||
}
|
||||
|
||||
fun sendGroupMessage(group: Group, message: Message): Unit {
|
||||
TODO()
|
||||
//sendPacket(ClientSendGroupMessagePacket(group.groupId, robot.account.qqNumber, sessionKey, message))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 动作: 获取好友列表, 点赞, 踢人等.
|
||||
* 处理动作事件, 承担动作任务.
|
||||
*/
|
||||
inner class ActionHandler : PacketHandler() {
|
||||
internal lateinit var cookies: String
|
||||
internal lateinit var sKey: String
|
||||
internal var gtk: Int = 0
|
||||
|
||||
override fun onPacketReceived(packet: ServerPacket) {
|
||||
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package net.mamoe.mirai.network.packet
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.utils.TEACryptor
|
||||
import net.mamoe.mirai.utils.TEA
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
@ -12,13 +12,14 @@ import java.io.DataInputStream
|
||||
@ExperimentalUnsignedTypes
|
||||
@PacketId("00 5C")
|
||||
class ClientAccountInfoRequestPacket(
|
||||
private val qq: Int,
|
||||
private val qq: Long,
|
||||
private val sessionKey: ByteArray
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.writeRandom(2)//part of packet id
|
||||
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol._fixVer)
|
||||
this.writeHex(Protocol.fixVer2)
|
||||
this.encryptAndWrite(sessionKey) {
|
||||
it.writeByte(0x88)
|
||||
it.writeQQ(qq)
|
||||
@ -27,6 +28,7 @@ class ClientAccountInfoRequestPacket(
|
||||
}
|
||||
}
|
||||
|
||||
@PacketId("00 5C")
|
||||
class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(input) {
|
||||
//等级
|
||||
//升级剩余活跃天数
|
||||
@ -34,16 +36,13 @@ class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(inp
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class ServerAccountInfoResponsePacketEncrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
|
||||
fun decrypt(sessionKey: ByteArray): ServerAccountInfoResponsePacket {
|
||||
this.input goto 14
|
||||
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
|
||||
return ServerAccountInfoResponsePacket(TEACryptor.decrypt(data, sessionKey).dataInputStream());
|
||||
@PacketId("00 5C")
|
||||
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
|
||||
fun decrypt(sessionKey: ByteArray): ServerAccountInfoResponsePacket {
|
||||
this.input goto 14
|
||||
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
|
||||
return ServerAccountInfoResponsePacket(TEA.decrypt(data, sessionKey).dataInputStream());
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package net.mamoe.mirai.network.packet
|
||||
|
||||
import lombok.Getter
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.util.TestedSuccessfully
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.io.DataOutputStream
|
||||
import java.io.IOException
|
||||
@ -15,11 +14,13 @@ import java.security.MessageDigest
|
||||
@ExperimentalUnsignedTypes
|
||||
abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
|
||||
@Getter
|
||||
val packageId: String
|
||||
val idHex: String
|
||||
|
||||
var encoded: Boolean = false
|
||||
|
||||
init {
|
||||
val annotation = this.javaClass.getAnnotation(PacketId::class.java)
|
||||
packageId = annotation.value
|
||||
idHex = annotation.value
|
||||
|
||||
try {
|
||||
this.writeHex(Protocol.head)
|
||||
@ -33,7 +34,7 @@ abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writePacketId() {
|
||||
this.writeHex(this@ClientPacket.packageId)
|
||||
this.writeHex(this@ClientPacket.idHex)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,11 +44,19 @@ abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
|
||||
* Before sending the packet, a [tail][Protocol.tail] will be added.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
abstract fun encode()
|
||||
protected abstract fun encode()
|
||||
|
||||
fun encodePacket() {
|
||||
if (encoded) {
|
||||
return
|
||||
}
|
||||
encode()
|
||||
writeHex(Protocol.tail)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun encodeToByteArray(): ByteArray {
|
||||
encode()
|
||||
encodePacket()
|
||||
return toByteArray()
|
||||
}
|
||||
|
||||
@ -89,73 +98,52 @@ fun DataOutputStream.writeHex(hex: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun DataOutputStream.writeVarInt(dec: UInt) {
|
||||
/*.判断开始 (n < 256)
|
||||
返回 (取文本右边 (“0” + 取十六进制文本 (n), 2))
|
||||
.判断 (n ≥ 256)
|
||||
hex = 取文本右边 (“0” + 取十六进制文本 (n), 4)
|
||||
返回 (取文本左边 (hex, 2) + “ ” + 取文本右边 (hex, 2))
|
||||
.默认
|
||||
返回 (“”)
|
||||
.判断结束*/
|
||||
|
||||
when {
|
||||
dec < 256u -> this.writeByte(dec.toByte().toInt())//drop other bits
|
||||
dec > 256u -> this.writeShort(dec.toShort().toInt())
|
||||
else -> throw IllegalArgumentException(dec.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, key: ByteArray) {
|
||||
this.write(TEACryptor.encrypt(byteArray, key))
|
||||
this.write(TEA.encrypt(byteArray, key))
|
||||
}
|
||||
|
||||
fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, cryptor: TEACryptor) {
|
||||
fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, cryptor: TEA) {
|
||||
this.write(cryptor.encrypt(byteArray))
|
||||
}
|
||||
|
||||
fun DataOutputStream.encryptAndWrite(key: ByteArray, encoder: (ByteArrayDataOutputStream) -> Unit) {
|
||||
this.write(TEACryptor.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }, key))
|
||||
this.write(TEA.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }, key))
|
||||
}
|
||||
|
||||
fun DataOutputStream.encryptAndWrite(cryptor: TEACryptor, encoder: (ByteArrayDataOutputStream) -> Unit) {
|
||||
@ExperimentalUnsignedTypes
|
||||
fun DataOutputStream.encryptAndWrite(keyHex: String, encoder: (ByteArrayDataOutputStream) -> Unit) {
|
||||
this.encryptAndWrite(keyHex.hexToBytes(), encoder)
|
||||
}
|
||||
|
||||
fun DataOutputStream.encryptAndWrite(cryptor: TEA, encoder: (ByteArrayDataOutputStream) -> Unit) {
|
||||
this.write(cryptor.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }))
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@Throws(IOException::class)
|
||||
fun DataOutputStream.writeTLV0006(qq: Int, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray) {
|
||||
fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray) {
|
||||
ByteArrayDataOutputStream().let {
|
||||
it.writeHex("12 12 12 12")//it.writeRandom(4) todo
|
||||
it.writeRandom(4)
|
||||
it.writeHex("00 02")
|
||||
it.writeQQ(qq)
|
||||
it.writeHex(Protocol._0825data2)
|
||||
it.writeHex(Protocol.constantData1)
|
||||
it.writeHex("00 00 01")
|
||||
|
||||
val md5_1 = md5(password);
|
||||
val md5_2 = md5(md5_1 + "00 00 00 00".hexToBytes() + qq.toByteArray())
|
||||
println(md5_1.toUByteArray().toUHexString())
|
||||
println(md5_2.toUByteArray().toUHexString())
|
||||
it.write(md5_1)
|
||||
val firstMD5 = md5(password)
|
||||
val secondMD5 = md5(firstMD5 + "00 00 00 00".hexToBytes() + qq.toUInt().toByteArray())
|
||||
it.write(firstMD5)
|
||||
it.writeInt(loginTime)
|
||||
it.writeByte(0);
|
||||
it.writeByte(0)
|
||||
it.writeZero(4 * 3)
|
||||
it.writeIP(loginIP)
|
||||
it.writeZero(8)
|
||||
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))
|
||||
this.write(TEA.encrypt(it.toByteArray(), secondMD5))
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@ExperimentalUnsignedTypes
|
||||
fun main() {
|
||||
@ -164,7 +152,7 @@ fun main() {
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@TestedSuccessfully
|
||||
fun DataOutputStream.writeCRC32() = writeCRC32(getRandomKey(16))
|
||||
fun DataOutputStream.writeCRC32() = writeCRC32(getRandomByteArray(16))
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@ -193,6 +181,17 @@ fun Int.toByteArray(): ByteArray = byteArrayOf(
|
||||
(this.ushr(0) and 0xFF).toByte()
|
||||
)
|
||||
|
||||
/**
|
||||
* 255u -> 00 00 00 FF
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
fun UInt.toByteArray(): ByteArray = byteArrayOf(
|
||||
(this.shr(24) and 255u).toByte(),
|
||||
(this.shr(16) and 255u).toByte(),
|
||||
(this.shr(8) and 255u).toByte(),
|
||||
(this.shr(0) and 255u).toByte()
|
||||
)
|
||||
|
||||
/**
|
||||
* 255 -> FF 00 00 00
|
||||
*/
|
||||
@ -204,7 +203,7 @@ fun Int.toLByteArray(): ByteArray = byteArrayOf(
|
||||
)
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun Int.toHexString(separator: String = " "): String = this.toByteArray().toUByteArray().toUHexString(separator);
|
||||
fun Int.toHexString(separator: String = " "): String = this.toByteArray().toUByteArray().toUHexString(separator)
|
||||
|
||||
internal fun md5(str: String): ByteArray = MessageDigest.getInstance("MD5").digest(str.toByteArray())
|
||||
|
||||
@ -225,7 +224,14 @@ fun DataOutputStream.writeRandom(length: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@Throws(IOException::class)
|
||||
fun DataOutputStream.writeQQ(qq: Int) {
|
||||
this.writeInt(qq)
|
||||
fun DataOutputStream.writeQQ(qq: Long) {
|
||||
this.write(qq.toUInt().toByteArray())
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@Throws(IOException::class)
|
||||
fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) {
|
||||
this.write(groupIdOrGroupNumber.toUInt().toByteArray())
|
||||
}
|
@ -10,7 +10,7 @@ import java.io.IOException
|
||||
@ExperimentalUnsignedTypes
|
||||
@PacketId("00 58")
|
||||
class ClientHeartbeatPacket(
|
||||
private val qq: Int,
|
||||
private val qq: Long,
|
||||
private val sessionKey: ByteArray
|
||||
) : ClientPacket() {
|
||||
@Throws(IOException::class)
|
||||
@ -24,8 +24,4 @@ class ClientHeartbeatPacket(
|
||||
}
|
||||
}
|
||||
|
||||
class ServerHeartbeatResponsePacket(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
}
|
||||
class ServerHeartbeatResponsePacket(input: DataInputStream) : ServerPacket(input)
|
@ -1,84 +0,0 @@
|
||||
package net.mamoe.mirai.network.packet
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.utils.TEACryptor
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import java.io.DataInputStream
|
||||
|
||||
|
||||
/**
|
||||
* 告知服务器已经收到数据
|
||||
*/
|
||||
@PacketId("")//随后写入
|
||||
@ExperimentalUnsignedTypes
|
||||
class ClientMessageResponsePacket(
|
||||
private val qq: Int,
|
||||
private val packetIdFromServer: ByteArray,
|
||||
private val sessionKey: ByteArray,
|
||||
private val eventIdentity: ByteArray
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.write(packetIdFromServer)
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol._fixVer)
|
||||
this.encryptAndWrite(sessionKey) {
|
||||
it.write(eventIdentity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 群聊和好友消息分发
|
||||
*/
|
||||
@PacketId("00 17")
|
||||
class ServerMessageEventPacketRaw(
|
||||
input: DataInputStream,
|
||||
private val dataLength: Int,
|
||||
private val packetId: ByteArray
|
||||
) : ServerPacket(input) {
|
||||
lateinit var type: ByteArray;
|
||||
lateinit var eventIdentity: ByteArray;
|
||||
|
||||
override fun decode() {
|
||||
eventIdentity = this.input.readNBytes(16)
|
||||
type = this.input.goto(18).readNBytes(2)
|
||||
}
|
||||
|
||||
fun analyze(): ServerEventPacket = when (val typeHex = type.toUHexString()) {
|
||||
"00 C4" -> {
|
||||
if (this.input.goto(33).readBoolean()) {
|
||||
ServerAndroidOnlineEventPacket(this.input, packetId, eventIdentity)
|
||||
} else {
|
||||
ServerAndroidOfflineEventPacket(this.input, packetId, eventIdentity)
|
||||
}
|
||||
}
|
||||
"00 2D" -> ServerGroupUploadFileEventPacket(this.input, packetId, eventIdentity)
|
||||
|
||||
"00 52" -> ServerGroupMessageEventPacket(this.input, packetId, eventIdentity)
|
||||
|
||||
"00 A6" -> ServerFriendMessageEventPacket(this.input, packetId, eventIdentity)
|
||||
|
||||
//"02 10", "00 12" -> ServerUnknownEventPacket(this.input, packetId, eventIdentity)
|
||||
|
||||
else -> UnknownServerEventPacket(this.input, packetId, eventIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
class UnknownServerEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity)
|
||||
|
||||
@PacketId("00 17")
|
||||
class ServerMessageEventPacketRawEncoded(input: DataInputStream, val packetId: ByteArray) : ServerPacket(input) {
|
||||
|
||||
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
|
||||
fun decrypt(sessionKey: ByteArray): ServerMessageEventPacketRaw {
|
||||
this.input goto 14
|
||||
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
|
||||
return ServerMessageEventPacketRaw(TEACryptor.decrypt(data, sessionKey).dataInputStream(), data.size, packetId);
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package net.mamoe.mirai.network.packet
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.utils.TEACryptor
|
||||
import net.mamoe.mirai.utils.TEA
|
||||
import java.io.DataInputStream
|
||||
|
||||
|
||||
@ -11,14 +11,14 @@ import java.io.DataInputStream
|
||||
@ExperimentalUnsignedTypes
|
||||
@PacketId("00 1D")
|
||||
class ClientSKeyRequestPacket(
|
||||
private val qq: Int,
|
||||
private val qq: Long,
|
||||
private val sessionKey: ByteArray
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.writeRandom(2)//part of packet id
|
||||
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol._fixVer)
|
||||
this.writeHex(Protocol.fixVer2)
|
||||
this.encryptAndWrite(sessionKey) {
|
||||
it.writeHex("33 00 05 00 08 74 2E 71 71 2E 63 6F 6D 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 0C 6A 75 62 61 6F 2E 71 71 2E 63 6F 6D 00 09 6B 65 2E 71 71 2E 63 6F 6D")
|
||||
}
|
||||
@ -31,7 +31,7 @@ class ClientSKeyRequestPacket(
|
||||
@PacketId("00 1D")
|
||||
@ExperimentalUnsignedTypes
|
||||
class ClientSKeyRefreshmentRequestPacket(
|
||||
private val qq: Int,
|
||||
private val qq: Long,
|
||||
private val sessionKey: ByteArray
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
@ -52,19 +52,13 @@ class ServerSKeyResponsePacket(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
this.sKey = String(this.input.goto(4).readNBytes(10))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
class ServerSKeyResponsePacketEncrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
|
||||
fun decrypt(sessionKey: ByteArray): ServerSKeyResponsePacket {
|
||||
this.input goto 14
|
||||
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
|
||||
return ServerSKeyResponsePacket(TEACryptor.decrypt(data, sessionKey).dataInputStream());
|
||||
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
|
||||
fun decrypt(sessionKey: ByteArray): ServerSKeyResponsePacket {
|
||||
this.input goto 14
|
||||
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
|
||||
return ServerSKeyResponsePacket(TEA.decrypt(data, sessionKey).dataInputStream());
|
||||
}
|
||||
}
|
||||
}
|
@ -2,28 +2,65 @@ package net.mamoe.mirai.network.packet
|
||||
|
||||
import net.mamoe.mirai.message.defaults.MessageChain
|
||||
import net.mamoe.mirai.message.defaults.PlainText
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.DataInputStream
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
/**
|
||||
* Packet id: `00 CE` or `00 17`
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
open class ServerEventPacket(input: DataInputStream, val packetId: ByteArray, val eventIdentity: ByteArray) : ServerPacket(input) {
|
||||
@PacketId("00 17")
|
||||
class Raw(input: DataInputStream, private val packetId: ByteArray) : ServerPacket(input) {
|
||||
@ExperimentalUnsignedTypes
|
||||
fun distribute(): ServerEventPacket {
|
||||
val eventIdentity = this.input.readNBytes(16)
|
||||
val type = this.input.goto(18).readNBytes(2)
|
||||
|
||||
override fun decode() {
|
||||
return when (type.toUHexString()) {
|
||||
"00 C4" -> {
|
||||
if (this.input.goto(33).readBoolean()) {
|
||||
ServerAndroidOnlineEventPacket(this.input, packetId, eventIdentity)
|
||||
} else {
|
||||
ServerAndroidOfflineEventPacket(this.input, packetId, eventIdentity)
|
||||
}
|
||||
}
|
||||
"00 2D" -> ServerGroupUploadFileEventPacket(this.input, packetId, eventIdentity)
|
||||
|
||||
"00 52" -> ServerGroupMessageEventPacket(this.input, packetId, eventIdentity)
|
||||
|
||||
"00 A6" -> ServerFriendMessageEventPacket(this.input, packetId, eventIdentity)
|
||||
|
||||
//"02 10", "00 12" -> ServerUnknownEventPacket(this.input, packetId, eventIdentity)
|
||||
|
||||
else -> UnknownServerEventPacket(this.input, packetId, eventIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
@PacketId("00 17")
|
||||
class Encrypted(input: DataInputStream, private val packetId: ByteArray) : ServerPacket(input) {
|
||||
fun decrypt(sessionKey: ByteArray): Raw = Raw(decryptBy(sessionKey), packetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown event
|
||||
*/
|
||||
class UnknownServerEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity)
|
||||
|
||||
/**
|
||||
* Android 客户端上线
|
||||
*/
|
||||
class ServerAndroidOnlineEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity)
|
||||
|
||||
/**
|
||||
* Android 客户端上线
|
||||
* Android 客户端下线
|
||||
*/
|
||||
class ServerAndroidOfflineEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity)
|
||||
|
||||
@ -31,7 +68,7 @@ class ServerAndroidOfflineEventPacket(input: DataInputStream, packetId: ByteArra
|
||||
* 群文件上传
|
||||
*/
|
||||
class ServerGroupUploadFileEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
|
||||
lateinit var xmlMessage: String
|
||||
private lateinit var xmlMessage: String
|
||||
|
||||
override fun decode() {
|
||||
xmlMessage = String(this.input.goto(65).readNBytes(this.input.goto(60).readShort().toInt()))
|
||||
@ -39,8 +76,8 @@ class ServerGroupUploadFileEventPacket(input: DataInputStream, packetId: ByteArr
|
||||
}
|
||||
|
||||
class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
|
||||
var groupNumber: Int = 0
|
||||
var qq: Int = 0
|
||||
var groupNumber: Long = 0
|
||||
var qq: Long = 0
|
||||
lateinit var message: String
|
||||
lateinit var messageType: MessageType
|
||||
|
||||
@ -58,9 +95,10 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
|
||||
OTHER,
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun decode() {
|
||||
groupNumber = this.input.goto(51).readInt()
|
||||
qq = this.input.goto(56).readInt()
|
||||
groupNumber = this.input.goto(51).readInt().toLong()
|
||||
qq = this.input.goto(56).readLong().toUInt().toLong()
|
||||
val fontLength = this.input.goto(108).readShort()
|
||||
//println(this.input.goto(110 + fontLength).readNBytesAt(2).toUHexString())//always 00 00
|
||||
|
||||
@ -76,7 +114,7 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
|
||||
25 -> MessageType.ANONYMOUS
|
||||
|
||||
else -> {
|
||||
println("id=$id")
|
||||
MiraiLogger debug ("ServerGroupMessageEventPacket id=$id")
|
||||
MessageType.OTHER
|
||||
}
|
||||
}
|
||||
@ -142,10 +180,9 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
|
||||
}
|
||||
|
||||
class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
|
||||
var qq: Int = 0
|
||||
var qq: Long = 0
|
||||
lateinit var message: MessageChain
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun decode() {
|
||||
//start at Sep1.0:27
|
||||
@ -153,7 +190,7 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
|
||||
println(input.readAllBytes().toUHexString())
|
||||
input.goto(0)
|
||||
|
||||
qq = input.readIntAt(0)
|
||||
qq = input.readIntAt(0).toLong()
|
||||
val msgLength = input.readShortAt(22)
|
||||
val fontLength = input.readShortAt(93 + msgLength)
|
||||
val offset = msgLength + fontLength
|
||||
@ -165,13 +202,42 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 告知服务器已经收到数据
|
||||
*/
|
||||
@PacketId("")//随后写入
|
||||
@ExperimentalUnsignedTypes
|
||||
class ClientMessageResponsePacket(
|
||||
private val qq: Long,
|
||||
private val packetIdFromServer: ByteArray,//4bytes
|
||||
private val sessionKey: ByteArray,
|
||||
private val eventIdentity: ByteArray
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.write(packetIdFromServer)//packet id 4bytes
|
||||
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol.fixVer2)
|
||||
this.encryptAndWrite(sessionKey) {
|
||||
it.write(eventIdentity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
3E 03 3F A2 76 E4 B8 DD 00 09 7C 3F 64 5C 2A 60 1F 40 00 A6 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 02 38 03 3E 03 3F A2 76 E4 B8 DD 01 10 9D D6 12 EA BC 07 91 EF DC 29 75 67 A9 1E 00 0B 2F E4 5D 6B A8 F6 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D 6B A8 F6 08 7E 90 CE 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
|
||||
3E 03 3F A2 76 E4 B8 DD 00 03 5F 85 64 5C 2A A4 1F 40 00 A6 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 02 38 03 3E 03 3F A2 76 E4 B8 DD 01 10 9D D6 12 EA BC 07 91 EF DC 29 75 67 A9 1E 00 0B 2F E5 5D 6B A9 16 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D 6B A9 17 1B B3 4D D7 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
|
||||
backup
|
||||
|
||||
class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
|
||||
var qq: Int = 0
|
||||
var qq: Long = 0
|
||||
lateinit var message: String
|
||||
|
||||
|
||||
|
@ -1,12 +1,9 @@
|
||||
package net.mamoe.mirai.network.packet
|
||||
|
||||
import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket
|
||||
import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket
|
||||
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.network.packet.verification.ServerVerificationCodePacketEncrypted
|
||||
import net.mamoe.mirai.utils.getAllDeclaredFields
|
||||
import net.mamoe.mirai.utils.hexToBytes
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
@ -14,7 +11,9 @@ import java.io.DataInputStream
|
||||
*/
|
||||
abstract class ServerPacket(val input: DataInputStream) : Packet {
|
||||
|
||||
abstract fun decode()
|
||||
open fun decode() {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@ -28,55 +27,56 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
|
||||
|
||||
|
||||
return when (val idHex = stream.readInt().toHexString(" ")) {
|
||||
"08 25 31 01" -> ServerTouchResponsePacketEncrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, stream)
|
||||
|
||||
"08 25 31 02" -> ServerTouchResponsePacketEncrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_02, stream)
|
||||
"08 25 31 01" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, stream)
|
||||
"08 25 31 02" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_02, stream)
|
||||
|
||||
"08 36 31 03", "08 36 31 04", "08 36 31 05", "08 36 31 06" -> {
|
||||
when (bytes.size) {
|
||||
271, 207 -> return ServerLoginResponseResendPacketEncrypted(stream, when (idHex) {
|
||||
271, 207 -> return ServerLoginResponseResendPacket.Encrypted(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)
|
||||
871 -> return ServerLoginResponseVerificationCodeInitPacket.Encrypted(stream)
|
||||
}
|
||||
|
||||
if (bytes.size > 700) {
|
||||
return ServerLoginResponseSuccessPacketEncrypted(stream)
|
||||
return ServerLoginResponseSuccessPacket.Encrypted(stream)
|
||||
}
|
||||
|
||||
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, 135 -> LoginState.WRONG_PASSWORD
|
||||
//135 -> LoginState.RETYPE_PASSWORD
|
||||
279 -> LoginState.BLOCKED
|
||||
263 -> LoginState.UNKNOWN_QQ_NUMBER
|
||||
551, 487 -> LoginState.DEVICE_LOCK
|
||||
359 -> LoginState.TAKEN_BACK
|
||||
|
||||
else -> LoginState.UNKNOWN
|
||||
/*
|
||||
//unknown
|
||||
63 -> throw IllegalArgumentException(bytes.size.toString() + " (Already logged in)")//可能是已经完成登录, 服务器拒绝第二次登录
|
||||
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())
|
||||
else -> throw IllegalArgumentException(bytes.size.toString())*/
|
||||
}, stream)
|
||||
}
|
||||
|
||||
"08 28 04 34" -> ServerSessionKeyResponsePacketEncrypted(stream)
|
||||
"08 28 04 34" -> ServerSessionKeyResponsePacket.Encrypted(stream)
|
||||
|
||||
|
||||
else -> when (idHex.substring(0, 5)) {
|
||||
"00 EC" -> ServerLoginSuccessPacket(stream)
|
||||
"00 1D" -> ServerSKeyResponsePacketEncrypted(stream)
|
||||
"00 5C" -> ServerAccountInfoResponsePacketEncrypted(stream)
|
||||
"00 1D" -> ServerSKeyResponsePacket.Encrypted(stream)
|
||||
"00 5C" -> ServerAccountInfoResponsePacket.Encrypted(stream)
|
||||
|
||||
"00 58" -> ServerHeartbeatResponsePacket(stream)
|
||||
|
||||
"00 BA" -> ServerVerificationCodePacketEncrypted(stream)
|
||||
"00 BA" -> ServerVerificationCodePacket.Encrypted(stream)
|
||||
|
||||
|
||||
"00 CE", "00 17" -> ServerMessageEventPacketRawEncoded(stream, idHex.hexToBytes())
|
||||
"00 CE", "00 17" -> ServerEventPacket.Raw.Encrypted(stream, idHex.hexToBytes())
|
||||
|
||||
"00 81" -> UnknownServerPacket(stream)
|
||||
|
||||
@ -89,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 ->
|
||||
@ -100,20 +101,19 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun DataInputStream.readUntil(byte: Byte): ByteArray {
|
||||
var buff = byteArrayOf()
|
||||
var b: Byte
|
||||
b = readByte()
|
||||
while (b != byte) {
|
||||
buff += b
|
||||
b = readByte()
|
||||
fun decryptBy(key: ByteArray): DataInputStream {
|
||||
input.goto(14)
|
||||
return DataInputStream(TEA.decrypt(input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }, key).inputStream())
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun decryptBy(keyHex: String): DataInputStream {
|
||||
return this.decryptBy(keyHex.hexToBytes())
|
||||
}
|
||||
return buff
|
||||
}
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun DataInputStream.readIP(): String {
|
||||
var buff = ""
|
||||
@ -150,6 +150,15 @@ fun <N : Number> DataInputStream.readNBytesAt(position: N, length: Int): ByteArr
|
||||
return this.readNBytes(length)
|
||||
}
|
||||
|
||||
fun <N : Number> DataInputStream.readNBytes(length: N): ByteArray {
|
||||
return this.readNBytes(length.toInt())
|
||||
}
|
||||
|
||||
fun DataInputStream.readNBytesIn(range: IntRange): ByteArray {
|
||||
this.goto(range.first)
|
||||
return this.readNBytes(range.last - range.first + 1)
|
||||
}
|
||||
|
||||
fun <N : Number> DataInputStream.readIntAt(position: N): Int {
|
||||
this.goto(position)
|
||||
return this.readInt();
|
||||
@ -163,4 +172,6 @@ fun <N : Number> DataInputStream.readByteAt(position: N): Byte {
|
||||
fun <N : Number> DataInputStream.readShortAt(position: N): Short {
|
||||
this.goto(position)
|
||||
return this.readShort();
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)
|
@ -2,8 +2,8 @@ package net.mamoe.mirai.network.packet
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.utils.ByteArrayDataOutputStream
|
||||
import net.mamoe.mirai.utils.TEACryptor
|
||||
import net.mamoe.mirai.utils.getRandomKey
|
||||
import net.mamoe.mirai.utils.TEA
|
||||
import net.mamoe.mirai.utils.getRandomByteArray
|
||||
import net.mamoe.mirai.utils.lazyEncode
|
||||
import java.io.DataInputStream
|
||||
import java.net.InetAddress
|
||||
@ -14,10 +14,8 @@ import java.net.InetAddress
|
||||
@ExperimentalUnsignedTypes
|
||||
@PacketId("08 28 04 34")
|
||||
class ClientSessionRequestPacket(
|
||||
private val qq: Int,
|
||||
private val qq: Long,
|
||||
private val serverIp: String,
|
||||
private val loginIP: String,
|
||||
private val md5_32: ByteArray,
|
||||
private val token38: ByteArray,
|
||||
private val token88: ByteArray,
|
||||
private val encryptionKey: ByteArray,
|
||||
@ -28,7 +26,7 @@ class ClientSessionRequestPacket(
|
||||
this.writeHex("02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A")
|
||||
this.writeHex("00 38")
|
||||
this.write(token38)
|
||||
this.write(TEACryptor.encrypt(object : ByteArrayDataOutputStream() {
|
||||
this.write(TEA.encrypt(object : ByteArrayDataOutputStream() {
|
||||
override fun toByteArray(): ByteArray {
|
||||
this.writeHex("00 07 00 88")
|
||||
this.write(token88)
|
||||
@ -36,22 +34,22 @@ class ClientSessionRequestPacket(
|
||||
this.writeIP(serverIp)
|
||||
this.writeHex("1F 40 00 00 00 00 00 15 00 30 00 01")//fix1
|
||||
this.writeHex("01 92 A5 D2 59 00 10 54 2D CF 9B 60 BF BB EC 0D D4 81 CE 36 87 DE 35 02 AE 6D ED DC 00 10 ")
|
||||
this.writeHex(Protocol._0836fix)
|
||||
this.writeHex(Protocol.fix0836)
|
||||
this.writeHex("00 36 00 12 00 02 00 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00")
|
||||
this.writeHex(Protocol._0825data0)
|
||||
this.writeHex(Protocol._0825data2)
|
||||
this.writeHex(Protocol.constantData0)
|
||||
this.writeHex(Protocol.constantData1)
|
||||
this.writeQQ(qq)
|
||||
this.writeHex("00 00 00 00 00 1F 00 22 00 01")
|
||||
this.writeHex("1A 68 73 66 E4 BA 79 92 CC C2 D4 EC 14 7C 8B AF 43 B0 62 FB 65 58 A9 EB 37 55 1D 26 13 A8 E5 3D")//device ID
|
||||
this.write(tlv0105)
|
||||
this.writeHex("01 0B 00 85 00 02")
|
||||
this.writeHex("B9 ED EF D7 CD E5 47 96 7A B5 28 34 CA 93 6B 5C")//fix2
|
||||
this.write(getRandomKey(1))
|
||||
this.write(getRandomByteArray(1))
|
||||
this.writeHex("10 00 00 00 00 00 00 00 02")
|
||||
|
||||
//fix3
|
||||
this.writeHex("00 63 3E 00 63 02 04 03 06 02 00 04 00 52 D9 00 00 00 00 A9 58 3E 6D 6D 49 AA F6 A6 D9 33 0A E7 7E 36 84 03 01 00 00 68 20 15 8B 00 00 01 02 00 00 03 00 07 DF 00 0A 00 0C 00 01 00 04 00 03 00 04 20 5C 00")
|
||||
this.write(md5_32)
|
||||
this.write(getRandomByteArray(32))//md5 32
|
||||
this.writeHex("68")
|
||||
|
||||
this.writeHex("00 00 00 00 00 2D 00 06 00 01")
|
||||
@ -64,8 +62,6 @@ class ClientSessionRequestPacket(
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose_0828
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val dataLength: Int) : ServerPacket(inputStream) {
|
||||
@ -106,16 +102,12 @@ class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val d
|
||||
//tlv0105 = "01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00" + 取文本中间(data, 取文本长度(data) - 367, 167) + “00 40 02 02 03 3C 01 03 00 00 ” + 取文本中间 (data, 取文本长度 (data) - 166, 167)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class ServerSessionKeyResponsePacketEncrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
|
||||
fun decrypt(_0828_rec_decr_key: ByteArray): ServerSessionKeyResponsePacket {
|
||||
this.input goto 14
|
||||
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
|
||||
return ServerSessionKeyResponsePacket(TEACryptor.decrypt(data, _0828_rec_decr_key).dataInputStream(), data.size);
|
||||
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
|
||||
fun decrypt(sessionResponseDecryptionKey: ByteArray): ServerSessionKeyResponsePacket {
|
||||
this.input goto 14
|
||||
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
|
||||
return ServerSessionKeyResponsePacket(TEA.decrypt(data, sessionResponseDecryptionKey).dataInputStream(), data.size)
|
||||
}
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
|
||||
|
||||
loginTime = input.readInt()
|
||||
loginIP = input.readIP()
|
||||
tgtgtKey = getRandomKey(16)
|
||||
tgtgtKey = getRandomByteArray(16)
|
||||
}
|
||||
|
||||
else -> {
|
||||
@ -48,24 +48,13 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ServerTouchResponsePacketEncrypted(private val type: ServerTouchResponsePacket.Type, inputStream: DataInputStream) : ServerPacket(inputStream) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun decrypt(): ServerTouchResponsePacket {
|
||||
input.skip(7)
|
||||
var bytes = input.readAllBytes();
|
||||
bytes = bytes.copyOfRange(0, bytes.size - 1);
|
||||
println(bytes.toUByteArray().toUHexString())
|
||||
|
||||
return ServerTouchResponsePacket(DataInputStream(TEACryptor.decrypt(bytes, when (type) {
|
||||
ServerTouchResponsePacket.Type.TYPE_08_25_31_02 -> Protocol.redirectionKey.hexToBytes()
|
||||
ServerTouchResponsePacket.Type.TYPE_08_25_31_01 -> Protocol._0825key.hexToBytes()
|
||||
}).inputStream()));
|
||||
class Encrypted(private val type: Type, inputStream: DataInputStream) : ServerPacket(inputStream) {
|
||||
@ExperimentalUnsignedTypes
|
||||
fun decrypt(): ServerTouchResponsePacket = ServerTouchResponsePacket(decryptBy(when (type) {
|
||||
Type.TYPE_08_25_31_02 -> Protocol.redirectionKey.hexToBytes()
|
||||
Type.TYPE_08_25_31_01 -> Protocol.key0825.hexToBytes()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,19 +65,19 @@ class ServerTouchResponsePacketEncrypted(private val type: ServerTouchResponsePa
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
@PacketId("08 25 31 01")
|
||||
class ClientTouchPacket(val qq: Int, val serverIp: String) : ClientPacket() {
|
||||
class ClientTouchPacket(val qq: Long, val serverIp: String) : ClientPacket() {
|
||||
@ExperimentalUnsignedTypes
|
||||
@Throws(IOException::class)
|
||||
override fun encode() {
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol.fixVer)
|
||||
this.writeHex(Protocol._0825key)
|
||||
this.writeHex(Protocol.key0825)
|
||||
|
||||
this.write(TEACryptor.CRYPTOR_0825KEY.encrypt(object : ByteArrayDataOutputStream() {
|
||||
this.write(TEA.CRYPTOR_0825KEY.encrypt(object : ByteArrayDataOutputStream() {
|
||||
@Throws(IOException::class)
|
||||
override fun toByteArray(): ByteArray {
|
||||
this.writeHex(Protocol._0825data0)
|
||||
this.writeHex(Protocol._0825data2)
|
||||
this.writeHex(Protocol.constantData0)
|
||||
this.writeHex(Protocol.constantData1)
|
||||
this.writeQQ(qq)
|
||||
this.writeHex("00 00 00 00 03 09 00 08 00 01")
|
||||
this.writeIP(serverIp);
|
||||
@ -108,7 +97,7 @@ class ClientTouchPacket(val qq: Int, val serverIp: String) : ClientPacket() {
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
@PacketId("08 25 31 02")
|
||||
class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Int) : ClientPacket() {
|
||||
class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() {
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun encode() {
|
||||
this.writeQQ(qq)
|
||||
@ -116,11 +105,11 @@ class ClientServerRedirectionPacket(private val serverIP: String, private val qq
|
||||
this.writeHex(Protocol.redirectionKey)
|
||||
|
||||
|
||||
this.write(TEACryptor.encrypt(object : ByteArrayDataOutputStream() {
|
||||
this.write(TEA.encrypt(object : ByteArrayDataOutputStream() {
|
||||
@Throws(IOException::class)
|
||||
override fun toByteArray(): ByteArray {
|
||||
this.writeHex(Protocol._0825data0)
|
||||
this.writeHex(Protocol._0825data2)
|
||||
this.writeHex(Protocol.constantData0)
|
||||
this.writeHex(Protocol.constantData1)
|
||||
this.writeQQ(qq)
|
||||
this.writeHex("00 01 00 00 03 09 00 0C 00 01")
|
||||
this.writeIP(serverIP)
|
||||
|
@ -0,0 +1,103 @@
|
||||
package net.mamoe.mirai.network.packet
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
* 客户端请求验证码图片数据的第几部分
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
@PacketId("00 BA 31")
|
||||
class ClientVerificationCodeTransmissionRequestPacket(
|
||||
private val count: Int,
|
||||
private val qq: Long,
|
||||
private val token0825: ByteArray,
|
||||
private val verificationSequence: Int,
|
||||
private val token00BA: ByteArray
|
||||
) : ClientPacket() {
|
||||
@TestedSuccessfully
|
||||
override fun encode() {
|
||||
this.writeByte(count)//part of packet id
|
||||
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol.fixVer2)
|
||||
this.writeHex(Protocol.key00BA)
|
||||
this.encryptAndWrite(Protocol.key00BA) {
|
||||
it.writeHex("00 02 00 00 08 04 01 E0")
|
||||
it.writeHex(Protocol.constantData1)
|
||||
it.writeHex("00 00 38")
|
||||
it.write(token0825)
|
||||
it.writeHex("01 03 00 19")
|
||||
it.writeHex(Protocol.publicKey)
|
||||
it.writeHex("13 00 05 00 00 00 00")
|
||||
it.writeByte(verificationSequence)
|
||||
it.writeHex("00 28")
|
||||
it.write(token00BA)
|
||||
it.writeHex("00 10")
|
||||
it.writeHex(Protocol.key00BAFix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器发送验证码图片文件一部分过来
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
class ServerVerificationCodeTransmissionPacket(input: DataInputStream, private val dataSize: Int, private val packetId: ByteArray) : ServerVerificationCodePacket(input) {
|
||||
|
||||
lateinit var verificationCodePartN: ByteArray
|
||||
lateinit var verificationToken: ByteArray//56bytes
|
||||
var transmissionCompleted: Boolean = false//验证码是否已经传输完成
|
||||
lateinit var token00BA: ByteArray//40 bytes
|
||||
var count: Int = 0
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun decode() {
|
||||
this.verificationToken = this.input.readNBytesAt(10, 56)
|
||||
|
||||
val length = this.input.readShortAt(66)
|
||||
this.verificationCodePartN = this.input.readNBytes(length)
|
||||
|
||||
this.input.skip(2)
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂不了解意义
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
class ServerVerificationCodeRepeatPacket(input: DataInputStream) : ServerVerificationCodePacket(input) {
|
||||
|
||||
lateinit var token00BA: ByteArray//56 bytes
|
||||
lateinit var tgtgtKeyUpdate: ByteArray
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun decode() {
|
||||
token00BA = this.input.readNBytesAt(10, 56)
|
||||
tgtgtKeyUpdate = getRandomByteArray(16)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ServerVerificationCodePacket(input: DataInputStream) : ServerPacket(input) {
|
||||
|
||||
@PacketId("00 BA")
|
||||
class Encrypted(input: DataInputStream) : ServerPacket(input) {
|
||||
@ExperimentalUnsignedTypes
|
||||
fun decrypt(): ServerVerificationCodePacket {
|
||||
this.input goto 14
|
||||
val data = TEA.decrypt(this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }, Protocol.key00BA.hexToBytes())
|
||||
return if (data.size == 95) {
|
||||
ServerVerificationCodeRepeatPacket(data.dataInputStream())
|
||||
} else {
|
||||
ServerVerificationCodeTransmissionPacket(data.dataInputStream(), data.size, this.input.readNBytesAt(3, 4))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.mamoe.mirai.network.packet.message
|
||||
package net.mamoe.mirai.network.packet.action
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
@ -11,15 +11,15 @@ import java.io.DataInputStream
|
||||
@PacketId("00 CD")
|
||||
@ExperimentalUnsignedTypes
|
||||
class ClientSendFriendMessagePacket(
|
||||
val robotQQ: Int,
|
||||
val targetQQ: Int,
|
||||
val sessionKey: ByteArray,
|
||||
val message: String
|
||||
private val robotQQ: Long,
|
||||
private val targetQQ: Long,
|
||||
private val sessionKey: ByteArray,
|
||||
private val message: String
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.writeRandom(2)//part of packet id
|
||||
this.writeQQ(robotQQ)
|
||||
this.writeHex(Protocol._fixVer)
|
||||
this.writeHex(Protocol.fixVer2)
|
||||
|
||||
this.encryptAndWrite(sessionKey) {
|
||||
it.writeQQ(robotQQ)
|
||||
@ -52,13 +52,10 @@ class ClientSendFriendMessagePacket(
|
||||
it.writeByte(0x01)
|
||||
it.writeShort(bytes.size)
|
||||
it.write(bytes)
|
||||
}//todo check
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PacketId("00 CD")
|
||||
class ServerSendFriendMessageResponsePacket(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
}
|
||||
}
|
||||
class ServerSendFriendMessageResponsePacket(input: DataInputStream) : ServerPacket(input)
|
@ -1,8 +1,7 @@
|
||||
package net.mamoe.mirai.network.packet.message
|
||||
package net.mamoe.mirai.network.packet.action
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.utils.lazyEncode
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import java.io.DataInputStream
|
||||
|
||||
@ -12,20 +11,20 @@ import java.io.DataInputStream
|
||||
@PacketId("00 02")
|
||||
@ExperimentalUnsignedTypes
|
||||
class ClientSendGroupMessagePacket(
|
||||
private val groupId: Int,//不是 number
|
||||
private val qq: Int,
|
||||
private val groupId: Long,//不是 number
|
||||
private val robotQQ: Long,
|
||||
private val sessionKey: ByteArray,
|
||||
private val message: String
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.writeRandom(2)//part of packet id
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol._fixVer)
|
||||
this.writeQQ(robotQQ)
|
||||
this.writeHex(Protocol.fixVer2)
|
||||
|
||||
this.encryptAndWrite(sessionKey) {
|
||||
val bytes = message.toByteArray()
|
||||
it.writeByte(0x2A)
|
||||
it.writeInt(groupId)
|
||||
it.writeGroup(groupId)
|
||||
it.writeShort(56 + bytes.size)
|
||||
|
||||
it.writeHex("00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
|
||||
@ -46,24 +45,5 @@ class ClientSendGroupMessagePacket(
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
println(lazyEncode {
|
||||
val bytes = "message".toByteArray()
|
||||
it.writeByte(0x2A)
|
||||
it.writeInt(580266363)
|
||||
it.writeShort(19 + bytes.size)
|
||||
|
||||
it.writeByte(0x01)
|
||||
it.writeByte(0x01)
|
||||
it.writeShort(bytes.size + 3)
|
||||
it.writeByte(0x01)
|
||||
it.writeShort(bytes.size)
|
||||
it.write(bytes)
|
||||
}.toUHexString())
|
||||
}
|
||||
|
||||
@PacketId("00 02")
|
||||
class ServerSendGroupMessageResponsePacket(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
}
|
||||
}
|
||||
class ServerSendGroupMessageResponsePacket(input: DataInputStream) : ServerPacket(input)
|
@ -5,12 +5,14 @@ import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.utils.ClientLoginStatus
|
||||
|
||||
/**
|
||||
* 改变在线状态: "我在线上", "隐身" 等
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
@PacketId("00 EC")
|
||||
class ClientLoginStatusPacket(
|
||||
private val qq: Int,
|
||||
class ClientChangeOnlineStatusPacket(
|
||||
private val qq: Long,
|
||||
private val sessionKey: ByteArray,
|
||||
private val loginStatus: ClientLoginStatus
|
||||
|
||||
@ -18,7 +20,7 @@ class ClientLoginStatusPacket(
|
||||
override fun encode() {
|
||||
this.writeRandom(2)//part of packet id
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol._fixVer)
|
||||
this.writeHex(Protocol.fixVer2)
|
||||
this.encryptAndWrite(sessionKey) {
|
||||
it.writeHex("01 00")
|
||||
it.writeByte(loginStatus.id)
|
@ -2,11 +2,7 @@ package net.mamoe.mirai.network.packet.login
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.util.TestedSuccessfully
|
||||
import net.mamoe.mirai.utils.ByteArrayDataOutputStream
|
||||
import net.mamoe.mirai.utils.TEACryptor
|
||||
import net.mamoe.mirai.utils.hexToBytes
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.io.DataOutputStream
|
||||
|
||||
/**
|
||||
@ -18,7 +14,7 @@ import java.io.DataOutputStream
|
||||
@ExperimentalUnsignedTypes
|
||||
@TestedSuccessfully
|
||||
class ClientPasswordSubmissionPacket(
|
||||
private val qq: Int,
|
||||
private val qq: Long,
|
||||
private val password: String,
|
||||
private val loginTime: Int,
|
||||
private val loginIP: String,
|
||||
@ -28,10 +24,10 @@ class ClientPasswordSubmissionPacket(
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun encode() {
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol._0836_622_fix1)
|
||||
this.writeHex(Protocol.passwordSubmissionKey1)
|
||||
this.writeHex(Protocol.publicKey)
|
||||
this.writeHex("00 00 00 10")
|
||||
this.writeHex(Protocol._0836key1)
|
||||
this.writeHex(Protocol.key0836)
|
||||
|
||||
this.encryptAndWrite(Protocol.shareKey.hexToBytes()) {
|
||||
it.writePart1(qq, password, loginTime, loginIP, tgtgtKey, token0825)
|
||||
@ -43,17 +39,22 @@ class ClientPasswordSubmissionPacket(
|
||||
|
||||
@PacketId("08 36 31 04")
|
||||
@ExperimentalUnsignedTypes
|
||||
class ClientLoginResendPacket3104(qq: Int, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv_0006_encr: ByteArray? = null)
|
||||
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv_0006_encr: ByteArray? = null)
|
||||
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv_0006_encr)
|
||||
|
||||
@PacketId("08 36 31 05")
|
||||
@ExperimentalUnsignedTypes
|
||||
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray)
|
||||
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, null)
|
||||
|
||||
@PacketId("08 36 31 06")
|
||||
@ExperimentalUnsignedTypes
|
||||
class ClientLoginResendPacket3106(qq: Int, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv_0006_encr: ByteArray? = null)
|
||||
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv_0006_encr: ByteArray? = null)
|
||||
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv_0006_encr)
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
open class ClientLoginResendPacket internal constructor(
|
||||
val qq: Int,
|
||||
val qq: Long,
|
||||
val password: String,
|
||||
val loginTime: Int,
|
||||
val loginIP: String,
|
||||
@ -64,12 +65,12 @@ open class ClientLoginResendPacket internal constructor(
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol._0836_622_fix1)
|
||||
this.writeHex(Protocol.passwordSubmissionKey1)
|
||||
this.writeHex(Protocol.publicKey)
|
||||
this.writeHex("00 00 00 10")
|
||||
this.writeHex(Protocol._0836key1)
|
||||
this.writeHex(Protocol.key0836)
|
||||
|
||||
this.write(TEACryptor.encrypt(object : ByteArrayDataOutputStream() {
|
||||
this.write(TEA.encrypt(object : ByteArrayDataOutputStream() {
|
||||
override fun toByteArray(): ByteArray {
|
||||
this.writePart1(qq, password, loginTime, loginIP, tgtgtKey, token0825, tlv_0006_encr)
|
||||
|
||||
@ -91,7 +92,7 @@ open class ClientLoginResendPacket internal constructor(
|
||||
* @author Him188moe
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
private fun DataOutputStream.writePart1(qq: Int, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, tlv_0006_encr: ByteArray? = null) {
|
||||
private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, tlv_0006_encr: ByteArray? = null) {
|
||||
|
||||
//this.writeInt(System.currentTimeMillis().toInt())
|
||||
this.writeHex("01 12")//tag
|
||||
@ -110,12 +111,12 @@ private fun DataOutputStream.writePart1(qq: Int, password: String, loginTime: In
|
||||
this.writeTLV0006(qq, password, loginTime, loginIP, tgtgtKey)
|
||||
}
|
||||
//fix
|
||||
this.writeHex(Protocol._0836_622_fix2)
|
||||
this.writeHex(Protocol.passwordSubmissionKey2)
|
||||
this.writeHex("00 1A")//tag
|
||||
this.writeHex("00 40")//length
|
||||
this.write(TEACryptor.encrypt(Protocol._0836_622_fix2.hexToBytes(), tgtgtKey))
|
||||
this.writeHex(Protocol._0825data0)
|
||||
this.writeHex(Protocol._0825data2)
|
||||
this.write(TEA.encrypt(Protocol.passwordSubmissionKey2.hexToBytes(), tgtgtKey))
|
||||
this.writeHex(Protocol.constantData0)
|
||||
this.writeHex(Protocol.constantData1)
|
||||
this.writeQQ(qq)
|
||||
this.writeZero(4)
|
||||
|
@ -1,41 +0,0 @@
|
||||
package net.mamoe.mirai.network.packet.login
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.utils.ByteArrayDataOutputStream
|
||||
import net.mamoe.mirai.utils.TEACryptor
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
@PacketId("00 BA 31 01")
|
||||
@ExperimentalUnsignedTypes
|
||||
class ClientLoginVerificationCodePacket(
|
||||
private val qq: Int,
|
||||
private val token0825: ByteArray,
|
||||
private val sequence: Int,
|
||||
private val token00BA: ByteArray
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.writeQQ(qq)
|
||||
this.writeHex(Protocol.fixVer)
|
||||
this.writeHex(Protocol._00BaKey)
|
||||
this.write(TEACryptor.CRYPTOR_00BAKEY.encrypt(object : ByteArrayDataOutputStream() {
|
||||
override fun toByteArray(): ByteArray {
|
||||
this.writeHex("00 02 00 00 08 04 01 E0")
|
||||
this.writeHex(Protocol._0825data2)
|
||||
this.writeHex("00 00 38")
|
||||
this.write(token0825)
|
||||
this.writeHex("01 03 00 19")
|
||||
this.writeHex(Protocol.publicKey)
|
||||
this.writeHex("13 00 05 00 00 00 00")
|
||||
this.writeVarInt(sequence.toUInt())
|
||||
this.writeHex("00 28")
|
||||
this.write(token00BA)
|
||||
this.writeHex("00 10")
|
||||
this.writeHex(Protocol._00BaFixKey)
|
||||
return super.toByteArray()
|
||||
}
|
||||
}.toByteArray()))
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package net.mamoe.mirai.network.packet.login
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
enum class LoginState {
|
||||
/**
|
||||
* 登录成功
|
||||
*/
|
||||
SUCCEED,
|
||||
|
||||
/**
|
||||
* 密码错误
|
||||
*/
|
||||
WRONG_PASSWORD,
|
||||
|
||||
/**
|
||||
* 被冻结
|
||||
*/
|
||||
BLOCKED,
|
||||
|
||||
/**
|
||||
* QQ 号码输入有误
|
||||
*/
|
||||
UNKNOWN_QQ_NUMBER,
|
||||
|
||||
/**
|
||||
* 账号开启了设备锁. 暂不支持设备锁登录
|
||||
*/
|
||||
DEVICE_LOCK,
|
||||
|
||||
/**
|
||||
* 账号被回收
|
||||
*/
|
||||
TAKEN_BACK,
|
||||
|
||||
/**
|
||||
* 需要验证码登录
|
||||
*/
|
||||
VERIFICATION_CODE,
|
||||
|
||||
/**
|
||||
* 未知. 更换服务器或等几分钟再登录可能解决.
|
||||
*/
|
||||
UNKNOWN,
|
||||
}
|
@ -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() {
|
||||
}
|
||||
}
|
@ -1,13 +1,9 @@
|
||||
package net.mamoe.mirai.network.packet.login
|
||||
|
||||
import net.mamoe.mirai.network.Protocol
|
||||
import net.mamoe.mirai.network.packet.ServerPacket
|
||||
import net.mamoe.mirai.network.packet.goto
|
||||
import net.mamoe.mirai.network.packet.readNBytesAt
|
||||
import net.mamoe.mirai.network.packet.readVarString
|
||||
import net.mamoe.mirai.util.TestedSuccessfully
|
||||
import net.mamoe.mirai.utils.TEACryptor
|
||||
import net.mamoe.mirai.utils.hexToBytes
|
||||
import net.mamoe.mirai.network.packet.*
|
||||
import net.mamoe.mirai.utils.TEA
|
||||
import net.mamoe.mirai.utils.TestedSuccessfully
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import java.io.DataInputStream
|
||||
|
||||
@ -15,8 +11,8 @@ import java.io.DataInputStream
|
||||
* @author NaturalHG
|
||||
*/
|
||||
class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(input) {
|
||||
lateinit var _0828_rec_decr_key: ByteArray//16 bytes|
|
||||
lateinit var nick: String
|
||||
lateinit var sessionResponseDecryptionKey: ByteArray//16 bytes|
|
||||
lateinit var nickname: String
|
||||
|
||||
lateinit var token38: ByteArray
|
||||
lateinit var token88: ByteArray
|
||||
@ -26,72 +22,6 @@ class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(in
|
||||
@TestedSuccessfully
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun decode() {
|
||||
//测试完成 @NaturalHG
|
||||
/**
|
||||
* Version 1 @Deprecated
|
||||
this.input.skip(7)//8
|
||||
|
||||
encryptionKey = this.input.readNBytesAt(16)//24
|
||||
|
||||
this.input.skip(2)//25->26
|
||||
|
||||
token38 = this.input.readNBytesAt(56)//81->82
|
||||
|
||||
this.input.skip(60L)//142
|
||||
|
||||
//??
|
||||
var b = this.input.readNBytesAt(2)
|
||||
val msgLength = when (b.toUByteArray().toUHexString()) {
|
||||
"01 07" -> 0
|
||||
"00 33" -> 28
|
||||
"01 10" -> 65
|
||||
else -> throw IllegalStateException()
|
||||
}//144
|
||||
|
||||
|
||||
System.out.println(msgLength)
|
||||
|
||||
this.input.skip(17L + msgLength)//161+msgLength
|
||||
|
||||
this.input.skip(10)//171+msgLength
|
||||
|
||||
_0828_rec_decr_key = this.input.readNBytesAt(16)//187+msgLength
|
||||
|
||||
|
||||
this.input.skip(2)
|
||||
|
||||
token88 = this.input.readNBytesAt(136)//325+ // msgLength
|
||||
|
||||
this.input.skip(299L)//624+msgLength
|
||||
|
||||
//varString (nickLength bytes)
|
||||
val nickLength = this.input.readByteAt().toInt()//625+msgLength
|
||||
|
||||
System.out.println(nickLength)
|
||||
|
||||
nick = this.input.readVarString(nickLength)//625+msgLength+nickLength
|
||||
|
||||
val dataIndex = packetDataLength - 31
|
||||
|
||||
/*
|
||||
this.input.skip((dataIndex - (625 + msgLength + nickLength)) + 0L)//-31
|
||||
|
||||
gender = this.input.readByteAt().toUByte().toInt()//-30
|
||||
|
||||
this.input.skip(9)//-27
|
||||
|
||||
age = this.input.readShortAt()//-25
|
||||
*/
|
||||
age = 0
|
||||
gender = 0
|
||||
|
||||
/*
|
||||
age = HexToDec(取文本中间(data, 取文本长度(data) - 82, 5))
|
||||
gender = 取文本中间(data, 取文本长度(data) - 94, 2)
|
||||
*/
|
||||
* **/
|
||||
/** version 2 */
|
||||
|
||||
this.input.skip(7)//8
|
||||
this.encryptionKey = this.input.readNBytes(16)//24
|
||||
|
||||
@ -106,32 +36,25 @@ class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(in
|
||||
else -> throw IllegalStateException(id)
|
||||
}
|
||||
|
||||
this._0828_rec_decr_key = this.input.readNBytesAt(171 + msgLength, 16)
|
||||
this.sessionResponseDecryptionKey = this.input.readNBytesAt(171 + msgLength, 16)
|
||||
|
||||
this.token88 = this.input.readNBytesAt(189 + msgLength, 136)
|
||||
|
||||
val nickLength = this.input.goto(624 + msgLength).readByte().toInt()
|
||||
this.nick = this.input.readVarString(nickLength)
|
||||
this.nickname = this.input.readVarString(nickLength)
|
||||
|
||||
//this.age = this.input.goto(packetDataLength - 28).readShortAt()
|
||||
|
||||
//this.gender = this.input.goto(packetDataLength - 32).readByteAt().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
class ServerLoginResponseSuccessPacketEncrypted(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
|
||||
class Encrypted(input: DataInputStream) : ServerPacket(input) {
|
||||
@ExperimentalUnsignedTypes
|
||||
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseSuccessPacket {
|
||||
input goto 14
|
||||
return ServerLoginResponseSuccessPacket(TEA.decrypt(TEA.decrypt(input.readAllBytes().cutTail(1), Protocol.shareKey), tgtgtKey).dataInputStream());
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseSuccessPacket {
|
||||
input goto 14
|
||||
var bytes = input.readAllBytes()
|
||||
bytes = bytes.copyOfRange(0, bytes.size - 1)
|
||||
println(bytes.toUByteArray().toUHexString())
|
||||
|
||||
return ServerLoginResponseSuccessPacket(DataInputStream(TEACryptor.decrypt(TEACryptor.decrypt(bytes, Protocol.shareKey.hexToBytes()), tgtgtKey).inputStream()));
|
||||
//TeaDecrypt(取文本中间(data, 43, 取文本长度(data) - 45), m_0828_rec_decr_key)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,12 +2,8 @@ package net.mamoe.mirai.network.packet.login
|
||||
|
||||
import net.mamoe.mirai.network.packet.PacketId
|
||||
import net.mamoe.mirai.network.packet.ServerPacket
|
||||
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 net.mamoe.mirai.utils.TestedSuccessfully
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
@ -45,33 +41,9 @@ class ServerLoginResponseResendPacket(input: DataInputStream, val flag: Flag) :
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ServerLoginResponseResendPacketEncrypted(input: DataInputStream, private val flag: ServerLoginResponseResendPacket.Flag) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
|
||||
@TestedSuccessfully
|
||||
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseResendPacket {
|
||||
//this.input.skip(7)
|
||||
this.input goto 14
|
||||
var data: ByteArray = this.input.readAllBytes()
|
||||
data = TEACryptor.CRYPTOR_SHARE_KEY.decrypt(data.let { it.copyOfRange(0, it.size - 1) });
|
||||
data = TEACryptor.decrypt(data, tgtgtKey)
|
||||
return ServerLoginResponseResendPacket(data.dataInputStream(), flag)
|
||||
class Encrypted(input: DataInputStream, private val flag: Flag) : ServerPacket(input) {
|
||||
@TestedSuccessfully
|
||||
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseResendPacket = ServerLoginResponseResendPacket(decryptBy(tgtgtKey), 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()) }
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package net.mamoe.mirai.network.packet.login
|
||||
|
||||
import net.mamoe.mirai.network.packet.ServerPacket
|
||||
import net.mamoe.mirai.network.packet.cutTail
|
||||
import net.mamoe.mirai.network.packet.dataInputStream
|
||||
import net.mamoe.mirai.network.packet.goto
|
||||
import net.mamoe.mirai.utils.TEA
|
||||
import net.mamoe.mirai.utils.TestedSuccessfully
|
||||
import net.mamoe.mirai.utils.hexToUBytes
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
* 收到这个包意味着需要验证码登录, 并且能得到验证码图片文件的一半
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, private val packetLength: Int) : ServerPacket(input) {
|
||||
|
||||
lateinit var verifyCodePart1: ByteArray
|
||||
lateinit var token00BA: ByteArray
|
||||
var unknownBoolean: Boolean? = null
|
||||
|
||||
|
||||
@TestedSuccessfully
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun decode() {
|
||||
val verifyCodeLength = this.input.goto(78).readShort()//2bytes
|
||||
this.verifyCodePart1 = this.input.readNBytes(verifyCodeLength.toInt())
|
||||
|
||||
this.input.skip(1)
|
||||
|
||||
this.unknownBoolean = this.input.readByte().toInt() == 1
|
||||
|
||||
this.token00BA = this.input.goto(packetLength - 60).readNBytes(40)
|
||||
}
|
||||
|
||||
|
||||
class Encrypted(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
|
||||
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket {
|
||||
this.input goto 14
|
||||
val data = TEA.CRYPTOR_SHARE_KEY.decrypt(this.input.readAllBytes().cutTail(1));
|
||||
return ServerLoginResponseVerificationCodeInitPacket(data.dataInputStream(), data.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val data = "FB 01 04 03 33 00 01 00 BA 02 03 2C 13 00 05 01 00 00 01 23 00 38 D5 01 05 8B 67 4D 52 5A FA 92 DB 99 18 D4 F0 72 03 E0 17 71 7C 8A 45 74 1F C3 2D F8 61 96 0D 93 0D 8C 51 95 70 F8 F9 CB B9 2D 5D BC 4F 5D 89 5F E7 59 8C E4 E5 A2 04 56 02 BC 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 00 82 00 00 00 35 08 03 00 00 00 BA 12 C3 02 00 00 00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 45 50 4C 54 45 FE F6 ED E2 F1 DF F3 FF F2 11 77 48 FE FE F3 F1 F9 EA D7 FD E7 F8 F9 EC FC EF E7 E8 FF EE 2D 69 48 2A 8A 5D 29 7A 52 F0 ED E1 A9 C7 B1 65 96 79 AB E0 C2 C3 F0 D5 42 7D 5C 4A 99 72 89 AA 93 51 73 5C 6E BA 94 42 BD 7A 0B 00 00 09 C5 49 44 41 54 58 C3 AC 99 8B 76 AB 3A 12 44 91 D0 1B 10 08 04 FF FF A9 B3 5B 60 C7 AF 38 77 EE 0C EB C4 76 6C 07 95 BA AB AB AB 75 BA EE 1F 5D DE CB 63 08 C1 C7 A8 AD BC B6 31 46 6B 3B A3 79 94 E7 F3 63 79 E4 D9 98 89 37 7D F7 FF BB 3C 10 82 6F B7 F7 2A B6 5B 07 EF B5 16 60 DE 1B 63 3A 1F 7E BE 6D 0C 50 EC 09 E1 FE 76 B8 3F FC 6B 0C 1D 5B 37 1F 3E E1 AE 61 9A D8 B6 07 93 B7 56 4D 66 22 06 9D FC EE 5F BF F9 3F 81 90 BB 99 F3 49 E2 1D DA 43 38 13 44 14 8C 9D B4 8E E7 65 40 11 E4 BB FE 8C 5B 78 41 F1 AF 01 48 2E 08 6D 8B AD F1 BA 05 F9 C4 E0 25 E6 31 BA EB 2A 90 C3 74 26 FC 1E 80 CF 14 91 44 7F FC 39 FF D4 37 10 82 C0 B3 B0 67 BF 2E 5E 5B F4 46 E5 3A 5F D7 BE A6 54 17 2D 7C 0D D7 DA E1 7E 93 C7 E5 DB 9E 6E 9F FD A3 14 9C 7F 23 97 55 FA DC 6F 74 8A CC 04 49 03 3F DE 4E 5C 4A 95 9C 53 8D 7A EA 82 F9 10 77 7F EE E5 43 28 C2 9F 00 BA 5B C0 59 A4 CE DB BA AE DB BC 14 D5 2A 82 A5 83 E0 30 92 19 A8 99 36 A7 15 7C BC DF F6 BE D7 86 C0 7F C9 C5 ED 93 C7 1F B9 E0 56 CB AC 61 F5 5D 2E 00 F0 93 D2 5A 9D B6 B0 3F 4B 4D 5C B9 0F C1 EE 29 46 65 4E 40 46 E2 E4 1B 91 C3 A5 2E FE D3 7A 3C 84 0B E3 F3 E2 57 16 2F 08 B9 E6 5A 73 29 65 29 4E B9 25 CF 6B 9A 97 48 20 B2 35 46 1B 6D AD D6 9A 57 69 45 B2 1A 00 90 05 51 8D C7 24 F8 1B B9 FE A6 41 B8 83 6C 59 05 84 9F 44 0F 85 05 9A 97 5A 97 BC A6 6D 81 FE 59 DE 2F 4B 5E E4 DF B2 A4 19 AA 06 D9 FE F9 33 4D 7E 6A 40 FC 97 34 BF 84 E4 81 81 ED E9 DC 85 32 56 47 E5 A4 F0 2D 6F 4D 2A BA 65 4B 73 89 B6 58 5E D7 35 8D 69 E4 4A 6B 76 50 C1 5C 3A D9 59 11 CF 37 99 FA 48 88 70 7F F4 9F 22 12 F2 24 91 3E 2B BF 28 A5 34 68 C0 50 A3 55 DD A4 E3 9C 6E 85 99 95 B6 24 2E 18 D9 3C 5C B1 4D AA 2F 08 E1 75 F1 F0 6B 49 FC BC E3 8D 00 01 00 28 42 E6 18 57 D4 B1 4D AE 51 27 D5 EF A2 38 91 39 15 37 6C 5A FE 75 93 49 DB FC 57 3C 12 3F 26 D9 16 1D 83 45 8B 78 39 D8 01 15 00 10 F6 F0 50 03 74 BB 18 91 D3 55 8D 7F BB 53 15 7A".hexToUBytes().toByteArray();
|
||||
ServerLoginResponseVerificationCodeInitPacket(
|
||||
data.dataInputStream(),
|
||||
data.size
|
||||
).let { it.decode(); println(it) }
|
||||
}
|
||||
|
||||
/*
|
||||
data
|
||||
FB 01 04 03 33 00 01 00 BA 02 03 2C 13 00 05 01 00 00 01 23 00 38 D5 01 05 8B 67 4D 52 5A FA 92 DB 99 18 D4 F0 72 03 E0 17 71 7C 8A 45 74 1F C3 2D F8 61 96 0D 93 0D 8C 51 95 70 F8 F9 CB B9 2D 5D BC 4F 5D 89 5F E7 59 8C E4 E5 A2 04 56 02 BC 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 00 82 00 00 00 35 08 03 00 00 00 BA 12 C3 02 00 00 00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 45 50 4C 54 45 FE F6 ED E2 F1 DF F3 FF F2 11 77 48 FE FE F3 F1 F9 EA D7 FD E7 F8 F9 EC FC EF E7 E8 FF EE 2D 69 48 2A 8A 5D 29 7A 52 F0 ED E1 A9 C7 B1 65 96 79 AB E0 C2 C3 F0 D5 42 7D 5C 4A 99 72 89 AA 93 51 73 5C 6E BA 94 42 BD 7A 0B 00 00 09 C5 49 44 41 54 58 C3 AC 99 8B 76 AB 3A 12 44 91 D0 1B 10 08 04 FF FF A9 B3 5B 60 C7 AF 38 77 EE 0C EB C4 76 6C 07 95 BA AB AB AB 75 BA EE 1F 5D DE CB 63 08 C1 C7 A8 AD BC B6 31 46 6B 3B A3 79 94 E7 F3 63 79 E4 D9 98 89 37 7D F7 FF BB 3C 10 82 6F B7 F7 2A B6 5B 07 EF B5 16 60 DE 1B 63 3A 1F 7E BE 6D 0C 50 EC 09 E1 FE 76 B8 3F FC 6B 0C 1D 5B 37 1F 3E E1 AE 61 9A D8 B6 07 93 B7 56 4D 66 22 06 9D FC EE 5F BF F9 3F 81 90 BB 99 F3 49 E2 1D DA 43 38 13 44 14 8C 9D B4 8E E7 65 40 11 E4 BB FE 8C 5B 78 41 F1 AF 01 48 2E 08 6D 8B AD F1 BA 05 F9 C4 E0 25 E6 31 BA EB 2A 90 C3 74 26 FC 1E 80 CF 14 91 44 7F FC 39 FF D4 37 10 82 C0 B3 B0 67 BF 2E 5E 5B F4 46 E5 3A 5F D7 BE A6 54 17 2D 7C 0D D7 DA E1 7E 93 C7 E5 DB 9E 6E 9F FD A3 14 9C 7F 23 97 55 FA DC 6F 74 8A CC 04 49 03 3F DE 4E 5C 4A 95 9C 53 8D 7A EA 82 F9 10 77 7F EE E5 43 28 C2 9F 00 BA 5B C0 59 A4 CE DB BA AE DB BC 14 D5 2A 82 A5 83 E0 30 92 19 A8 99 36 A7 15 7C BC DF F6 BE D7 86 C0 7F C9 C5 ED 93 C7 1F B9 E0 56 CB AC 61 F5 5D 2E 00 F0 93 D2 5A 9D B6 B0 3F 4B 4D 5C B9 0F C1 EE 29 46 65 4E 40 46 E2 E4 1B 91 C3 A5 2E FE D3 7A 3C 84 0B E3 F3 E2 57 16 2F 08 B9 E6 5A 73 29 65 29 4E B9 25 CF 6B 9A 97 48 20 B2 35 46 1B 6D AD D6 9A 57 69 45 B2 1A 00 90 05 51 8D C7 24 F8 1B B9 FE A6 41 B8 83 6C 59 05 84 9F 44 0F 85 05 9A 97 5A 97 BC A6 6D 81 FE 59 DE 2F 4B 5E E4 DF B2 A4 19 AA 06 D9 FE F9 33 4D 7E 6A 40 FC 97 34 BF 84 E4 81 81 ED E9 DC 85 32 56 47 E5 A4 F0 2D 6F 4D 2A BA 65 4B 73 89 B6 58 5E D7 35 8D 69 E4 4A 6B 76 50 C1 5C 3A D9 59 11 CF 37 99 FA 48 88 70 7F F4 9F 22 12 F2 24 91 3E 2B BF 28 A5 34 68 C0 50 A3 55 DD A4 E3 9C 6E 85 99 95 B6 24 2E 18 D9 3C 5C B1 4D AA 2F 08 E1 75 F1 F0 6B 49 FC BC E3 8D 00 01 00 28 42 E6 18 57 D4 B1 4D AE 51 27 D5 EF A2 38 91 39 15 37 6C 5A FE 75 93 49 DB FC 57 3C 12 3F 26 D9 16 1D 83 45 8B 78 39 D8 01 15 00 10 F6 F0 50 03 74 BB 18 91 D3 55 8D 7F BB 53 15 7A
|
||||
|
||||
length 700
|
||||
|
||||
verify code
|
||||
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 00 82 00 00 00 35 08 03 00 00 00 BA 12 C3 02 00 00 00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 45 50 4C 54 45 FE F6 ED E2 F1 DF F3 FF F2 11 77 48 FE FE F3 F1 F9 EA D7 FD E7 F8 F9 EC FC EF E7 E8 FF EE 2D 69 48 2A 8A 5D 29 7A 52 F0 ED E1 A9 C7 B1 65 96 79 AB E0 C2 C3 F0 D5 42 7D 5C 4A 99 72 89 AA 93 51 73 5C 6E BA 94 42 BD 7A 0B 00 00 09 C5 49 44 41 54 58 C3 AC 99 8B 76 AB 3A 12 44 91 D0 1B 10 08 04 FF FF A9 B3 5B 60 C7 AF 38 77 EE 0C EB C4 76 6C 07 95 BA AB AB AB 75 BA EE 1F 5D DE CB 63 08 C1 C7 A8 AD BC B6 31 46 6B 3B A3 79 94 E7 F3 63 79 E4 D9 98 89 37 7D F7 FF BB 3C 10 82 6F B7 F7 2A B6 5B 07 EF B5 16 60 DE 1B 63 3A 1F 7E BE 6D 0C 50 EC 09 E1 FE 76 B8 3F FC 6B 0C 1D 5B 37 1F 3E E1 AE 61 9A D8 B6 07 93 B7 56 4D 66 22 06 9D FC EE 5F BF F9 3F 81 90 BB 99 F3 49 E2 1D DA 43 38 13 44 14 8C 9D B4 8E E7 65 40 11 E4 BB FE 8C 5B 78 41 F1 AF 01 48 2E 08 6D 8B AD F1 BA 05 F9 C4 E0 25 E6 31 BA EB 2A 90 C3 74 26 FC 1E 80 CF 14 91 44 7F FC 39 FF D4 37 10 82 C0 B3 B0 67 BF 2E 5E 5B F4 46 E5 3A 5F D7 BE A6 54 17 2D 7C 0D D7 DA E1 7E 93 C7 E5 DB 9E 6E 9F FD A3 14 9C 7F 23 97 55 FA DC 6F 74 8A CC 04 49 03 3F DE 4E 5C 4A 95 9C 53 8D 7A EA 82 F9 10 77 7F EE E5 43 28 C2 9F 00 BA 5B C0 59 A4 CE DB BA AE DB BC 14 D5 2A 82 A5 83 E0 30 92 19 A8 99 36 A7 15 7C BC DF F6 BE D7 86 C0 7F C9 C5 ED 93 C7 1F B9 E0 56 CB AC 61 F5 5D 2E 00 F0 93 D2 5A 9D B6 B0 3F 4B 4D 5C B9 0F C1 EE 29 46 65 4E 40 46 E2 E4 1B 91 C3 A5 2E FE D3 7A 3C 84 0B E3 F3 E2 57 16 2F 08 B9 E6 5A 73 29 65 29 4E B9 25 CF 6B 9A 97 48 20 B2 35 46 1B 6D AD D6 9A 57 69 45 B2 1A 00 90 05 51 8D C7 24 F8 1B B9 FE A6 41 B8 83 6C 59 05 84 9F 44 0F 85 05 9A 97 5A 97 BC A6 6D 81 FE 59 DE 2F 4B 5E E4 DF B2 A4 19 AA 06 D9 FE F9 33 4D 7E 6A 40 FC 97 34 BF 84 E4 81 81 ED E9 DC 85 32 56 47 E5 A4 F0 2D 6F 4D 2A BA 65 4B 73 89 B6 58 5E D7 35 8D 69 E4 4A 6B 76 50 C1 5C 3A D9 59 11 CF 37 99 FA 48 88 70 7F F4 9F 22 12 F2 24 91 3E 2B BF 28 A5 34 68 C0 50 A3 55 DD A4 E3 9C 6E 85 99 95 B6 24 2E 18 D9 3C 5C B1 4D AA 2F 08 E1 75 F1 F0 6B 49 FC BC E3 8D
|
||||
|
||||
token00ba
|
||||
42 E6 18 57 D4 B1 4D AE 51 27 D5 EF A2 38 91 39 15 37 6C 5A FE 75 93 49 DB FC 57 3C 12 3F 26 D9 16 1D 83 45 8B 78 39 D8
|
||||
*/
|
@ -1,42 +0,0 @@
|
||||
package net.mamoe.mirai.network.packet.login
|
||||
|
||||
import net.mamoe.mirai.network.packet.ServerPacket
|
||||
import net.mamoe.mirai.network.packet.dataInputStream
|
||||
import net.mamoe.mirai.network.packet.goto
|
||||
import net.mamoe.mirai.utils.TEACryptor
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
class ServerLoginResponseVerificationCodePacket(input: DataInputStream, private val packetLength: Int) : ServerPacket(input) {
|
||||
|
||||
lateinit var verifyCode: ByteArray
|
||||
lateinit var token00BA: ByteArray
|
||||
var unknownBoolean: Boolean? = null
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun decode() {
|
||||
val verifyCodeLength = this.input.goto(78).readShort()//2bytes
|
||||
this.verifyCode = this.input.readNBytes(verifyCodeLength.toInt())
|
||||
|
||||
this.input.skip(1)
|
||||
|
||||
this.unknownBoolean = this.input.readByte().toInt() == 1
|
||||
|
||||
this.token00BA = this.input.goto(packetLength - 60).readNBytes(40)
|
||||
}
|
||||
}
|
||||
|
||||
class ServerLoginResponseVerificationCodePacketEncrypted(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
|
||||
fun decrypt(): ServerLoginResponseVerificationCodePacket {
|
||||
this.input goto 14
|
||||
val data = TEACryptor.CRYPTOR_SHARE_KEY.decrypt(this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) });
|
||||
return ServerLoginResponseVerificationCodePacket(data.dataInputStream(), data.size)
|
||||
}
|
||||
}
|
@ -4,10 +4,8 @@ import net.mamoe.mirai.network.packet.ServerPacket
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
* Congratulations!
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
class ServerLoginSuccessPacket(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
}
|
||||
class ServerLoginSuccessPacket(input: DataInputStream) : ServerPacket(input)
|
@ -1,44 +0,0 @@
|
||||
package net.mamoe.mirai.network.packet.verification
|
||||
|
||||
import net.mamoe.mirai.network.packet.ServerPacket
|
||||
import net.mamoe.mirai.network.packet.dataInputStream
|
||||
import net.mamoe.mirai.network.packet.goto
|
||||
import net.mamoe.mirai.utils.TEACryptor
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
class ServerVerificationCodePacket(input: DataInputStream) : ServerPacket(input) {
|
||||
|
||||
lateinit var verifyCode: ByteArray
|
||||
lateinit var verifyToken: ByteArray
|
||||
var unknownBoolean: Boolean? = null
|
||||
lateinit var token00BA: ByteArray
|
||||
var count: Int = 0
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun decode() {
|
||||
TODO()
|
||||
val verifyCodeLength = this.input.goto(78).readShort()//2bytes
|
||||
this.verifyCode = this.input.readNBytes(verifyCodeLength.toInt())
|
||||
|
||||
this.input.skip(1)
|
||||
|
||||
this.unknownBoolean = this.input.readByte().toInt() == 1
|
||||
|
||||
//this.token00BA = this.input.goto(packetLength - 60).readNBytesAt(40)
|
||||
}
|
||||
}
|
||||
|
||||
class ServerVerificationCodePacketEncrypted(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
|
||||
}
|
||||
|
||||
fun decrypt(token00BA: ByteArray): ServerVerificationCodePacket {
|
||||
this.input goto 14
|
||||
val data = TEACryptor.decrypt(token00BA, this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) });
|
||||
return ServerVerificationCodePacket(data.dataInputStream())
|
||||
}
|
||||
}
|
@ -1,4 +1,14 @@
|
||||
package net.mamoe.mirai.plugin;
|
||||
|
||||
public class MiraiPluginBase {
|
||||
import net.mamoe.mirai.Robot;
|
||||
|
||||
/**
|
||||
* 插件基类.
|
||||
* <p>
|
||||
* 插件属于整个 Mirai, 而不是属于单个 {@link Robot}.
|
||||
*
|
||||
* @see net.mamoe.mirai.event.MiraiEventManager
|
||||
* @see net.mamoe.mirai.event.MiraiEventManagerKt
|
||||
*/
|
||||
public abstract class MiraiPluginBase {
|
||||
}
|
||||
|
@ -4,15 +4,12 @@ package net.mamoe.mirai.task;
|
||||
public interface MiraiTaskExceptionHandler {
|
||||
void onHandle(Throwable e);
|
||||
|
||||
static MiraiTaskExceptionHandler byDefault(){
|
||||
return byPrint();
|
||||
}
|
||||
|
||||
static MiraiTaskExceptionHandler byIgnore(){
|
||||
return a -> {};
|
||||
}
|
||||
|
||||
static MiraiTaskExceptionHandler byPrint(){
|
||||
static MiraiTaskExceptionHandler printing() {
|
||||
return Throwable::printStackTrace;
|
||||
}
|
||||
|
||||
static MiraiTaskExceptionHandler ignoring() {
|
||||
return a -> {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package net.mamoe.mirai.task;
|
||||
|
||||
|
||||
import net.mamoe.mirai.event.MiraiEventHook;
|
||||
import net.mamoe.mirai.event.events.server.ServerDisableEvent;
|
||||
import net.mamoe.mirai.event.events.server.ServerDisabledEvent;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
@ -24,7 +24,7 @@ public final class MiraiTaskManager {
|
||||
this.pool = new MiraiThreadPool();
|
||||
|
||||
MiraiEventHook
|
||||
.onEvent(ServerDisableEvent.class)
|
||||
.onEvent(ServerDisabledEvent.class)
|
||||
.handler(a -> this.pool.close())
|
||||
.mount();
|
||||
|
||||
@ -35,7 +35,7 @@ public final class MiraiTaskManager {
|
||||
*/
|
||||
|
||||
public void execute(Runnable runnable) {
|
||||
this.execute(runnable, MiraiTaskExceptionHandler.byDefault());
|
||||
this.execute(runnable, MiraiTaskExceptionHandler.printing());
|
||||
}
|
||||
|
||||
public void execute(Runnable runnable, MiraiTaskExceptionHandler handler) {
|
||||
@ -51,7 +51,7 @@ public final class MiraiTaskManager {
|
||||
|
||||
|
||||
public <D> Future<D> submit(Callable<D> callable) {
|
||||
return this.submit(callable, MiraiTaskExceptionHandler.byDefault());
|
||||
return this.submit(callable, MiraiTaskExceptionHandler.printing());
|
||||
}
|
||||
|
||||
public <D> Future<D> submit(Callable<D> callable, MiraiTaskExceptionHandler handler) {
|
||||
@ -69,7 +69,7 @@ public final class MiraiTaskManager {
|
||||
* 异步任务
|
||||
*/
|
||||
public <D> void ansycTask(Callable<D> callable, Consumer<D> callback) {
|
||||
this.ansycTask(callable, callback, MiraiTaskExceptionHandler.byDefault());
|
||||
this.ansycTask(callable, callback, MiraiTaskExceptionHandler.printing());
|
||||
}
|
||||
|
||||
public <D> void ansycTask(Callable<D> callable, Consumer<D> callback, MiraiTaskExceptionHandler handler) {
|
||||
@ -87,7 +87,7 @@ public final class MiraiTaskManager {
|
||||
*/
|
||||
|
||||
public void repeatingTask(Runnable runnable, long intervalMillis) {
|
||||
this.repeatingTask(runnable, intervalMillis, MiraiTaskExceptionHandler.byDefault());
|
||||
this.repeatingTask(runnable, intervalMillis, MiraiTaskExceptionHandler.printing());
|
||||
}
|
||||
|
||||
public void repeatingTask(Runnable runnable, long intervalMillis, MiraiTaskExceptionHandler handler) {
|
||||
@ -95,7 +95,7 @@ public final class MiraiTaskManager {
|
||||
}
|
||||
|
||||
public void repeatingTask(Runnable runnable, long intervalMillis, int times) {
|
||||
this.repeatingTask(runnable, intervalMillis, times, MiraiTaskExceptionHandler.byDefault());
|
||||
this.repeatingTask(runnable, intervalMillis, times, MiraiTaskExceptionHandler.printing());
|
||||
}
|
||||
|
||||
public void repeatingTask(Runnable runnable, long intervalMillis, int times, MiraiTaskExceptionHandler handler) {
|
||||
|
@ -4,10 +4,14 @@ package net.mamoe.mirai.utils;
|
||||
* @author Him188moe
|
||||
*/
|
||||
public enum ClientLoginStatus {
|
||||
/**
|
||||
* 我在线上
|
||||
*/
|
||||
ONLINE(0x0A);
|
||||
|
||||
// TODO: 2019/8/31 add more
|
||||
public final int id;//1byte
|
||||
// TODO: 2019/8/31 add more ClientLoginStatus
|
||||
|
||||
public final int id;//1 ubyte
|
||||
|
||||
ClientLoginStatus(int id) {
|
||||
this.id = id;
|
||||
|
@ -6,5 +6,5 @@ import net.mamoe.mirai.utils.config.MiraiSynchronizedLinkedListMap;
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
public class ContactList<C extends Contact> extends MiraiSynchronizedLinkedListMap<Integer, C> {
|
||||
public class ContactList<C extends Contact> extends MiraiSynchronizedLinkedListMap<Long, C> {
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package net.mamoe.mirai.utils;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
@Data
|
||||
public final class RobotAccount {
|
||||
public final long qqNumber;
|
||||
public final String password;
|
||||
|
||||
public RobotAccount(long qqNumber, String password) {
|
||||
this.qqNumber = qqNumber;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
@ -6,12 +6,13 @@ import java.nio.ByteBuffer;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* TEA 加密
|
||||
*
|
||||
* @author iweiz https://github.com/iweizime/StepChanger/blob/master/app/src/main/java/me/iweizi/stepchanger/qq/Cryptor.java
|
||||
*/
|
||||
public class TEACryptor {
|
||||
public static final TEACryptor CRYPTOR_SHARE_KEY = new TEACryptor(Protocol.Companion.hexToBytes(Protocol.shareKey));
|
||||
public static final TEACryptor CRYPTOR_0825KEY = new TEACryptor(Protocol.Companion.hexToBytes(Protocol._0825key));
|
||||
public static final TEACryptor CRYPTOR_00BAKEY = new TEACryptor(Protocol.Companion.hexToBytes(Protocol._00BaKey));
|
||||
public final class TEA {
|
||||
public static final TEA CRYPTOR_SHARE_KEY = new TEA(Protocol.INSTANCE.hexToBytes(Protocol.shareKey));
|
||||
public static final TEA CRYPTOR_0825KEY = new TEA(Protocol.INSTANCE.hexToBytes(Protocol.key0825));
|
||||
|
||||
private static final long UINT32_MASK = 0xffffffffL;
|
||||
private final long[] mKey;
|
||||
@ -23,26 +24,33 @@ public class TEACryptor {
|
||||
private int mOutPos;
|
||||
private int mPreOutPos;
|
||||
private boolean isFirstBlock;
|
||||
private boolean isRand;
|
||||
|
||||
public TEACryptor(byte[] key) {
|
||||
public TEA(byte[] key) {
|
||||
mKey = new long[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
mKey[i] = pack(key, i * 4, 4);
|
||||
}
|
||||
isRand = true;
|
||||
mRandom = new Random();
|
||||
isFirstBlock = true;
|
||||
}
|
||||
|
||||
public static byte[] encrypt(byte[] source, byte[] key) {
|
||||
return new TEACryptor(key).encrypt(source);
|
||||
return new TEA(key).encrypt(source);
|
||||
}
|
||||
|
||||
public static byte[] encrypt(byte[] source, String keyHex) {
|
||||
return encrypt(source, UtilsKt.hexToBytes(keyHex));
|
||||
}
|
||||
|
||||
public static byte[] decrypt(byte[] source, byte[] key) {
|
||||
return new TEACryptor(key).decrypt(source);
|
||||
return new TEA(key).decrypt(source);
|
||||
}
|
||||
|
||||
public static byte[] decrypt(byte[] source, String keyHex) {
|
||||
return decrypt(source, UtilsKt.hexToBytes(keyHex));
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static long pack(byte[] bytes, int offset, int len) {
|
||||
long result = 0;
|
||||
int max_offset = len > 8 ? offset + 8 : offset + len;
|
||||
@ -53,11 +61,7 @@ public class TEACryptor {
|
||||
}
|
||||
|
||||
private int rand() {
|
||||
return isRand ? mRandom.nextInt() : 0xff00ff;
|
||||
}
|
||||
|
||||
public void enableRandom(boolean rand) {
|
||||
isRand = rand;
|
||||
return mRandom.nextInt();
|
||||
}
|
||||
|
||||
private byte[] encode(byte[] bytes) {
|
||||
@ -109,6 +113,7 @@ public class TEACryptor {
|
||||
isFirstBlock = false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean decodeOneBlock(byte[] ciphertext, int offset, int len) {
|
||||
for (mIndexPos = 0; mIndexPos < 8; mIndexPos++) {
|
||||
if (mOutPos + mIndexPos < len) {
|
||||
@ -125,6 +130,7 @@ public class TEACryptor {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private byte[] encrypt(byte[] plaintext, int offset, int len) {
|
||||
mInBlock = new byte[8];
|
||||
mIV = new byte[8];
|
||||
@ -175,11 +181,12 @@ public class TEACryptor {
|
||||
return mOutput;
|
||||
}
|
||||
|
||||
private byte[] decrypt(byte[] ciphertext, int offset, int len) {
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private byte[] decrypt(byte[] cipherText, int offset, int len) {
|
||||
if (len % 8 != 0 || len < 16) {
|
||||
throw new IllegalArgumentException("must len % 8 == 0 && len >= 16");
|
||||
}
|
||||
mIV = decode(ciphertext, offset);
|
||||
mIV = decode(cipherText, offset);
|
||||
mIndexPos = mIV[0] & 7;
|
||||
int plen = len - mIndexPos - 10;
|
||||
isFirstBlock = true;
|
||||
@ -198,7 +205,7 @@ public class TEACryptor {
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
isFirstBlock = false;
|
||||
if (!decodeOneBlock(ciphertext, offset, len)) {
|
||||
if (!decodeOneBlock(cipherText, offset, len)) {
|
||||
throw new RuntimeException("Unable to decode");
|
||||
}
|
||||
}
|
||||
@ -208,20 +215,20 @@ public class TEACryptor {
|
||||
if (mIndexPos < 8) {
|
||||
mOutput[outpos++] = isFirstBlock ?
|
||||
mIV[mIndexPos] :
|
||||
(byte) (ciphertext[mPreOutPos + offset + mIndexPos] ^ mIV[mIndexPos]);
|
||||
(byte) (cipherText[mPreOutPos + offset + mIndexPos] ^ mIV[mIndexPos]);
|
||||
++mIndexPos;
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
mPreOutPos = mOutPos - 8;
|
||||
isFirstBlock = false;
|
||||
if (!decodeOneBlock(ciphertext, offset, len)) {
|
||||
if (!decodeOneBlock(cipherText, offset, len)) {
|
||||
throw new RuntimeException("Unable to decode");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (g = 0; g < 7; g++) {
|
||||
if (mIndexPos < 8) {
|
||||
if ((ciphertext[mPreOutPos + offset + mIndexPos] ^ mIV[mIndexPos]) != 0) {
|
||||
if ((cipherText[mPreOutPos + offset + mIndexPos] ^ mIV[mIndexPos]) != 0) {
|
||||
throw new RuntimeException();
|
||||
} else {
|
||||
++mIndexPos;
|
||||
@ -230,7 +237,7 @@ public class TEACryptor {
|
||||
|
||||
if (mIndexPos == 8) {
|
||||
mPreOutPos = mOutPos;
|
||||
if (!decodeOneBlock(ciphertext, offset, len)) {
|
||||
if (!decodeOneBlock(cipherText, offset, len)) {
|
||||
throw new RuntimeException("Unable to decode");
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.mamoe.mirai.util
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
|
@ -8,7 +8,9 @@ import java.lang.reflect.Field
|
||||
import java.util.*
|
||||
import java.util.zip.CRC32
|
||||
|
||||
@JvmSynthetic
|
||||
fun ByteArray.toHexString(): String = toHexString(" ")
|
||||
|
||||
fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(separator) {
|
||||
var ret = it.toString(16).toUpperCase()
|
||||
if (ret.length == 1) {
|
||||
@ -21,18 +23,23 @@ fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(s
|
||||
fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator)
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@JvmSynthetic
|
||||
fun ByteArray.toUHexString(): String = this.toUByteArray().toUHexString()
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString(separator) {
|
||||
var ret = it.toString(16).toUpperCase()
|
||||
if (ret.length == 1) {
|
||||
ret = "0$ret"
|
||||
@JvmSynthetic
|
||||
fun UByteArray.toUHexString(separator: String = " "): String {
|
||||
return this.joinToString(separator) {
|
||||
var ret = it.toString(16).toUpperCase()
|
||||
if (ret.length == 1) {
|
||||
ret = "0$ret"
|
||||
}
|
||||
return@joinToString ret
|
||||
}
|
||||
return@joinToString ret
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@JvmSynthetic
|
||||
fun UByteArray.toUHexString(): String = this.toUHexString(" ")
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@ -51,7 +58,7 @@ fun String.hexToUBytes(): UByteArray = Protocol.hexToUBytes(this)
|
||||
fun String.hexToShort(): Short = hexToBytes().let { ((it[1].toInt() shl 8) + it[0]).toShort() }
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun String.hexToInt(): Int = hexToBytes().let { ((it[3].toInt() shl 24) + (it[2].toInt() shl 16) + (it[1].toInt() shl 8) + it[0]) }
|
||||
fun String.hexToInt(): Int = hexToBytes().let { ((it[0].toInt() shl 24) + (it[1].toInt() shl 16) + (it[2].toInt() shl 8) + it[3]) }
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun String.hexToByte(): Byte = hexToBytes()[0]
|
||||
@ -65,17 +72,18 @@ open class ByteArrayDataOutputStream : DataOutputStream(ByteArrayOutputStream())
|
||||
fun lazyEncode(t: (ByteArrayDataOutputStream) -> Unit): ByteArray = ByteArrayDataOutputStream().let { t(it); return it.toByteArray() }
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun getRandomKey(length: Int): ByteArray {
|
||||
fun getRandomByteArray(length: Int): ByteArray {
|
||||
val bytes = LinkedList<Byte>()
|
||||
repeat(length) { bytes.add((Math.random() * 255).toByte()) }
|
||||
return bytes.toByteArray()
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
operator fun File.plus(child: String): File = File(this, child)
|
||||
|
||||
private const val GTK_BASE_VALUE: Int = 5381
|
||||
|
||||
fun getGTK(sKey: String): Int {
|
||||
internal fun getGTK(sKey: String): Int {
|
||||
var value = GTK_BASE_VALUE
|
||||
for (c in sKey.toCharArray()) {
|
||||
value += (value shl 5) + c.toInt()
|
||||
@ -85,7 +93,7 @@ fun getGTK(sKey: String): Int {
|
||||
return value
|
||||
}
|
||||
|
||||
fun getCrc32(key: ByteArray): Int = CRC32().let { it.update(key); it.value.toInt() }
|
||||
internal fun getCrc32(key: ByteArray): Int = CRC32().let { it.update(key); it.value.toInt() }
|
||||
|
||||
|
||||
/**
|
||||
@ -118,3 +126,13 @@ fun Any.getAllDeclaredFields(): List<Field> {
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
private const val ZERO_BYTE: Byte = 0
|
||||
|
||||
fun ByteArray.removeZeroTail(): ByteArray {
|
||||
var i = this.size - 1
|
||||
while (this[i] == ZERO_BYTE) {
|
||||
--i
|
||||
}
|
||||
return this.copyOfRange(0, i + 1)
|
||||
}
|
@ -126,10 +126,10 @@ public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedListMap<String
|
||||
return result==null?defaultV:result.toString();
|
||||
}
|
||||
|
||||
public String getStringOrThrow(String key, Callable<Throwable> throwableCallable) throws Throwable {
|
||||
public String getStringOrThrow(String key, Supplier<Throwable> exceptionSupplier) throws Throwable {
|
||||
Object result = this.getOrDefault(key, null);
|
||||
if(result == null){
|
||||
throw throwableCallable.call();
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
@ -1,21 +1,22 @@
|
||||
package net.mamoe.mirai.utils.setting;
|
||||
|
||||
import net.mamoe.mirai.plugin.MiraiPluginBase;
|
||||
import org.ini4j.Config;
|
||||
import org.ini4j.Ini;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Mirai Config
|
||||
* Only support {INI} format
|
||||
* Support MAP and LIST
|
||||
* Thread safe
|
||||
* Thread-safe Mirai Config <br>
|
||||
* Only supports <code>INI</code> format <br>
|
||||
* Supports {@link Map} and {@link List}
|
||||
*/
|
||||
public class MiraiSetting {
|
||||
public class MiraiSettings {
|
||||
|
||||
private File file;
|
||||
|
||||
@ -23,14 +24,21 @@ public class MiraiSetting {
|
||||
|
||||
private volatile Map<String, MiraiSettingSection> cacheSection = new ConcurrentHashMap<>();
|
||||
|
||||
public MiraiSetting(File file){
|
||||
public MiraiSettings(MiraiPluginBase pluginBase, String filename) {
|
||||
// TODO: 2019/9/6 每个插件独立文件夹存放
|
||||
this(new File(filename));
|
||||
}
|
||||
|
||||
public MiraiSettings(File file) {
|
||||
if(!file.getName().contains(".")){
|
||||
file = new File(file.getParent() + file.getName() + ".ini");
|
||||
file = new File(file.getPath() + ".ini");
|
||||
}
|
||||
this.file = file;
|
||||
try {
|
||||
if(file.exists()){
|
||||
file.createNewFile();
|
||||
if (!file.createNewFile()) {
|
||||
throw new RuntimeException("cannot create config file " + file);
|
||||
}
|
||||
}
|
||||
Config config = new Config();
|
||||
config.setMultiSection(true);
|
||||
@ -42,12 +50,12 @@ public class MiraiSetting {
|
||||
}
|
||||
}
|
||||
|
||||
public void setSection(String key, MiraiSettingSection section){
|
||||
public synchronized void setSection(String key, MiraiSettingSection section) {
|
||||
cacheSection.put(key, section);
|
||||
}
|
||||
|
||||
|
||||
public MiraiSettingMapSection getMapSection(String key){
|
||||
public synchronized MiraiSettingMapSection getMapSection(String key) {
|
||||
if(!cacheSection.containsKey(key)) {
|
||||
MiraiSettingMapSection section = new MiraiSettingMapSection();
|
||||
if(ini.containsKey(key)){
|
||||
@ -58,7 +66,7 @@ public class MiraiSetting {
|
||||
return (MiraiSettingMapSection) cacheSection.get(key);
|
||||
}
|
||||
|
||||
public MiraiSettingListSection getListSection(String key){
|
||||
public synchronized MiraiSettingListSection getListSection(String key) {
|
||||
if(!cacheSection.containsKey(key)) {
|
||||
MiraiSettingListSection section = new MiraiSettingListSection();
|
||||
if(ini.containsKey(key)){
|
||||
@ -85,7 +93,7 @@ public class MiraiSetting {
|
||||
}
|
||||
}
|
||||
|
||||
public void clearCache(){
|
||||
public synchronized void clearCache() {
|
||||
cacheSection.clear();
|
||||
}
|
||||
}
|
129
mirai-core/src/test/java/BadQQFilter.kt
Normal file
129
mirai-core/src/test/java/BadQQFilter.kt
Normal file
@ -0,0 +1,129 @@
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mamoe.mirai.Robot
|
||||
import net.mamoe.mirai.network.packet.login.LoginState
|
||||
import net.mamoe.mirai.utils.RobotAccount
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
|
||||
val qqList = "2258868346----123456789.\n" +
|
||||
"1545483785----yuk7k1dxnf3jn5\n" +
|
||||
"2948786488----123123123\n" +
|
||||
"3059674084----qq123456\n" +
|
||||
"1918079979----123456789.\n" +
|
||||
"3050478794----18872590321\n" +
|
||||
"3331537204----123456789.\n" +
|
||||
"2128659972----123456789.\n" +
|
||||
"3435376516----abc123456\n" +
|
||||
"2980527804----a123456\n" +
|
||||
"2752195782----qq123456789\n" +
|
||||
"3130257966----13415986622\n" +
|
||||
"1802730396----123456789\n" +
|
||||
"3021732783----15866103923\n" +
|
||||
"306499606----abc123456\n" +
|
||||
"2893904328----abc123456\n" +
|
||||
"1765904806----123456789\n" +
|
||||
"3254202261----15223045268\n" +
|
||||
"2947707697----abc123456\n" +
|
||||
"3500959200----123456789.\n" +
|
||||
"2169513531----123456789.\n" +
|
||||
"2983688661----a123456\n" +
|
||||
"1246882194----pz49779866\n" +
|
||||
"2315275635----147258369\n" +
|
||||
"2802294904----123456789\n" +
|
||||
"2955364492----1234567890\n" +
|
||||
"1753325115----123456789\n" +
|
||||
"2642725191----qq123456\n" +
|
||||
"2152972686----123456789.\n" +
|
||||
"2845953617----123456789.\n" +
|
||||
"3329641753----123456789.\n" +
|
||||
"1458302685----123456789a\n" +
|
||||
"2351156352----987654321\n" +
|
||||
"2304786984----fkhwt53787\n" +
|
||||
"3322756212----123456789.\n" +
|
||||
"3187253283----123456789.\n" +
|
||||
"3168715730----147258369\n" +
|
||||
"2189916732----18831892323\n" +
|
||||
"2965337631----123456789.\n" +
|
||||
"1901802165----123456789.\n" +
|
||||
"414015319----abc123456\n" +
|
||||
"3400636089----123456789a\n" +
|
||||
"3530336304----seoua80060\n" +
|
||||
"3147312971----123456789.\n" +
|
||||
"3011083526----yp70y9\n" +
|
||||
"286888078----abc123456\n" +
|
||||
"3126754112----1234567890\n" +
|
||||
"2924643025----123123123\n" +
|
||||
"341870356----ncvhZtQD\n" +
|
||||
"3358177328----123456789a\n" +
|
||||
"1396419201----eakuj14475\n" +
|
||||
"3541159580----123456789.\n" +
|
||||
"2540245592----1234567890\n" +
|
||||
"2024802855----123456789.\n" +
|
||||
"2578309660----1234567890\n" +
|
||||
"1934965091----123456789.\n" +
|
||||
"3449408956----a123456789\n" +
|
||||
"2509348670----123456789.\n" +
|
||||
"2305961679----123456789.\n" +
|
||||
"3532858521----123456789.\n" +
|
||||
"3308276898----123456789a\n" +
|
||||
"1760897490----123456789\n" +
|
||||
"2920800012----123123123\n" +
|
||||
"2923942248----123123123\n" +
|
||||
"3216600579----13882755274\n" +
|
||||
"3100259299----qq123456\n" +
|
||||
"3242723735----1234567890\n" +
|
||||
"2142733062----123456789.\n" +
|
||||
"1557689693----123456789\n" +
|
||||
"3505693439----sb2662vqy6q\n" +
|
||||
"3231125974----123456789.\n" +
|
||||
"3433048975----13893690883\n" +
|
||||
"3168017129----18780999209\n" +
|
||||
"2922045831----123123123\n" +
|
||||
"3578152022----a123456789\n" +
|
||||
"2116254935----147258369\n" +
|
||||
"3158479284----1234567890\n" +
|
||||
"3149394424----qq123456789\n" +
|
||||
"2829521712----123456789.\n" +
|
||||
"3218671461----123456789.\n" +
|
||||
"3035873094----123456789a\n" +
|
||||
"2224518667----147258369\n" +
|
||||
"3175801590----123456789.\n" +
|
||||
"3203228181----123456789a\n" +
|
||||
"3213497536----123456789a\n" +
|
||||
"3377317115----123456789\n" +
|
||||
"2672537341----qq123456789\n" +
|
||||
"2945957617----123123123\n" +
|
||||
"2763390197----123456789.\n" +
|
||||
"3322711709----123456789."
|
||||
|
||||
|
||||
fun main() {
|
||||
val goodRobotList = Collections.synchronizedList(mutableListOf<Robot>())
|
||||
|
||||
qqList.split("\n").forEach {
|
||||
GlobalScope.launch {
|
||||
val strings = it.split("----")
|
||||
val robot = Robot(RobotAccount(strings[0].toLong(), strings[1].let { password ->
|
||||
if (password.endsWith(".")) {
|
||||
return@let password.substring(0, password.length - 1)
|
||||
}
|
||||
return@let password
|
||||
}), listOf())
|
||||
|
||||
robot.network.tryLogin().whenComplete { state, _ ->
|
||||
if (!(state == LoginState.BLOCKED || state == LoginState.DEVICE_LOCK || state == LoginState.WRONG_PASSWORD)) {
|
||||
goodRobotList.add(robot)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread.sleep(9 * 3000)
|
||||
|
||||
println(goodRobotList.joinToString("\n") { it.account.qqNumber.toString() + " " + it.account.password })
|
||||
}
|
||||
|
@ -3,11 +3,17 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import net.mamoe.mirai.network.Protocol;
|
||||
import net.mamoe.mirai.network.packet.ClientPacketKt;
|
||||
import net.mamoe.mirai.utils.UtilsKt;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
@ -32,15 +38,24 @@ public class HexComparator {
|
||||
|
||||
private static final String BLUE = "\033[34m";
|
||||
|
||||
public static final List<HexReader> consts = new LinkedList<>(){{
|
||||
public static final List<HexReader> consts = new LinkedList<>() {{
|
||||
add(new HexReader("90 5E 39 DF 00 02 76 E4 B8 DD 00"));
|
||||
}};
|
||||
|
||||
private static class ConstMatcher {
|
||||
private static final List<Field> CONST_FIELDS = new LinkedList<>() {{
|
||||
List.of(Protocol.class).forEach(aClass -> Arrays.stream(aClass.getDeclaredFields()).peek(this::add).forEach(Field::trySetAccessible));
|
||||
List.of(TestConsts.class).forEach(aClass -> Arrays.stream(aClass.getDeclaredFields()).peek(this::add).forEach(Field::trySetAccessible));
|
||||
}};
|
||||
|
||||
@SuppressWarnings({"unused", "NonAsciiCharacters"})
|
||||
private static class TestConsts {
|
||||
private static final String 牛逼 = UtilsKt.toUHexString("牛逼".getBytes(), " ");
|
||||
private static final String _1994701021 = ClientPacketKt.toHexString(1994701021, " ");
|
||||
private static final String _1040400290 = ClientPacketKt.toHexString(1040400290, " ");
|
||||
private static final String _580266363 = ClientPacketKt.toHexString(580266363, " ");
|
||||
}
|
||||
|
||||
private final List<Match> matches = new LinkedList<>();
|
||||
|
||||
private ConstMatcher(String hex) {
|
||||
@ -89,6 +104,23 @@ public class HexComparator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildConstNameChain(int length, ConstMatcher constMatcher, StringBuilder constNameBuilder) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
constNameBuilder.append(" ");
|
||||
String match = constMatcher.getMatchedConstName(i / 4);
|
||||
if (match != null) {
|
||||
int appendedNameLength = match.length();
|
||||
constNameBuilder.append(match);
|
||||
while (constMatcher.getMatchedConstName(i++ / 4) != null) {
|
||||
if (appendedNameLength-- <= 0) {
|
||||
constNameBuilder.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String compare(String hex1s, String hex2s) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
@ -105,10 +137,17 @@ public class HexComparator {
|
||||
|
||||
|
||||
StringBuilder numberLine = new StringBuilder();
|
||||
StringBuilder hex1ConstName = new StringBuilder();
|
||||
StringBuilder hex1b = new StringBuilder();
|
||||
StringBuilder hex2b = new StringBuilder();
|
||||
StringBuilder hex2ConstName = new StringBuilder();
|
||||
int dif = 0;
|
||||
|
||||
int length = Math.max(hex1.length, hex2.length) * 4;
|
||||
buildConstNameChain(length, constMatcher1, hex1ConstName);
|
||||
buildConstNameChain(length, constMatcher2, hex2ConstName);
|
||||
|
||||
|
||||
for (int i = 0; i < Math.max(hex1.length, hex2.length); ++i) {
|
||||
String h1 = null;
|
||||
String h2 = null;
|
||||
@ -152,7 +191,7 @@ public class HexComparator {
|
||||
}
|
||||
}
|
||||
|
||||
numberLine.append(UNKNOWN).append(getNumber(i)).append(" ");
|
||||
numberLine.append(UNKNOWN).append(getFixedNumber(i)).append(" ");
|
||||
hex1b.append(" ").append(h1).append(" ");
|
||||
hex2b.append(" ").append(h2).append(" ");
|
||||
if (isDif) {
|
||||
@ -165,42 +204,46 @@ public class HexComparator {
|
||||
|
||||
return (builder.append(" ").append(dif).append(" 个不同").append("\n")
|
||||
.append(numberLine).append("\n")
|
||||
.append(hex1ConstName).append("\n")
|
||||
.append(hex1b).append("\n")
|
||||
.append(hex2b))
|
||||
.append(hex2b).append("\n")
|
||||
.append(hex2ConstName).append("\n")
|
||||
)
|
||||
.toString();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static void doConstReplacement(StringBuilder builder){
|
||||
private static void doConstReplacement(StringBuilder builder) {
|
||||
String mirror = builder.toString();
|
||||
HexReader hexs = new HexReader(mirror);
|
||||
for (AtomicInteger i=new AtomicInteger(0);i.get()<builder.length();i.addAndGet(1)){
|
||||
for (AtomicInteger i = new AtomicInteger(0); i.get() < builder.length(); i.addAndGet(1)) {
|
||||
hexs.setTo(i.get());
|
||||
consts.forEach(a -> {
|
||||
hexs.setTo(i.get());
|
||||
List<Integer> posToPlaceColor = new LinkedList<>();
|
||||
AtomicBoolean is = new AtomicBoolean(false);
|
||||
|
||||
a.readFully((c,d) -> {
|
||||
if(c.equals(hexs.readHex())){
|
||||
a.readFully((c, d) -> {
|
||||
if (c.equals(hexs.readHex())) {
|
||||
posToPlaceColor.add(d);
|
||||
}else{
|
||||
} else {
|
||||
is.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
if(is.get()){
|
||||
if (is.get()) {
|
||||
AtomicInteger adder = new AtomicInteger();
|
||||
posToPlaceColor.forEach(e -> {
|
||||
builder.insert(e + adder.getAndAdd(BLUE.length()),BLUE);
|
||||
builder.insert(e + adder.getAndAdd(BLUE.length()), BLUE);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
private static String getNumber(int number) {
|
||||
|
||||
private static String getFixedNumber(int number) {
|
||||
if (number < 10) {
|
||||
return "00" + number;
|
||||
}
|
||||
@ -210,8 +253,31 @@ public class HexComparator {
|
||||
return String.valueOf(number);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
private static String getClipboardString() {
|
||||
Transferable trans = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
|
||||
if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) {
|
||||
try {
|
||||
return (String) trans.getTransferData(DataFlavor.stringFlavor);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
while (true) {
|
||||
System.out.println("Hex1: ");
|
||||
var hex1 = scanner.nextLine();
|
||||
System.out.println("Hex2: ");
|
||||
var hex2 = scanner.nextLine();
|
||||
System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
|
||||
System.out.println(HexComparator.compare(hex1, hex2));
|
||||
System.out.println();
|
||||
}
|
||||
/*
|
||||
System.out.println(HexComparator.compare(
|
||||
//mirai
|
||||
|
||||
@ -230,66 +296,63 @@ public class HexComparator {
|
||||
"6F 0B DF 92 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 E9 E9 E9 E9 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC\n\n\n"
|
||||
));*/
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class HexReader{
|
||||
class HexReader {
|
||||
private String s;
|
||||
private int pos = 0;
|
||||
private int lastHaxPos = 0;
|
||||
|
||||
|
||||
public HexReader(String s){
|
||||
public HexReader(String s) {
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
public String readHex(){
|
||||
public String readHex() {
|
||||
boolean isStr = false;
|
||||
String next = "";
|
||||
for (;pos<s.length()-2;++pos){
|
||||
for (; pos < s.length() - 2; ++pos) {
|
||||
|
||||
char s1 = ' ';
|
||||
if(pos != 0){
|
||||
if (pos != 0) {
|
||||
s1 = this.s.charAt(0);
|
||||
}
|
||||
char s2 = this.s.charAt(pos+1);
|
||||
char s3 = this.s.charAt(pos+2);
|
||||
char s2 = this.s.charAt(pos + 1);
|
||||
char s3 = this.s.charAt(pos + 2);
|
||||
char s4 = ' ';
|
||||
if(this.s.length() != (this.pos+3)){
|
||||
s4 = this.s.charAt(pos+3);
|
||||
if (this.s.length() != (this.pos + 3)) {
|
||||
s4 = this.s.charAt(pos + 3);
|
||||
}
|
||||
if(
|
||||
if (
|
||||
Character.isSpaceChar(s1) && Character.isSpaceChar(s4)
|
||||
&&
|
||||
&&
|
||||
(Character.isDigit(s2) || Character.isAlphabetic(s2))
|
||||
&&
|
||||
(Character.isDigit(s3) || Character.isAlphabetic(s3))
|
||||
){
|
||||
this.pos+=2;
|
||||
this.lastHaxPos = this.pos+1;
|
||||
return String.valueOf(s2) + String.valueOf(s3);
|
||||
) {
|
||||
this.pos += 2;
|
||||
this.lastHaxPos = this.pos + 1;
|
||||
return String.valueOf(s2) + s3;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void readFully(BiConsumer<String, Integer> processor){
|
||||
public void readFully(BiConsumer<String, Integer> processor) {
|
||||
this.reset();
|
||||
String nextHax = this.readHex();
|
||||
while (!nextHax.equals(" ")){
|
||||
processor.accept(nextHax,this.lastHaxPos);
|
||||
while (!nextHax.equals(" ")) {
|
||||
processor.accept(nextHax, this.lastHaxPos);
|
||||
nextHax = this.readHex();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTo(int pos){
|
||||
public void setTo(int pos) {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public void reset(){
|
||||
public void reset() {
|
||||
this.pos = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
8
mirai-core/src/test/java/TestKt.kt
Normal file
8
mirai-core/src/test/java/TestKt.kt
Normal file
@ -0,0 +1,8 @@
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
fun main() {
|
||||
println("牛逼".toByteArray().toUHexString())
|
||||
}
|
Loading…
Reference in New Issue
Block a user