oicq
Version:
QQ protocol!
147 lines (135 loc) • 5.49 kB
JavaScript
"use strict";
const net = require("net");
const https = require("https");
const { BUF0, timestamp } = require("./common");
const tea = require("./algo/tea");
const jce = require("./algo/jce");
const default_host = "msfwifi.3g.qq.com";
const default_port = 8080;
let update_time = 0;
let host_port = { };
class Network extends net.Socket {
closed_time = timestamp();
host = default_host;
port = default_port;
_data = BUF0;
/**
* @param {import("./ref").Client} c
*/
constructor(c) {
super();
this.c = c;
this.on("error", (err) => {
this.c.logger.error(err.message);
});
this.on("close", () => {
this._data = BUF0;
if (this.remoteAddress)
this.c.logger.mark(`${this.remoteAddress}:${this.remotePort} closed`);
if (timestamp() - this.closed_time <= 300)
delete host_port[this.remoteAddress];
this.closed_time = timestamp();
this.c.emit("internal.offline");
});
this.on("data", (data) => {
this._data = this._data.length === 0 ? data : Buffer.concat([this._data, data]);
while (this._data.length > 4) {
let len = this._data.readUInt32BE();
if (this._data.length >= len) {
const packet = this._data.slice(4, len);
this._data = this._data.slice(len);
this.c.emit("internal.packet", packet);
} else {
break;
}
}
});
}
async join(cb) {
if (timestamp() - this.closed_time > 300) {
//
} else if (net.isIP(this.c.config.remote_ip)) {
this.host = this.c.config.remote_ip;
this.port = 8080;
if (this.c.config.remote_port > 0 && this.c.config.remote_port < 65536)
this.host = this.c.config.remote_port;
} else if (this.c.config.auto_server) {
if (!Object.keys(host_port).length || timestamp() - update_time >= 3600) {
this.c.logger.mark("正在探索可用服务器...");
try {
await this._queryServerList();
this.host = Object.keys(host_port)[0];
this.port = host_port[this.host];
} catch (err) {
this.c.logger.warn("探索服务器失败: " + (err ? err.message : "timeout"));
}
} else {
this.host = Object.keys(host_port)[0];
this.port = host_port[this.host];
}
}
this.c.logger.mark(`connecting to ${this.host}:${this.port}`);
this.removeAllListeners("connect");
this.connect(this.port, this.host, () => {
this.c.logger.mark(`${this.remoteAddress}:${this.remotePort} connected`);
cb();
});
}
async _queryServerList() {
const key = Buffer.from("F0441F5FF42DA58FDCF7949ABA62D411", "hex");
const HttpServerListReq = jce.encodeStruct([
null,
0, 0, 1, "00000", 100, this.c.apk.subid, this.c.device.imei, 0, 0, 0,
0, 0, 0, 1
]);
const extra = {
service: "ConfigHttp",
method: "HttpServerListReq",
};
let body = jce.encodeWrapper({ HttpServerListReq }, extra);
const len = Buffer.alloc(4);
len.writeUInt32BE(body.length + 4);
body = Buffer.concat([len, body]);
body = tea.encrypt(body, key);
return await new Promise((resolve, reject) => {
const id = setTimeout(reject, 3000);
https.request("https://configsvr.msf.3g.qq.com/configsvr/serverlist.jsp", { method: "POST" }, (res) => {
let data = [];
res.on("error", reject);
res.on("data", (chunk) => data.push(chunk));
res.on("end", () => {
try {
clearTimeout(id);
data = Buffer.concat(data);
data = tea.decrypt(data, key).slice(4);
const nested = jce.decode(data);
host_port = { };
for (let v of nested[2]) {
if (Object.keys(host_port).length >= 3)
continue;
host_port[v[1]] = v[2];
}
if (Object.keys(host_port).length > 0) {
update_time = timestamp();
this.c.logger.debug(host_port);
resolve();
} else {
reject(new Error("no aliveable server"));
}
} catch (err) {
reject(err);
}
});
}).on("error", reject).end(body);
});
}
}
module.exports = Network;
/**
* 关于探索服务器:
* 由于tx服务器偶尔会抽风,若固定使用域名msfwifi.3g.qq.com连接,遇到抽风的IP会炸
* 探索服务器每次获得3个理论最优IP,一小时内只进行一次探索,除非IP已经用完
* 在连接后5分钟内若出现断线,则判断此IP抽风,将其排除
* 5分钟之外的断线则正常重连
* 同一线程中的所有客户端实例共用一个IP池
*/