Merge remote-tracking branch 'origin/master'

# Conflicts:
#	mirai-core/src/main/java/net/mamoe/mirai/Robot.java
This commit is contained in:
liujiahua123123 2019-09-07 11:49:05 +08:00
commit 93dc8e589e
76 changed files with 1977 additions and 1335 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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()));
}
}

View File

@ -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);
}
});
}

View File

@ -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
}
*/

View File

@ -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

View File

@ -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
}

View File

@ -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>()
}

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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)
}

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.event.events;
package net.mamoe.mirai.event;
/**
* @author NaturalHG

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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());
}
}

View File

@ -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)
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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
}
}

View File

@ -7,5 +7,4 @@ public final class RobotLoginSucceedEvent extends RobotEvent {
public RobotLoginSucceedEvent(Robot robot) {
super(robot);
}
}

View File

@ -1,7 +0,0 @@
package net.mamoe.mirai.event.events.server;
import net.mamoe.mirai.event.events.MiraiEvent;
public final class ServerDisableEvent extends MiraiEvent {
}

View File

@ -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 {
}

View File

@ -1,8 +0,0 @@
package net.mamoe.mirai.event.events.server;
import net.mamoe.mirai.event.events.MiraiEvent;
public final class ServerEnableEvent extends MiraiEvent {
}

View File

@ -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 {
}

View File

@ -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));
}

View File

@ -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)

View File

@ -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();
}
}

View File

@ -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 {

View File

@ -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);
}

View File

@ -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()
}

View File

@ -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() {
}
}
}

View File

@ -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());
}
}
}

View File

@ -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())
}

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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());
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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))
}
}
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()))
}
}

View File

@ -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,
}

View File

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

View File

@ -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)
}
}
}

View File

@ -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()) }
}

View File

@ -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
*/

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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())
}
}

View File

@ -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 {
}

View File

@ -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 -> {
};
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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> {
}

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.util
package net.mamoe.mirai.utils
/**
* @author Him188moe

View File

@ -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)
}

View File

@ -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();
}

View File

@ -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();
}
}

View 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 })
}

View File

@ -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;
}
}

View File

@ -0,0 +1,8 @@
import net.mamoe.mirai.utils.toUHexString
/**
* @author Him188moe
*/
fun main() {
println("牛逼".toByteArray().toUHexString())
}