icqq
Version:
QQ protocol for NodeJS!
1,306 lines (1,305 loc) • 71.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a, _b, _c, _d, _e;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseClient = exports.QrcodeResult = exports.ApiRejection = exports.VerboseLevel = void 0;
const triptrap_1 = require("triptrap");
const crypto_1 = __importStar(require("crypto"));
const stream_1 = require("stream");
const network_1 = __importDefault(require("./network"));
const ecdh_1 = __importDefault(require("./ecdh"));
const writer_1 = __importDefault(require("./writer"));
const tlv = __importStar(require("./tlv"));
const tea = __importStar(require("./tea"));
const pb = __importStar(require("./protobuf"));
const jce = __importStar(require("./jce"));
const constants_1 = require("./constants");
const device_1 = require("./device");
const log4js = __importStar(require("log4js"));
const path = __importStar(require("path"));
const timers_1 = require("timers");
const FN_NEXT_SEQ = Symbol("FN_NEXT_SEQ");
const FN_SEND = Symbol("FN_SEND");
const FN_SEND_LOGIN = Symbol("FN_SEND_LOGIN");
const HANDLERS = Symbol("HANDLERS");
const NET = Symbol("NET");
const ECDH = Symbol("ECDH");
const IS_ONLINE = Symbol("IS_ONLINE");
const LOGIN_LOCK = Symbol("LOGIN_LOCK");
const HEARTBEAT = Symbol("HEARTBEAT");
var VerboseLevel;
(function (VerboseLevel) {
VerboseLevel[VerboseLevel["Fatal"] = 0] = "Fatal";
VerboseLevel[VerboseLevel["Mark"] = 1] = "Mark";
VerboseLevel[VerboseLevel["Error"] = 2] = "Error";
VerboseLevel[VerboseLevel["Warn"] = 3] = "Warn";
VerboseLevel[VerboseLevel["Info"] = 4] = "Info";
VerboseLevel[VerboseLevel["Debug"] = 5] = "Debug";
})(VerboseLevel || (exports.VerboseLevel = VerboseLevel = {}));
class ApiRejection {
constructor(code, message = "unknown") {
this.code = code;
this.message = message;
this.code = Number(this.code);
this.message = this.message?.toString() || "unknown";
}
}
exports.ApiRejection = ApiRejection;
var QrcodeResult;
(function (QrcodeResult) {
QrcodeResult[QrcodeResult["OtherError"] = 0] = "OtherError";
QrcodeResult[QrcodeResult["Timeout"] = 17] = "Timeout";
QrcodeResult[QrcodeResult["WaitingForScan"] = 48] = "WaitingForScan";
QrcodeResult[QrcodeResult["WaitingForConfirm"] = 53] = "WaitingForConfirm";
QrcodeResult[QrcodeResult["Canceled"] = 54] = "Canceled";
})(QrcodeResult || (exports.QrcodeResult = QrcodeResult = {}));
class BaseClient extends triptrap_1.Trapper {
constructor(p = device_1.Platform.Android, d, config) {
super();
this.config = config;
this[_a] = false;
this[_b] = false;
this[_c] = new ecdh_1.default;
this[_d] = new network_1.default;
// 回包的回调函数
this[_e] = new Map();
this.sig = {
seq: (0, crypto_1.randomBytes)(4).readUInt32BE() & 0xfff,
session: (0, crypto_1.randomBytes)(4),
randkey: (0, crypto_1.randomBytes)(16),
tgtgt: (0, crypto_1.randomBytes)(16),
tgt: constants_1.BUF0,
skey: constants_1.BUF0,
a1: constants_1.BUF0,
d2: constants_1.BUF0,
d2key: constants_1.BUF0,
old_d2key: constants_1.BUF16,
st_web: constants_1.BUF0,
t104: constants_1.BUF0,
t174: constants_1.BUF0,
qrsig: constants_1.BUF0,
t543: constants_1.BUF0,
t546: constants_1.BUF0,
t547: constants_1.BUF0,
t553: constants_1.BUF0,
/** 大数据上传通道 */
bigdata: {
ip: "",
port: 0,
sig_session: constants_1.BUF0,
session_key: constants_1.BUF0,
},
/** 心跳包 */
hb480: (() => {
const buf = Buffer.alloc(9);
buf.writeUInt32BE(this.uin);
buf.writeInt32BE(0x19e39, 5);
return pb.encode({
1: 1152,
2: 9,
4: buf
});
})(),
sign_api_addr: "",
sign_api_init: false,
/** 上次cookie刷新时间 */
emp_time: 0,
time_diff: 0,
requestTokenTime: 0,
/** token登录重试计数 */
token_retry_count: 0,
/** 上线失败重试计数 */
register_retry_count: 0
};
this.pkg = require("../../package.json");
this.pskey = {};
this.pt4token = {};
/** 心跳间隔(秒) */
this.interval = 60;
/** token刷新间隔(秒) */
this.emp_interval = 12 * 3600;
/** 随心跳一起触发的函数,可以随意设定 */
this.heartbeat = constants_1.NOOP;
/** token登录重试次数 */
this.token_retry_num = 2;
/** 上线失败重试次数 */
this.register_retry_num = 3;
this.login_timer = null;
/** 数据统计 */
this.statistics = {
start_time: (0, constants_1.timestamp)(),
lost_times: 0,
recv_pkt_cnt: 0,
sent_pkt_cnt: 0,
lost_pkt_cnt: 0,
recv_msg_cnt: 0,
sent_msg_cnt: 0,
msg_cnt_per_min: 0,
remote_ip: "",
remote_port: 0,
ver: ''
};
this.signCmd = [
"trpc.o3.ecdh_access.EcdhAccess.SsoSecureA2Establish",
"trpc.o3.ecdh_access.EcdhAccess.SsoSecureA2Access",
"OidbSvcTrpcTcp.0xf88_1",
"OidbSvcTrpcTcp.0x1105_1",
"trpc.o3.report.Report.SsoReport",
"wtlogin.trans_emp",
"OidbSvcTrpcTcp.0xf89_1",
"wtlogin_device.login",
"OidbSvcTrpcTcp.0xf67_5",
"OidbSvcTrpcTcp.0xfa5_1",
"OidbSvcTrpcTcp.0x55f_0",
"wtlogin.device_lock",
"qidianservice.207",
"wtlogin_device.tran_sim_emp",
"OidbSvc.0x758_0",
"SQQzoneSvc.addReply",
"trpc.o3.ecdh_access.EcdhAccess.SsoEstablishShareKey",
"OidbSvc.0x89a_0",
"trpc.passwd.manager.PasswdManager.SetPasswd",
"QQConnectLogin.pre_auth",
"trpc.qlive.word_svr.WordSvr.NewPublicChat",
"OidbSvc.0x8a1_0",
"SQQzoneSvc.publishmood",
"OidbSvcTrpcTcp.0x101e_2",
"OidbSvcTrpcTcp.0x101e_1",
"OidbSvcTrpcTcp.0xf67_1",
"OidbSvcTrpcTcp.0xf6e_1",
"OidbSvc.0x8a1_7",
"OidbSvc.0x758_1",
"OidbSvc.0x4ff_9",
"OidbSvc.0x88d_0",
"FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoLike",
"SQQzoneSvc.addComment",
"MessageSvc.PbSendMsg",
"FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoComment",
"SQQzoneSvc.shuoshuo",
"SQQzoneSvc.like",
"trpc.o3.ecdh_access.EcdhAccess.SsoSecureAccess",
"OidbSvc.0x758",
"QChannelSvr.trpc.qchannel.commwriter.ComWriter.DoComment",
"QChannelSvr.trpc.qchannel.commwriter.ComWriter.DoReply",
"wtlogin.qrlogin",
"OidbSvcTrpcTcp.0xf57_1",
"OidbSvc.oidb_0x758",
"OidbSvcTrpcTcp.0xf57_9",
"wtlogin.exchange_emp",
"OidbSvc.0x56c_6",
"QChannelSvr.trpc.qchannel.commwriter.ComWriter.PublishFeed",
"OidbSvcTrpcTcp.0xf55_1",
"OidbSvcTrpcTcp.0x6d9_4",
"trpc.qlive.relationchain_svr.RelationchainSvr.Follow",
"ProfileService.GroupMngReq",
"ProfileService.getGroupInfoReq",
"ConnAuthSvr.get_app_info_emp",
"OidbSvcTrpcTcp.0x1100_1",
"FeedCloudSvr.trpc.videocircle.circleprofile.CircleProfile.SetProfile",
"qidianservice.135",
"trpc.group_pro.msgproxy.sendmsg",
"FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoPush",
"FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoReply",
"FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoBarrage",
"QQConnectLogin.get_promote_page",
"friendlist.addFriend",
"SQQzoneSvc.forward",
"OidbSvc.0x4ff_9_IMCore",
"OidbSvc.0x6d9_4",
"trpc.springfestival.redpacket.LuckyBag.SsoSubmitGrade",
"wtlogin.login",
"OidbSvc.0x89b_1",
"trpc.qqhb.qqhb_proxy.Handler.sso_handle",
"qidianservice.290",
"wtlogin.register",
"OidbSvc.0x8ba",
"ConnAuthSvr.sdk_auth_api_emp",
"OidbSvc.0x9fa",
"qidianservice.269",
"OidbSvcTrpcTcp.0xf65_1",
"FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.PublishFeed",
"OidbSvcTrpcTcp.0xf65_10",
"ConnAuthSvr.fast_qq_login",
"trpc.login.ecdh.EcdhService.SsoQRLoginGenQr",
"friendlist.AddFriendReq",
"MsgProxy.SendMsg",
"trpc.login.ecdh.EcdhService.SsoNTLoginPasswordLoginUnusualDevice",
"trpc.passwd.manager.PasswdManager.VerifyPasswd",
"trpc.login.ecdh.EcdhService.SsoQRLoginScanQr",
"QQConnectLogin.get_promote_page_emp",
"FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoFollow",
"QQConnectLogin.submit_promote_page",
"friendlist.ModifyGroupInfoReq",
"OidbSvcTrpcTcp.0xf57_106",
"QQConnectLogin.submit_promote_page_emp",
"OidbSvcTrpcTcp.0x1107_1",
"QQConnectLogin.pre_auth_emp",
"ConnAuthSvr.get_app_info",
"ConnAuthSvr.sdk_auth_api",
"wtlogin.name2uin",
"QQConnectLogin.auth",
"ConnAuthSvr.get_auth_api_list_emp",
"QQConnectLogin.auth_emp",
"ConnAuthSvr.get_auth_api_list"
];
this.ssoPacketList = [];
if (config.log_config)
log4js.configure(config.log_config);
this.apk = this.getApkInfo(p, config.ver);
this.device = new device_1.Device(this.apk, d);
this[NET].on("error", err => this.emit("internal.verbose", err.message, VerboseLevel.Error));
this[NET].on("close", () => {
this.statistics.remote_ip = "";
this.statistics.remote_port = 0;
this[NET].remoteAddress && this.emit("internal.verbose", `${this[NET].remoteAddress}:${this[NET].remotePort} closed`, VerboseLevel.Mark);
});
this[NET].on("connect2", () => {
this.statistics.remote_ip = this[NET].remoteAddress;
this.statistics.remote_port = this[NET].remotePort;
this.emit("internal.verbose", `${this[NET].remoteAddress}:${this[NET].remotePort} connected`, VerboseLevel.Mark);
this.syncTimeDiff();
});
this[NET].on("packet", packetListener.bind(this));
this[NET].on("lost", lostListener.bind(this));
this.on("internal.online", onlineListener);
this.on("internal.sso", ssoListener);
Object.defineProperty(this, "apk", { writable: false });
(0, constants_1.lock)(this, "device");
(0, constants_1.lock)(this, "sig");
(0, constants_1.lock)(this, "pskey");
(0, constants_1.lock)(this, "pt4token");
(0, constants_1.lock)(this, "statistics");
(0, constants_1.hide)(this, "heartbeat");
(0, constants_1.hide)(this, "interval");
}
/** 设置连接服务器,不设置则自动搜索 */
setRemoteServer(host, port) {
if (host && port) {
this[NET].host = host;
this[NET].port = port;
this[NET].auto_search = false;
}
else {
this[NET].auto_search = true;
}
}
async setSignServer(addr) {
if (!addr)
return;
(0, constants_1.unlock)(this, "sig");
this.sig.sign_api_init = false;
if (!/http(s)?:\/\//.test(addr))
addr = `http://${addr}`;
this.sig.sign_api_addr = addr;
let url = new URL(this.sig.sign_api_addr);
let module;
if (url.searchParams.get('key')) {
module = await Promise.resolve().then(() => __importStar(require('./qsign')));
}
else {
module = await Promise.resolve().then(() => __importStar(require('./sign')));
this.getCmdWhiteList = module.getCmdWhiteList.bind(this);
}
this.getApiQQVer = module.getApiQQVer.bind(this);
this.getT544 = module.getT544.bind(this);
this.getSign = module.getSign.bind(this);
this.requestSignToken = module.requestSignToken.bind(this);
this.submitSsoPacket = module.submitSsoPacket.bind(this);
this.sig.sign_api_init = true;
(0, constants_1.lock)(this, "sig");
}
on(matcher, listener) {
return this.trap(matcher, listener);
}
once(matcher, listener) {
return this.trapOnce(matcher, listener);
}
off(matcher) {
return this.offTrap(matcher);
}
emit(matcher, ...args) {
return this.trip(matcher, ...args);
}
/** 是否为在线状态 (可以收发业务包的状态) */
isOnline() {
return this[IS_ONLINE];
}
getApkInfo(platform, ver) {
if (platform == device_1.Platform.iPad)
platform = device_1.Platform.aPad;
const apks = this.getApkInfoList(platform);
return apks.find(val => val.ver === ver) || apks[0];
}
getApkInfoList(platform) {
return (0, device_1.getApkInfoList)(platform);
}
buildReserveFields(cmd, sec_info) {
let qImei36 = this.device.qImei36 || this.device.qImei16;
let reserveFields;
if (this.apk.ssover >= 20 && false) {
reserveFields = {
9: 1,
11: 2052,
12: qImei36,
14: 0,
15: '',
16: this.uid || '',
18: 0,
19: 1,
20: 1,
21: 32,
24: sec_info,
26: 100,
28: 3
};
}
else {
reserveFields = {
9: 1,
12: qImei36,
14: 0,
16: this.uin,
18: 0,
19: 1,
20: 1,
21: 0,
24: sec_info,
28: 3
};
}
return Buffer.from(pb.encode(reserveFields));
}
async switchQQVer(ver = '') {
if (this.config.sign_api_addr && !this.sig.sign_api_init) {
await this.setSignServer(this.config.sign_api_addr);
}
if (this.config.ver) {
this.statistics.ver = this.config.ver;
return false;
}
const old_ver = this.statistics.ver || this.config.ver;
ver = ver || await this.getApiQQVer();
if (old_ver != ver) {
const new_apk = this.getApkInfo(this.config.platform, ver);
if (new_apk.ver === ver) {
Object.defineProperty(this, "apk", { writable: true });
this.apk = new_apk;
Object.defineProperty(this, "apk", { writable: false });
this.device.apk = this.apk;
this.statistics.ver = ver;
return true;
}
}
return false;
}
async updateCmdWhiteList() {
let list = await this.getCmdWhiteList();
if (list?.length)
this.signCmd = Array.from(new Set(this.signCmd.concat(list)));
}
async getCmdWhiteList() {
return [];
}
async getApiQQVer() {
return this.config.ver;
}
async getT544(cmd) {
return this.generateT544Packet(cmd, constants_1.BUF0);
}
async getSign(cmd, seq, body) {
return constants_1.BUF0;
}
generateT544Packet(cmd, sign) {
const t = tlv.getPacker(this);
let getLocalT544 = (cmd) => {
switch (cmd) {
case '810_2':
return t(0x544, 0, 2);
case '810_7':
return t(0x544, 0, 7);
case '810_9':
return t(0x544, 2, 9);
case '810_a':
return t(0x544, 2, 10);
case '810_f':
return t(0x544, 2, 15);
}
return constants_1.BUF0;
};
if (!sign || sign.length < 1) {
return getLocalT544(cmd);
}
return t(0x544, -1, -1, sign);
}
generateSignPacket(sign, token, extra) {
if (!sign) {
return constants_1.BUF0;
}
let sec_info = {
1: Buffer.from(sign, 'hex'),
2: Buffer.from(token, 'hex'),
3: Buffer.from(extra, 'hex')
};
return Buffer.from(pb.encode(sec_info));
}
async ssoPacketListHandler(list) {
let handle = (list) => {
let new_list = [];
for (let val of list) {
try {
let data = pb.decode(Buffer.from(val.body, 'hex'));
val.type = data[1].toString();
}
catch (err) { }
new_list.push(val);
}
return new_list;
};
if (list === null && this.isOnline()) {
if (this.ssoPacketList.length > 0) {
list = this.ssoPacketList;
this.ssoPacketList = [];
}
}
if (!list || !list.length)
return;
if (!this.isOnline()) {
list = handle(list);
if (this.ssoPacketList.length > 0) {
let list1 = this.ssoPacketList;
this.ssoPacketList = [];
for (let val of list) {
let ssoPacket = list1.find((data) => {
return data.cmd === val.cmd && data.type === val.type;
});
if (ssoPacket) {
ssoPacket.body = val.body;
}
else {
list1.push(val);
}
}
}
else {
this.ssoPacketList = this.ssoPacketList.concat(list);
}
return;
}
for (let ssoPacket of list) {
let cmd = ssoPacket.cmd;
let body = Buffer.from(ssoPacket.body, 'hex');
let callbackId = ssoPacket.callbackId;
let payload = await this.sendUni(cmd, body);
this.emit("internal.verbose", `sendUni:${cmd} result: ${payload.toString('hex')}`, VerboseLevel.Debug);
if (callbackId > -1) {
await this.submitSsoPacket(cmd, callbackId, payload);
}
}
}
async requestToken() {
if ((Date.now() - this.sig.requestTokenTime) >= (60 * 60 * 1000)) {
this.sig.requestTokenTime = Date.now();
let list = await this.requestSignToken();
await this.ssoPacketListHandler(list);
}
}
async requestSignToken() {
return [];
}
async submitSsoPacket(cmd, callbackId, body) {
return [];
}
calcPoW(data) {
if (!data || data.length === 0)
return Buffer.alloc(0);
const stream = stream_1.Readable.from(data, { objectMode: false });
const version = stream.read(1).readUInt8();
const typ = stream.read(1).readUInt8();
const hashType = stream.read(1).readUInt8();
let ok = stream.read(1).readUInt8() === 0;
const maxIndex = stream.read(2).readUInt16BE();
const reserveBytes = stream.read(2);
const src = stream.read(stream.read(2).readUInt16BE());
const tgt = stream.read(stream.read(2).readUInt16BE());
const cpy = stream.read(stream.read(2).readUInt16BE());
if (hashType !== 1) {
this.emit("internal.verbose", `Unsupported tlv546 hash type ${hashType}`, VerboseLevel.Warn);
return Buffer.alloc(0);
}
let inputNum = BigInt("0x" + src.toString("hex"));
switch (typ) {
case 1:
// TODO
// See https://github.com/mamoe/mirai/blob/cc7f35519ea7cc03518a57dc2ee90d024f63be0e/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLoginExt.kt#L207
this.emit("internal.verbose", `Unsupported tlv546 algorithm type ${typ}`, VerboseLevel.Warn);
break;
case 2:
// Calc SHA256
let dst;
let elp = 0, cnt = 0;
if (tgt.length === 32) {
const start = Date.now();
let hash = (0, crypto_1.createHash)("sha256").update(Buffer.from(inputNum.toString(16).padStart(256, "0"), "hex")).digest();
while (Buffer.compare(hash, tgt) !== 0) {
inputNum++;
hash = (0, crypto_1.createHash)("sha256").update(Buffer.from(inputNum.toString(16).padStart(256, "0"), "hex")).digest();
cnt++;
if (cnt > 6000000) {
this.emit("internal.verbose", "Calculating PoW cost too much time, maybe something wrong", VerboseLevel.Error);
throw new Error("Calculating PoW cost too much time, maybe something wrong");
}
}
ok = true;
dst = Buffer.from(inputNum.toString(16).padStart(256, "0"), "hex");
elp = Date.now() - start;
this.emit("internal.verbose", `Calculating PoW of plus ${cnt} times cost ${elp} ms`, VerboseLevel.Debug);
}
if (!ok)
return Buffer.alloc(0);
const body = new writer_1.default()
.writeU8(version)
.writeU8(typ)
.writeU8(hashType)
.writeU8(ok ? 1 : 0)
.writeU16(maxIndex)
.writeBytes(reserveBytes)
.writeTlv(src)
.writeTlv(tgt)
.writeTlv(cpy);
if (dst)
body.writeTlv(dst);
body.writeU32(elp)
.writeU32(cnt);
return body.read();
default:
this.emit("internal.verbose", `Unsupported tlv546 algorithm type ${typ}`, VerboseLevel.Warn);
break;
}
return Buffer.alloc(0);
}
/** 下线 (keepalive: 是否保持tcp连接) */
async logout(keepalive = false) {
await this.register(true);
if (!keepalive && this[NET].connected) {
this.terminate();
await new Promise(resolve => this[NET].once("close", resolve));
}
}
/** 关闭连接 */
terminate() {
this[IS_ONLINE] = false;
this[NET].destroy();
}
async refreshToken(force = false) {
return await refreshToken.call(this, force);
}
/** 使用接收到的token登录 */
async tokenLogin(token = constants_1.BUF0, cmd = 11) {
if (!this.device.qImei36 || !this.device.qImei16) {
await this.device.getQIMEI();
}
if (token != constants_1.BUF0) {
this.sig.randkey = (0, crypto_1.randomBytes)(16);
this.sig.session = (0, crypto_1.randomBytes)(4);
this[ECDH] = new ecdh_1.default;
try {
const stream = stream_1.Readable.from(token, { objectMode: false });
let info = stream.read(stream.read(2).readUInt16BE());
if ((String(info) || '').includes('icqq')) {
info = JSON.parse(String(info));
if (info.apk.version != this.apk.version) {
if (info.apk.id != this.apk.id || (this.statistics.ver && info.apk.subid > this.apk.subid)) {
this.sig.token_retry_count = this.token_retry_num;
await this.token_expire();
return constants_1.BUF0;
}
else if (!this.statistics.ver) {
if (await this.switchQQVer(info.apk.ver)) {
this.emit("internal.verbose", `[${this.uin}]获取到token协议版本:${this.statistics.ver}`, VerboseLevel.Info);
const apk_info = `${this.apk.display}_${this.apk.version}`;
this.emit("internal.verbose", `[${this.uin}]使用协议:${apk_info}`, VerboseLevel.Info);
await this.device.getQIMEI();
}
}
}
const t119 = stream.read(stream.read(2).readUInt16BE());
this.sig.tgtgt = stream.read(stream.read(2).readUInt16BE());
this.sig.a1 = stream.read(stream.read(2).readUInt16BE());
this.sig.d2 = stream.read(stream.read(2).readUInt16BE());
this.sig.d2key = stream.read(stream.read(2).readUInt16BE());
this.sig.tgt = stream.read(stream.read(2).readUInt16BE());
this.sig.tgt_key = stream.read(stream.read(2).readUInt16BE());
this.sig.ticket_key = stream.read(stream.read(2).readUInt16BE());
this.sig.sig_key = stream.read(stream.read(2).readUInt16BE());
this.sig.srm_token = stream.read(stream.read(2).readUInt16BE());
this.sig.device_token = stream.read(stream.read(2).readUInt16BE());
this.sig.t543 = stream.read(stream.read(2).readUInt16BE());
if (info.token_ver >= 3) {
this.sig.randkey = stream.read(stream.read(2).readUInt16BE());
this.sig.session = stream.read(stream.read(2).readUInt16BE());
}
if (this.sig.token_retry_count === 0 && info.token_ver >= 3) {
read_bigdata.call(this);
const { nickname, gender, age } = decodeT119.call(this, t119);
const err = (await this.register());
if (err === 1) {
this.sig.emp_time = info.emp_time;
this.sig.register_retry_count = 0;
await this.updateCmdWhiteList();
await this.ssoPacketListHandler(null);
this.emit("internal.online", constants_1.BUF0, nickname, gender, age);
return constants_1.BUF0;
}
else if (err === -2) {
return constants_1.BUF0;
}
}
}
else {
this.sig.d2key = info;
this.sig.d2 = stream.read(stream.read(2).readUInt16BE());
this.sig.tgt = stream.read(stream.read(2).readUInt16BE());
this.sig.ticket_key = stream.read(stream.read(2).readUInt16BE());
this.sig.sig_key = stream.read(stream.read(2).readUInt16BE());
this.sig.srm_token = stream.read(stream.read(2).readUInt16BE());
this.sig.tgt = stream.read(stream.read(2).readUInt16BE());
this.sig.md5Pass = stream.read(stream.read(2).readUInt16BE());
this.sig.device_token = stream.read(stream.read(2).readUInt16BE());
try {
this.sig.t543 = stream.read(stream.read(2).readUInt16BE()) || constants_1.BUF0;
}
catch { }
}
}
catch (err) {
console.log(err);
this.emit("internal.verbose", '旧版token于当前版本不兼容,请手动删除token后重新运行', VerboseLevel.Error);
this.emit("internal.verbose", '若非无法登录,请勿随意升级版本', VerboseLevel.Warn);
this.emit("internal.error.login", 123456, `token不兼容`);
return constants_1.BUF0;
}
}
cmd = (this.sig.srm_token?.length && this.sig.a1?.length) ? cmd : 11;
this.sig.tgtgt = cmd == 15 ? this.sig.tgtgt : (0, constants_1.md5)(this.sig.d2key);
const t = tlv.getPacker(this);
const tlvs = [
t(0x8),
t(0x18),
t(0x100),
t(0x116),
t(0x141),
t(0x142),
t(0x144),
t(0x145),
t(0x147),
t(0x154),
t(0x177),
t(0x187),
t(0x188),
t(0x511),
t(0x542),
t(0x548)
];
if (cmd === 15) {
tlvs.push(...[
t(0x1),
new writer_1.default().writeU16(0x106).writeTlv(this.sig.a1).read(),
t(0x107),
t(0x16a),
t(0x191),
t(0x516),
t(0x521, 0)
]);
}
else {
tlvs.push(...[
t(0x108),
t(0x10a),
t(0x112),
t(0x143),
]);
}
if (this.apk.ssover >= 5) {
tlvs.push(await this.getT544(cmd === 15 ? '810_f' : '810_a'));
if (this.sig.t553)
tlvs.push(t(0x553));
}
if (this.device.qImei16) {
tlvs.push(t(0x545, this.device.qImei16));
}
else {
tlvs.push(t(0x194));
tlvs.push(t(0x202));
}
let writer = new writer_1.default()
.writeU16(cmd)
.writeU16(tlvs.length);
for (let tlv of tlvs) {
writer.writeBytes(tlv);
}
const body = writer.read();
if (token != constants_1.BUF0) {
await this[FN_SEND_LOGIN]("wtlogin.exchange_emp", body);
return constants_1.BUF0;
}
return body;
}
/**
* 使用密码登录
* @param uin 登录账号
* @param md5pass 密码的md5值
*/
async passwordLogin(uin, md5pass) {
if (!this.device.qImei36 || !this.device.qImei16) {
await this.device.getQIMEI();
}
this.uin = uin;
this.sig.session = (0, crypto_1.randomBytes)(4);
this.sig.randkey = (0, crypto_1.randomBytes)(16);
this.sig.tgtgt = (0, crypto_1.randomBytes)(16);
this[ECDH] = new ecdh_1.default;
const t = tlv.getPacker(this);
const tlvs = [
t(0x1),
t(0x8),
t(0x18),
t(0x100),
t(0x106, md5pass),
t(0x107),
t(0x116),
t(0x141),
t(0x142),
t(0x144),
t(0x145),
t(0x147),
t(0x154),
t(0x177),
t(0x187),
t(0x188),
t(0x191),
t(0x511),
t(0x516),
t(0x521, 0),
t(0x525),
t(0x542),
t(0x548)
];
if (this.apk.ssover >= 12) {
tlvs.push(await this.getT544('810_9'));
if (this.sig.t553)
tlvs.push(t(0x553));
}
if (this.device.qImei16) {
tlvs.push(t(0x545, this.device.qImei16));
}
else {
tlvs.push(t(0x194));
tlvs.push(t(0x202));
}
let writer = new writer_1.default()
.writeU16(9)
.writeU16(tlvs.length);
for (let tlv of tlvs) {
writer.writeBytes(tlv);
}
this[FN_SEND_LOGIN]("wtlogin.login", writer.read());
}
/** 收到滑动验证码后,用于提交滑动验证码 */
async submitSlider(ticket) {
try {
if (this.sig.t546.length)
this.sig.t547 = this.calcPoW(this.sig.t546);
}
catch (err) {
}
ticket = String(ticket).trim();
const t = tlv.getPacker(this);
const tlvs = [
t(0x8),
t(0x104),
t(0x116),
t(0x193, ticket),
];
if (this.apk.ssover >= 12) {
tlvs.push(await this.getT544('810_2'));
if (this.sig.t553)
tlvs.push(t(0x553));
}
if (this.sig.t547.length) {
tlvs.push(t(0x547));
}
let writer = new writer_1.default()
.writeU16(2)
.writeU16(tlvs.length);
for (let tlv of tlvs) {
writer.writeBytes(tlv);
}
this[FN_SEND_LOGIN]("wtlogin.login", writer.read());
}
/** 收到设备锁验证请求后,用于发短信 */
async sendSmsCode() {
const t = tlv.getPacker(this);
let tlv_count = 6;
const writer = new writer_1.default()
.writeU16(8)
.writeU16(tlv_count)
.writeBytes(t(0x8))
.writeBytes(t(0x104))
.writeBytes(t(0x116))
.writeBytes(t(0x174))
.writeBytes(t(0x17a))
.writeBytes(t(0x197));
this[FN_SEND_LOGIN]("wtlogin.login", writer.read());
}
/** 提交短信验证码 */
async submitSmsCode(code) {
code = String(code).trim();
if (Buffer.byteLength(code) !== 6)
code = "123456";
const t = tlv.getPacker(this);
const tlvs = [
t(0x8),
t(0x104),
t(0x116),
t(0x174),
t(0x17c, code),
t(0x198),
t(0x401)
];
if (this.apk.ssover >= 12) {
tlvs.push(await this.getT544('810_7'));
if (this.sig.t553)
tlvs.push(t(0x553));
}
if (this.sig.t547.length) {
tlvs.push(t(0x547));
}
let writer = new writer_1.default()
.writeU16(7)
.writeU16(tlvs.length);
for (let tlv of tlvs) {
writer.writeBytes(tlv);
}
this[FN_SEND_LOGIN]("wtlogin.login", writer.read());
}
/** 获取登录二维码 */
async fetchQrcode() {
const t = tlv.getPacker(this);
const body = new writer_1.default()
.writeU16(0)
.writeU32(16)
.writeU64(0)
.writeU8(8)
.writeTlv(constants_1.BUF0)
.writeU16(6)
.writeBytes(t(0x16))
.writeBytes(t(0x1B))
.writeBytes(t(0x1D))
.writeBytes(t(0x1F))
.writeBytes(t(0x33))
.writeBytes(t(0x35, this.apk.device_type))
.read();
const pkt = await buildCode2dPacket.call(this, 0x31, 0x11100, body);
this[FN_SEND](pkt).then(payload => {
payload = tea.decrypt(payload.slice(16, -1), this[ECDH].share_key);
const stream = stream_1.Readable.from(payload, { objectMode: false });
stream.read(54);
const retcode = stream.read(1)[0];
const qrsig = stream.read(stream.read(2).readUInt16BE());
stream.read(2);
const t = readTlv(stream);
if (!retcode && t[0x17]) {
this.sig.qrsig = qrsig;
this.emit("internal.qrcode", t[0x17]);
}
else {
this.emit("internal.error.qrcode", retcode, "获取二维码失败,请重试");
}
}).catch(() => this.emit("internal.error.network", -2, "server is busy"));
}
/** 扫码后调用此方法登录 */
async qrcodeLogin() {
const { retcode, uin, t106, t16a, t318, tgtgt } = await this.queryQrcodeResult();
if (retcode < 0) {
this.emit("internal.error.network", -2, "server is busy");
}
else if (retcode === 0 && t106 && t16a && t318 && tgtgt) {
if (this.apk.qua != '' && (!this.device.qImei36 || !this.device.qImei16)) {
await this.device.getQIMEI();
}
this.uin = uin;
this.sig.qrsig = constants_1.BUF0;
this.sig.tgtgt = tgtgt;
const t = tlv.getPacker(this);
const tlvs = [
t(0x1),
t(0x8),
t(0x18),
t(0x100),
new writer_1.default().writeU16(0x106).writeTlv(t106).read(),
t(0x107),
t(0x116),
t(0x141),
t(0x142),
t(0x144),
t(0x145),
t(0x147),
t(0x154),
t(0x16a, t16a),
t(0x177),
t(0x187),
t(0x188),
t(0x191),
t(0x511),
t(0x516),
t(0x521, this.apk.device_type),
new writer_1.default().writeU16(0x318).writeTlv(t318).read(),
];
if (this.apk.ssover >= 5) {
tlvs.push(t(0x542));
tlvs.push(await this.getT544('810_9'));
tlvs.push(t(0x548));
if (this.sig.t553)
tlvs.push(t(0x553));
}
if (this.device.qImei16) {
tlvs.push(t(0x545, this.device.qImei16));
}
else {
tlvs.push(t(0x194));
tlvs.push(t(0x202));
}
let writer = new writer_1.default()
.writeU16(9)
.writeU16(tlvs.length);
for (let tlv of tlvs) {
writer.writeBytes(tlv);
}
this[FN_SEND_LOGIN]("wtlogin.login", writer.read());
}
else {
let message;
switch (retcode) {
case QrcodeResult.Timeout:
message = "二维码超时,请重新获取";
break;
case QrcodeResult.WaitingForScan:
message = "二维码尚未扫描";
break;
case QrcodeResult.WaitingForConfirm:
message = "二维码尚未确认";
break;
case QrcodeResult.Canceled:
message = "二维码被取消,请重新获取";
break;
default:
message = "扫码遇到未知错误,请重新获取";
break;
}
this.sig.qrsig = constants_1.BUF0;
this.emit("internal.error.qrcode", retcode, message);
}
}
/** 获取扫码结果(可定时查询,retcode为0则调用qrcodeLogin登录) */
async queryQrcodeResult() {
let retcode = -1, uin, t106, t16a, t318, tgtgt;
if (!this.sig.qrsig.length)
return { retcode, uin, t106, t16a, t318, tgtgt };
const body = new writer_1.default()
.writeU16(5)
.writeU8(1)
.writeU32(8)
.writeU32(16)
.writeTlv(this.sig.qrsig)
.writeU64(0)
.writeU8(8)
.writeTlv(constants_1.BUF0)
.writeU16(0)
.read();
const pkt = await buildCode2dPacket.call(this, 0x12, 0x6200, body);
try {
let payload = await this[FN_SEND](pkt);
payload = tea.decrypt(payload.slice(16, -1), this[ECDH].share_key);
const stream = stream_1.Readable.from(payload, { objectMode: false });
stream.read(48);
let len = stream.read(2).readUInt16BE();
if (len > 0) {
len--;
if (stream.read(1)[0] === 2) {
stream.read(8);
len -= 8;
}
if (len > 0)
stream.read(len);
}
stream.read(4);
retcode = stream.read(1)[0];
if (retcode === 0) {
stream.read(4);
uin = stream.read(4).readUInt32BE();
stream.read(6);
const t = readTlv(stream);
t106 = t[0x18];
t16a = t[0x19];
t318 = t[0x65];
tgtgt = t[0x1e];
}
}
catch {
}
return { retcode, uin, t106, t16a, t318, tgtgt };
}
[(_a = IS_ONLINE, _b = LOGIN_LOCK, _c = ECDH, _d = NET, _e = HANDLERS, FN_NEXT_SEQ)]() {
if (++this.sig.seq >= 0x8000)
this.sig.seq = 1;
return this.sig.seq;
}
[FN_SEND](pkt, timeout = 5, seq = 0) {
this.statistics.sent_pkt_cnt++;
seq = seq || this.sig.seq;
return new Promise((resolve, reject) => {
const id = (0, timers_1.setTimeout)(() => {
this[HANDLERS].delete(seq);
this.statistics.lost_pkt_cnt++;
reject(new ApiRejection(-2, `packet timeout (seq: ${seq})`));
}, timeout * 1000);
this[NET].join(() => {
this[NET].write(pkt, () => {
this[HANDLERS].set(seq, (payload) => {
clearTimeout(id);
this[HANDLERS].delete(seq);
resolve(payload);
});
});
});
});
}
async [FN_SEND_LOGIN](cmd, body) {
if (this[IS_ONLINE] || this[LOGIN_LOCK])
return;
const pkt = await buildLoginPacket.call(this, cmd, body);
try {
this[LOGIN_LOCK] = true;
decodeLoginResponse.call(this, await this[FN_SEND](pkt));
}
catch (e) {
this[LOGIN_LOCK] = false;
this.emit("internal.error.network", -2, "server is busy");
this.emit("internal.verbose", e.message, VerboseLevel.Error);
this.emit("internal.verbose", e.stack, VerboseLevel.Debug);
}
}
/** 发送一个业务包不等待返回 */
async writeUni(cmd, body, seq = 0) {
this.statistics.sent_pkt_cnt++;
const pkt = await buildUniPkt.call(this, cmd, body, seq);
if (pkt.length > 0)
this[NET].write(pkt);
}
/** dont use it if not clear the usage */
sendOidb(cmd, body, timeout = 5) {
const sp = cmd //OidbSvc.0x568_22
.replace("OidbSvc.", "")
.replace("oidb_", "")
.split("_");
const type1 = parseInt(sp[0], 16), type2 = parseInt(sp[1]);
body = pb.encode({
1: type1,
2: isNaN(type2) ? 1 : type2,
3: 0,
4: body,
6: "android " + this.apk.ver,
});
return this.sendUni(cmd, body, timeout);
}
async sendPacket(type, cmd, body) {
if (type === 'Uni')
return await this.sendUni(cmd, body);
else
return await this.sendOidb(cmd, body);
}
/** 发送一个业务包并等待返回 */
async sendUni(cmd, body, timeout = 5) {
if (!this[IS_ONLINE])
throw new ApiRejection(-1, `client not online (cmd: ${cmd})`);
let seq = this[FN_NEXT_SEQ]();
const pkt = await buildUniPkt.call(this, cmd, body, seq);
if (pkt.length < 1) {
return Buffer.from(pb.encode({
1: -1,
2: '签名api异常',
3: {}
}));
}
return this[FN_SEND](pkt, timeout, seq);
}
async sendOidbSvcTrpcTcp(cmd, body) {
const sp = cmd //OidbSvcTrpcTcp.0xf5b_1
.replace("OidbSvcTrpcTcp.", "")
.split("_");
const type1 = parseInt(sp[0], 16), type2 = parseInt(sp[1]);
body = pb.encode({
1: type1,
2: type2,
4: body,
6: "android " + this.apk.ver,
});
const payload = await this.sendUni(cmd, body);
//log(payload)
const rsp = pb.decode(payload);
if (rsp[3] === 0)
return rsp[4];
throw new ApiRejection(rsp[3], rsp[5]);
}
async register(logout = false, reflush = false) {
const err = await new Promise(async (resolve) => {
const re_register = async () => {
const err = await _register.call(this, logout, reflush);
if (err === 1) {
this.sig.register_retry_count = 0;
resolve(err);
}
else if (err === -1) {
if (this.register_retry_num > this.sig.register_retry_count) {
this.sig.register_retry_count++;
this.emit("internal.verbose", '上线失败,第' + this.sig.register_retry_count + '次重试', VerboseLevel.Warn);
(0, timers_1.setTimeout)(() => {
re_register();
}, 2000);
}
else {
this.sig.register_retry_count = 0;
this.sig.token_retry_count = this.token_retry_num;
resolve(err);
}
}
else {
this.sig.register_retry_count = 0;
resolve(err);
}
};
re_register();
});
//if (!logout && err == 1) this.emit("internal.error.token")
//if (!logout && err == 2) this.emit("internal.error.network", -3, "server is busy(register)")
return err;
}
async syncTimeDiff() {
const pkt = await buildLoginPacket.call(this, "Client.CorrectTime", constants_1.BUF4, 0);
this[FN_SEND](pkt).then(buf => {
try {
this.sig.time_diff = buf.readInt32BE() - (0, constants_1.timestamp)();
}
catch {
}
}).catch(constants_1.NOOP);
}
async token_expire(data = {}) {
this.emit("internal.error.token", data?.retcode, data?.retmsg);
}
sendHeartbeat() {
return new Promise(async (resolve, reject) => {
if (typeof this.heartbeat === "function") {
try {
await this.heartbeat();
}
catch { }
}
this.syncTimeDiff();
this[FN_SEND](await buildLoginPacket.call(this, "Heartbeat.Alive", constants_1.BUF0, 0), 10).catch(async () => {
this.emit("internal.verbose", "Heartbeat.Alive timeout", VerboseLevel.Warn);
this[FN_SEND](await buildLoginPacket.call(this, "Heartbeat.Alive", constants_1.BUF0, 0), 10).catch((e) => {
this.emit("internal.verbose", "Heartbeat.Alive timeout x 2", VerboseLevel.Warn);
reject(e);
});
}).catch(reject).then(resolve);
});
}
}
exports.BaseClient = BaseClient;
const EVENT_KICKOFF = Symbol("EVENT_KICKOFF");
function ssoListener(cmd, payload, seq) {
switch (cmd) {
case "StatSvc.ReqMSFOffline":
case "MessageSvc.PushForceOffline":
{
this.logout();
const nested = jce.decodeWrapper(payload);
const msg = nested[4] ? `[${nested[4]}]${nested[3]}` : `[${nested[1]}]${nested[2]}`;
this.emit(EVENT_KICKOFF, msg);
}
break;
case "QualityTest.PushList":
this.writeUni(cmd, constants_1.BUF0, seq);
break;
case "OnlinePush.SidTicketExpired":
this.writeUni(cmd, constants_1.BUF0, seq);
break;
case "ConfigPushSvc.PushReq":
{
if (payload[0] === 0)
payload = payload.slice(4);
const nested = jce.decodeWrapper(payload);
if (nested[1] === 2 && nested[2]) {
const buf = jce.decode(nested[2])[5][5];
const decoded = pb.decode(buf)[1281];
try {
this.sig.bigdata.sig_session = decoded[1].toBuffer();
this.sig.bigdata.session_key = decoded[2].toBuffer();
for (let v of decoded[3]) {
if (v[1] === 10) {
this.sig.bigdata.port = v[2][0][3];
this.sig.bigdata.ip = (0, constants_1.int32ip2str)(v[2][0][2]);
}
}
save_bigdata.call(this, buf);
}
catch {
this.sig.bigdata.sig_session = Buffer.from('');
this.sig.bigdata.session_key = Buffer.from('');
this.sig.bigdata.port = 0;
this.sig.bigdata.ip = '';
}
}
ConfigPushSvc_PushResp.call(this, [nested[1], nested[3]]);
}
break;
}
}
function save_bigdata(data) {
try {
const fs = require('fs');
fs.writeFileSync(path.join(this.config.data_dir, this.uin + '_bigdata'), data);
}
catch { }
}
function read_bigdata() {
try {
const fs = require('fs');
const file = path.join(this.config.data_dir, this.uin + '_bigdata');
if (!fs.existsSync(file))