refactor: packet highway & etc, kill some todo

This commit is contained in:
pk5ls20
2024-10-25 05:11:10 +08:00
parent 698947ed97
commit d3405edd42
6 changed files with 66 additions and 50 deletions

View File

@@ -150,7 +150,7 @@ export class NTQQPacketApi {
} }
} }
} }
return Promise.all(reqList); // TODO: use promise.allSettled return Promise.allSettled(reqList);
} }
async sendUploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) { async sendUploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {

View File

@@ -36,7 +36,7 @@ export class PacketHighwayClient {
this.port = port; this.port = port;
} }
private buildDataUpTrans(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array, timeout: number = 3600): PacketHighwayTrans { private buildDataUpTrans(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array, timeout: number = 1200): PacketHighwayTrans {
return { return {
uin: this.sig.uin, uin: this.sig.uin,
cmd: cmd, cmd: cmd,

View File

@@ -59,7 +59,6 @@ export class PacketHighwaySession {
private async checkAvailable() { private async checkAvailable() {
if (!this.packetClient.available) { if (!this.packetClient.available) {
this.logger.logError('[Highway] packetServer not available!');
throw new Error('packetServer不可用请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置'); throw new Error('packetServer不可用请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置');
} }
if (this.sig.sigSession === null || this.sig.sessionKey === null) { if (this.sig.sigSession === null || this.sig.sessionKey === null) {
@@ -96,7 +95,7 @@ export class PacketHighwaySession {
async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> { async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> {
await this.checkAvailable(); await this.checkAvailable();
if (peer.chatType === ChatType.KCHATTYPEGROUP) { if (peer.chatType === ChatType.KCHATTYPEGROUP) {
await this.uploadGroupImageReq(Number(peer.peerUid), img); await this.uploadGroupImageReq(+peer.peerUid, img);
} else if (peer.chatType === ChatType.KCHATTYPEC2C) { } else if (peer.chatType === ChatType.KCHATTYPEC2C) {
await this.uploadC2CImageReq(peer.peerUid, img); await this.uploadC2CImageReq(peer.peerUid, img);
} else { } else {
@@ -107,7 +106,7 @@ export class PacketHighwaySession {
async uploadVideo(peer: Peer, video: PacketMsgVideoElement): Promise<void> { async uploadVideo(peer: Peer, video: PacketMsgVideoElement): Promise<void> {
await this.checkAvailable(); await this.checkAvailable();
if (peer.chatType === ChatType.KCHATTYPEGROUP) { if (peer.chatType === ChatType.KCHATTYPEGROUP) {
await this.uploadGroupVideoReq(Number(peer.peerUid), video); await this.uploadGroupVideoReq(+peer.peerUid, video);
} else if (peer.chatType === ChatType.KCHATTYPEC2C) { } else if (peer.chatType === ChatType.KCHATTYPEC2C) {
await this.uploadC2CVideoReq(peer.peerUid, video); await this.uploadC2CVideoReq(peer.peerUid, video);
} else { } else {
@@ -118,7 +117,7 @@ export class PacketHighwaySession {
async uploadPtt(peer: Peer, ptt: PacketMsgPttElement): Promise<void> { async uploadPtt(peer: Peer, ptt: PacketMsgPttElement): Promise<void> {
await this.checkAvailable(); await this.checkAvailable();
if (peer.chatType === ChatType.KCHATTYPEGROUP) { if (peer.chatType === ChatType.KCHATTYPEGROUP) {
await this.uploadGroupPttReq(Number(peer.peerUid), ptt); await this.uploadGroupPttReq(+peer.peerUid, ptt);
} else if (peer.chatType === ChatType.KCHATTYPEC2C) { } else if (peer.chatType === ChatType.KCHATTYPEC2C) {
await this.uploadC2CPttReq(peer.peerUid, ptt); await this.uploadC2CPttReq(peer.peerUid, ptt);
} else { } else {
@@ -129,7 +128,7 @@ export class PacketHighwaySession {
async uploadFile(peer: Peer, file: PacketMsgFileElement): Promise<void> { async uploadFile(peer: Peer, file: PacketMsgFileElement): Promise<void> {
await this.checkAvailable(); await this.checkAvailable();
if (peer.chatType === ChatType.KCHATTYPEGROUP) { if (peer.chatType === ChatType.KCHATTYPEGROUP) {
await this.uploadGroupFileReq(Number(peer.peerUid), file); await this.uploadGroupFileReq(+peer.peerUid, file);
} else if (peer.chatType === ChatType.KCHATTYPEC2C) { } else if (peer.chatType === ChatType.KCHATTYPEC2C) {
await this.uploadC2CFileReq(peer.peerUid, file); await this.uploadC2CFileReq(peer.peerUid, file);
} else { } else {

View File

@@ -19,11 +19,20 @@ abstract class HighwayUploader {
this.logger = logger; this.logger = logger;
} }
encryptTransExt(key: Uint8Array) { private encryptTransExt(key: Uint8Array) {
if (!this.trans.encrypt) return; if (!this.trans.encrypt) return;
this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key)); this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key));
} }
protected timeout(): Promise<void> {
return new Promise<void>((_, reject) => {
setTimeout(() => {
reject(new Error(`[Highway] tcpUpload timeout after ${this.trans.timeout}s`));
}, (this.trans.timeout ?? Infinity) * 1000
);
})
}
buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array { buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array {
return new NapProtoMsg(ReqDataHighwayHead).encode({ return new NapProtoMsg(ReqDataHighwayHead).encode({
msgBaseHead: { msgBaseHead: {
@@ -86,16 +95,18 @@ class HighwayTcpUploaderTransform extends stream.Transform {
export class HighwayTcpUploader extends HighwayUploader { export class HighwayTcpUploader extends HighwayUploader {
async upload(): Promise<void> { async upload(): Promise<void> {
const controller = new AbortController();
const { signal } = controller;
const upload = new Promise<void>((resolve, reject) => {
const highwayTransForm = new HighwayTcpUploaderTransform(this); const highwayTransForm = new HighwayTcpUploaderTransform(this);
const upload = new Promise<void>((resolve, _) => {
const socket = net.connect(this.trans.port, this.trans.server, () => { const socket = net.connect(this.trans.port, this.trans.server, () => {
this.trans.data.pipe(highwayTransForm).pipe(socket, {end: false}); this.trans.data.pipe(highwayTransForm).pipe(socket, {end: false});
}); });
const handleRspHeader = (header: Buffer) => { const handleRspHeader = (header: Buffer) => {
const rsp = new NapProtoMsg(RespDataHighwayHead).decode(header); const rsp = new NapProtoMsg(RespDataHighwayHead).decode(header);
if (rsp.errorCode !== 0) { if (rsp.errorCode !== 0) {
// TODO: immediately reject promise if error code is not 0 socket.end();
this.logger.logWarn(`[Highway] tcpUpload failed (code: ${rsp.errorCode})`); reject(new Error(`[Highway] tcpUpload failed (code=${rsp.errorCode})`));
} }
const percent = ((Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength)) / Number(rsp.msgSegHead?.filesize)).toFixed(2); const percent = ((Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength)) / Number(rsp.msgSegHead?.filesize)).toFixed(2);
this.logger.logDebug(`[Highway] tcpUpload ${rsp.errorCode} | ${percent} | ${Buffer.from(header).toString('hex')}`); this.logger.logDebug(`[Highway] tcpUpload ${rsp.errorCode} | ${percent} | ${Buffer.from(header).toString('hex')}`);
@@ -106,49 +117,58 @@ export class HighwayTcpUploader extends HighwayUploader {
} }
}; };
socket.on('data', (chunk: Buffer) => { socket.on('data', (chunk: Buffer) => {
try { if (signal.aborted) {
socket.end();
reject(new Error('Upload aborted due to timeout'));
}
const [head, _] = Frame.unpack(chunk); const [head, _] = Frame.unpack(chunk);
handleRspHeader(head); handleRspHeader(head);
} catch (e) {
this.logger.logError(`[Highway] tcpUpload parse response error: ${e}`);
}
}); });
socket.on('close', () => { socket.on('close', () => {
this.logger.logDebug('[Highway] tcpUpload socket closed.'); this.logger.logDebug('[Highway] tcpUpload socket closed.');
resolve(); resolve();
}); });
socket.on('error', (err) => { socket.on('error', (err) => {
this.logger.logError('[Highway] tcpUpload socket.on error:', err); socket.end();
reject(new Error(`[Highway] tcpUpload socket.on error: ${err}`));
}); });
this.trans.data.on('error', (err) => { this.trans.data.on('error', (err) => {
this.logger.logError('[Highway] tcpUpload readable error:', err);
socket.end(); socket.end();
reject(new Error(`[Highway] tcpUpload readable error: ${err}`));
}); });
}); });
const timeout = new Promise<void>((_, reject) => { const timeout = this.timeout().then(() => {
setTimeout(() => { controller.abort();
reject(new Error(`[Highway] tcpUpload timeout after ${this.trans.timeout}s`)); throw new Error('Highway TCP Upload timed out');
}, (this.trans.timeout ?? Infinity) * 1000
);
}); });
await Promise.race([upload, timeout]); await Promise.race([upload, timeout]);
} }
} }
// TODO: timeout impl
export class HighwayHttpUploader extends HighwayUploader { export class HighwayHttpUploader extends HighwayUploader {
async upload(): Promise<void> { async upload(): Promise<void> {
const controller = new AbortController();
const { signal } = controller;
const upload = (async () => {
let offset = 0; let offset = 0;
for await (const chunk of this.trans.data) { for await (const chunk of this.trans.data) {
if (signal.aborted) {
throw new Error('Upload aborted due to timeout');
}
const block = chunk as Buffer; const block = chunk as Buffer;
try { try {
await this.uploadBlock(block, offset); await this.uploadBlock(block, offset);
} catch (err) { } catch (err) {
this.logger.logError(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`); throw new Error(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`)
throw err;
} }
offset += block.length; offset += block.length;
} }
})();
const timeout = this.timeout().then(() => {
controller.abort();
throw new Error('Highway HTTP Upload timed out');
});
await Promise.race([upload, timeout]);
} }
private async uploadBlock(block: Buffer, offset: number): Promise<void> { private async uploadBlock(block: Buffer, offset: number): Promise<void> {
@@ -159,10 +179,7 @@ export class HighwayHttpUploader extends HighwayUploader {
const [head, body] = Frame.unpack(resp); const [head, body] = Frame.unpack(resp);
const headData = new NapProtoMsg(RespDataHighwayHead).decode(head); const headData = new NapProtoMsg(RespDataHighwayHead).decode(head);
this.logger.logDebug(`[Highway] httpUploadBlock: ${headData.errorCode} | ${headData.msgSegHead?.retCode} | ${headData.bytesRspExtendInfo} | ${head.toString('hex')} | ${body.toString('hex')}`); this.logger.logDebug(`[Highway] httpUploadBlock: ${headData.errorCode} | ${headData.msgSegHead?.retCode} | ${headData.bytesRspExtendInfo} | ${head.toString('hex')} | ${body.toString('hex')}`);
if (headData.errorCode !== 0) { if (headData.errorCode !== 0) throw new Error(`[Highway] httpUploadBlock failed (code=${headData.errorCode})`);
// TODO: immediately throw error if error code is not 0
this.logger.logError(`[Highway] httpUploadBlock failed (code=${headData.errorCode})`);
}
} }
private async httpPostHighwayContent(frame: Buffer, serverURL: string): Promise<Buffer> { private async httpPostHighwayContent(frame: Buffer, serverURL: string): Promise<Buffer> {
@@ -178,12 +195,12 @@ export class HighwayHttpUploader extends HighwayUploader {
}, },
}; };
const req = http.request(serverURL, options, (res) => { const req = http.request(serverURL, options, (res) => {
let data = Buffer.alloc(0); const data: Buffer[] = [];
res.on('data', (chunk) => { res.on('data', (chunk) => {
data = Buffer.concat([data, chunk]); data.push(chunk);
}); });
res.on('end', () => { res.on('end', () => {
resolve(data); resolve(Buffer.concat(data));
}); });
}); });
req.write(frame); req.write(frame);

View File

@@ -114,7 +114,7 @@ export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
super(element); super(element);
this.path = element.picElement.sourcePath; this.path = element.picElement.sourcePath;
this.name = element.picElement.fileName; this.name = element.picElement.fileName;
this.size = Number(element.picElement.fileSize); this.size = +element.picElement.fileSize;
this.md5 = element.picElement.md5HexStr ?? ''; this.md5 = element.picElement.md5HexStr ?? '';
this.width = element.picElement.picWidth; this.width = element.picElement.picWidth;
this.height = element.picElement.picHeight; this.height = element.picElement.picHeight;
@@ -149,11 +149,11 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
constructor(element: SendReplyElement) { constructor(element: SendReplyElement) {
super(element); super(element);
this.messageId = BigInt(element.replyElement.replayMsgId ?? 0); this.messageId = BigInt(element.replyElement.replayMsgId ?? 0);
this.messageSeq = Number(element.replyElement.replayMsgSeq ?? 0); this.messageSeq = +(element.replyElement.replayMsgSeq ?? 0);
this.messageClientSeq = Number(element.replyElement.replyMsgClientSeq ?? 0); this.messageClientSeq = +(element.replyElement.replyMsgClientSeq ?? 0);
this.targetUin = Number(element.replyElement.senderUin ?? 0); this.targetUin = +(element.replyElement.senderUin ?? 0);
this.targetUid = element.replyElement.senderUidStr ?? ''; this.targetUid = element.replyElement.senderUidStr ?? '';
this.time = Number(element.replyElement.replyMsgTime ?? 0); this.time = +(element.replyElement.replyMsgTime ?? 0);
this.elems = []; // TODO: in replyElement.sourceMsgTextElems this.elems = []; // TODO: in replyElement.sourceMsgTextElems
} }

View File

@@ -186,7 +186,7 @@ export class PacketPacker {
uploadInfo: [ uploadInfo: [
{ {
fileInfo: { fileInfo: {
fileSize: Number(img.size), fileSize: +img.size,
fileHash: img.md5, fileHash: img.md5,
fileSha1: this.toHexStr(await calculateSha1(img.path)), fileSha1: this.toHexStr(await calculateSha1(img.path)),
fileName: img.name, fileName: img.name,
@@ -254,7 +254,7 @@ export class PacketPacker {
uploadInfo: [ uploadInfo: [
{ {
fileInfo: { fileInfo: {
fileSize: Number(img.size), fileSize: +img.size,
fileHash: img.md5, fileHash: img.md5,
fileSha1: this.toHexStr(await calculateSha1(img.path)), fileSha1: this.toHexStr(await calculateSha1(img.path)),
fileName: img.name, fileName: img.name,