oicq
Version:
QQ protocol!
276 lines (275 loc) • 11.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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 };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.highwayHttpUpload = exports.highwayUpload = exports.CmdID = void 0;
const stream = __importStar(require("stream"));
const net = __importStar(require("net"));
const crypto_1 = require("crypto");
const http_1 = __importDefault(require("http"));
const axios_1 = __importDefault(require("axios"));
const core_1 = require("../core");
const errors_1 = require("../errors");
const common_1 = require("../common");
var CmdID;
(function (CmdID) {
CmdID[CmdID["DmImage"] = 1] = "DmImage";
CmdID[CmdID["GroupImage"] = 2] = "GroupImage";
CmdID[CmdID["SelfPortrait"] = 5] = "SelfPortrait";
CmdID[CmdID["ShortVideo"] = 25] = "ShortVideo";
CmdID[CmdID["DmPtt"] = 26] = "DmPtt";
CmdID[CmdID["MultiMsg"] = 27] = "MultiMsg";
CmdID[CmdID["GroupPtt"] = 29] = "GroupPtt";
CmdID[CmdID["OfflineFile"] = 69] = "OfflineFile";
CmdID[CmdID["GroupFile"] = 71] = "GroupFile";
CmdID[CmdID["Ocr"] = 76] = "Ocr";
})(CmdID = exports.CmdID || (exports.CmdID = {}));
const __ = Buffer.from([41]);
class HighwayTransform extends stream.Transform {
constructor(c, obj) {
super();
this.c = c;
this.obj = obj;
this.seq = (0, crypto_1.randomBytes)(2).readUInt16BE();
this.offset = 0;
if (!obj.ticket)
this.obj.ticket = c.sig.bigdata.sig_session;
if (obj.encrypt && obj.ext)
this.obj.ext = core_1.tea.encrypt(obj.ext, c.sig.bigdata.session_key);
this.on("error", common_1.NOOP);
}
_transform(data, encoding, callback) {
let offset = 0, limit = 1048576;
while (offset < data.length) {
const chunk = data.slice(offset, limit + offset);
const head = core_1.pb.encode({
1: {
1: 1,
2: String(this.c.uin),
3: "PicUp.DataUp",
4: this.seq++,
6: this.c.apk.subid,
7: 4096,
8: this.obj.cmdid,
10: 2052,
},
2: {
2: this.obj.size,
3: this.offset + offset,
4: chunk.length,
6: this.obj.ticket,
8: (0, common_1.md5)(chunk),
9: this.obj.md5,
},
3: this.obj.ext
});
offset += chunk.length;
const _ = Buffer.allocUnsafe(9);
_.writeUInt8(40);
_.writeUInt32BE(head.length, 1);
_.writeUInt32BE(chunk.length, 5);
this.push(_);
this.push(head);
this.push(chunk);
this.push(__);
}
this.offset += data.length;
callback(null);
}
}
/** highway上传数据 (只能上传流) */
function highwayUpload(readable, obj, ip, port) {
ip = (0, common_1.int32ip2str)(ip || this.sig.bigdata.ip);
port = port || this.sig.bigdata.port;
if (!port)
throw new core_1.ApiRejection(errors_1.ErrorCode.NoUploadChannel, "没有上传通道,如果你刚刚登录,请等待几秒");
if (!readable)
throw new core_1.ApiRejection(errors_1.ErrorCode.HighwayFileTypeError, "不支持的file类型");
this.logger.debug(`highway ip:${ip} port:${port}`);
return new Promise((resolve, reject) => {
const highway = new HighwayTransform(this, obj);
const socket = net.connect(port, ip, () => { readable.pipe(highway).pipe(socket, { end: false }); });
const handleRspHeader = (header) => {
const rsp = core_1.pb.decode(header);
if (typeof rsp[3] === "number" && rsp[3] !== 0) {
this.logger.warn(`highway upload failed (code: ${rsp[3]})`);
readable.unpipe(highway).destroy();
highway.unpipe(socket).destroy();
socket.end();
reject(new core_1.ApiRejection(rsp[3], "unknown highway error"));
}
else {
const percentage = ((rsp[2][3] + rsp[2][4]) / rsp[2][2] * 100).toFixed(2);
this.logger.debug(`highway chunk uploaded (${percentage}%)`);
if (typeof obj.callback === "function")
obj.callback(percentage);
if (Number(percentage) >= 100) {
socket.end();
resolve(rsp[7]);
}
}
};
let buf = common_1.BUF0;
socket.on("data", (chunk) => {
try {
buf = buf.length ? Buffer.concat([buf, chunk]) : chunk;
while (buf.length >= 5) {
const len = buf.readInt32BE(1);
if (buf.length >= len + 10) {
handleRspHeader(buf.slice(9, len + 9));
buf = buf.slice(len + 10);
}
else {
break;
}
}
}
catch (err) {
this.logger.error(err);
}
});
socket.on("close", () => {
reject(new core_1.ApiRejection(errors_1.ErrorCode.HighwayNetworkError, "上传遇到网络错误"));
});
socket.on("error", (err) => {
this.logger.error(err);
});
readable.on("error", (err) => {
this.logger.error(err);
socket.end();
});
if (obj.timeout > 0) {
setTimeout(() => {
socket.end();
reject(new core_1.ApiRejection(errors_1.ErrorCode.HighwayTimeout, `上传超时(${obj.timeout}s)`));
}, obj.timeout * 1000);
}
});
}
exports.highwayUpload = highwayUpload;
const agent = new http_1.default.Agent({ maxSockets: 10 });
function highwayHttpUpload(readable, obj) {
const ip = this.sig.bigdata.ip;
const port = this.sig.bigdata.port;
if (!port)
throw new core_1.ApiRejection(errors_1.ErrorCode.NoUploadChannel, "没有上传通道,如果你刚刚登录,请等待几秒");
this.logger.debug(`highway(http) ip:${ip} port:${port}`);
const url = "http://" + ip + ":" + port + "/cgi-bin/httpconn?htcmd=0x6FF0087&uin=" + this.uin;
let seq = 1;
let offset = 0, limit = 524288;
obj.ticket = this.sig.bigdata.sig_session;
const tasks = new Set();
const cancels = new Set();
let finished = 0;
readable.on("data", data => {
let _offset = 0;
while (_offset < data.length) {
const chunk = data.slice(_offset, limit + _offset);
const head = core_1.pb.encode({
1: {
1: 1,
2: String(this.uin),
3: "PicUp.DataUp",
4: seq++,
5: 0,
6: this.apk.subid,
8: obj.cmdid,
},
2: {
1: 0,
2: obj.size,
3: offset + _offset,
4: chunk.length,
6: obj.ticket,
8: (0, common_1.md5)(chunk),
9: obj.md5,
10: 0,
13: 0,
},
3: obj.ext,
4: Date.now()
});
_offset += chunk.length;
const _ = Buffer.allocUnsafe(9);
_.writeUInt8(40);
_.writeUInt32BE(head.length, 1);
_.writeUInt32BE(chunk.length, 5);
const buf = Buffer.concat([_, head, chunk, __]);
const task = new Promise((resolve, reject) => {
const c = axios_1.default.CancelToken.source();
cancels.add(c);
axios_1.default.post(url, buf, {
responseType: "arraybuffer",
httpAgent: agent,
cancelToken: c.token,
headers: {
"Content-Length": String(buf.length)
}
}).then(r => {
let percentage, rsp;
try {
const buf = Buffer.from(r?.data);
const header = buf.slice(9, buf.length - 1);
rsp = core_1.pb.decode(header);
}
catch (err) {
this.logger.error(err);
reject(err);
return;
}
if (rsp?.[3] !== 0) {
reject(new core_1.ApiRejection(rsp[3], "unknown highway error"));
return;
}
++finished;
percentage = (finished / tasks.size * 100).toFixed(2);
this.logger.debug(`highway(http) chunk uploaded (${percentage}%)`);
if (typeof obj.callback === "function" && percentage)
obj.callback(percentage);
if (rsp[7]?.toBuffer().length > 0 && finished < tasks.size) {
cancels.forEach(c => c.cancel());
this.logger.debug(`highway(http) chunk uploaded (100.00%)`);
if (typeof obj.callback === "function")
obj.callback("100.00");
}
if (!rsp[7]?.toBuffer().length && finished >= tasks.size)
reject(new core_1.ApiRejection(errors_1.ErrorCode.UnsafeFile, "文件校验未通过,上传失败"));
resolve(undefined);
}).catch(reject);
});
tasks.add(task);
}
offset += data.length;
});
return new Promise((resolve, reject) => {
readable.on("err", reject)
.on("end", () => {
Promise.all(tasks).then(resolve).catch(err => {
if (err instanceof axios_1.default.Cancel === false)
reject(err);
resolve(undefined);
});
});
});
}
exports.highwayHttpUpload = highwayHttpUpload;