diff --git a/src/core/apis/file.ts b/src/core/apis/file.ts index 14bf8b87..48b650ae 100644 --- a/src/core/apis/file.ts +++ b/src/core/apis/file.ts @@ -64,7 +64,7 @@ export class NTQQFileApi { } } - async getFileUrl(chatType: ChatType, peer: string, fileUUID?: string, file10MMd5?: string | undefined,timeout: number = 20000) { + async getFileUrl(chatType: ChatType, peer: string, fileUUID?: string, file10MMd5?: string | undefined,timeout: number = 5000) { if (this.core.apis.PacketApi.packetStatus) { try { if (chatType === ChatType.KCHATTYPEGROUP && fileUUID) { @@ -79,7 +79,7 @@ export class NTQQFileApi { throw new Error('fileUUID or file10MMd5 is undefined'); } - async getPttUrl(peer: string, fileUUID?: string,timeout: number = 20000) { + async getPttUrl(peer: string, fileUUID?: string,timeout: number = 5000) { if (this.core.apis.PacketApi.packetStatus && fileUUID) { let appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid; try { @@ -107,7 +107,7 @@ export class NTQQFileApi { throw new Error('packet cant get ptt url'); } - async getVideoUrlPacket(peer: string, fileUUID?: string,timeout: number = 20000) { + async getVideoUrlPacket(peer: string, fileUUID?: string,timeout: number = 5000) { if (this.core.apis.PacketApi.packetStatus && fileUUID) { let appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid; try { diff --git a/src/core/apis/group.ts b/src/core/apis/group.ts index dc70f4f3..ca8de1c8 100644 --- a/src/core/apis/group.ts +++ b/src/core/apis/group.ts @@ -13,12 +13,11 @@ import { Peer, ChatType, } from '@/core'; -import { isNumeric, sleep, solveAsyncProblem } from '@/common/helper'; +import { isNumeric, solveAsyncProblem } from '@/common/helper'; import { LimitedHashTable } from '@/common/message-unique'; import { NTEventWrapper } from '@/common/event'; import { CancelableTask, TaskExecutor } from '@/common/cancel-task'; import { createGroupDetailInfoV2Param, createGroupExtFilter, createGroupExtInfo } from '../data'; -import { dlopen } from 'node:process'; export class NTQQGroupApi { context: InstanceContext; @@ -49,12 +48,7 @@ export class NTQQGroupApi { async initApi() { this.initCache().then().catch(e => this.context.logger.logError(e)); - let napcatNativeModule = { exports: {} }; - dlopen(napcatNativeModule, "E:\\NewDevelop\\Napi2Native\\build\\Release\\napi2native.node"); - console.log(await this.context.session.getMsgService().sendSsoCmdReqByContend('OidbSvcTrpcTcp.0xed3_1', '0aed030100000000000000000000000000000000000000000000000000000000')); - console.log(await this.context.session.getMsgService().sendSsoCmdReqByContend('OidbSvcTrpcTcp.0xed3_1', Buffer.from('0aed030100000000000000000000000000000000000000000000000000000000', 'hex'))); } - async createGrayTip(groupCode: string, tip: string) { return this.context.session.getMsgService().addLocalJsonGrayTipMsg( { diff --git a/src/core/apis/packet.ts b/src/core/apis/packet.ts index 1c9db417..197213ef 100644 --- a/src/core/apis/packet.ts +++ b/src/core/apis/packet.ts @@ -1,5 +1,5 @@ import * as os from 'os'; -import offset from '@/core/external/offset.json'; +import offset from '@/core/external/napi2native.json'; import { InstanceContext, NapCatCore } from '@/core'; import { LogWrapper } from '@/common/log'; import { PacketClientSession } from '@/core/packet/clientSession'; diff --git a/src/core/external/appid.json b/src/core/external/appid.json index 9224f4fb..dc4d0b4b 100644 --- a/src/core/external/appid.json +++ b/src/core/external/appid.json @@ -419,7 +419,7 @@ "appid": 537319880, "qua": "V1_MAC_NQ_6.9.82_40990_GW_B" }, - "9.9.22.40990": { + "9.9.22-40990": { "appid": 537319855, "qua": "V1_WIN_NQ_9.9.22.40990_GW_B" }, diff --git a/src/core/external/napi2native.json b/src/core/external/napi2native.json new file mode 100644 index 00000000..1c4ca987 --- /dev/null +++ b/src/core/external/napi2native.json @@ -0,0 +1,6 @@ +{ + "9.9.22-40990-x64": { + "send": "1B5699C", + "recv": "1D8CA9D" + } +} \ No newline at end of file diff --git a/src/core/packet/client/baseClient.ts b/src/core/packet/client/baseClient.ts index a6025049..6eb0f3b1 100644 --- a/src/core/packet/client/baseClient.ts +++ b/src/core/packet/client/baseClient.ts @@ -1,8 +1,8 @@ -import crypto, { createHash } from 'crypto'; import { OidbPacket, PacketHexStr } from '@/core/packet/transformer/base'; import { LogStack } from '@/core/packet/context/clientContext'; import { NapCoreContext } from '@/core/packet/context/napCoreContext'; import { PacketLogger } from '@/core/packet/context/loggerContext'; +import { CancelableTask } from '@/common/cancel-task'; export interface RecvPacket { type: string, // 仅recv @@ -12,16 +12,7 @@ export interface RecvPacket { export interface RecvPacketData { seq: number cmd: string - hex_data: string -} - -function randText(len: number): string { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < len; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; + data: Buffer } @@ -42,47 +33,43 @@ export abstract class IPacketClient { abstract init(pid: number, recv: string, send: string): Promise; - abstract sendCommandImpl(cmd: string, data: string, hash: string, timeout: number): void; + async sendPacket(cmd: string, data: PacketHexStr, rsp = false, timeout = 5000): Promise { + if (!rsp) { + this.napcore.sendSsoCmdReqByContend(cmd, Buffer.from(data, 'hex')).catch(err => { + this.logger.error(`[PacketClient] sendPacket 无响应命令发送失败 cmd=${cmd} err=${err}`); + }); + return { seq: 0, cmd: cmd, data: Buffer.alloc(0) }; + } - private async sendCommand(cmd: string, data: string, trace_data: string, rsp: boolean = false, timeout: number = 20000, sendcb: (json: RecvPacketData) => void = () => { - }): Promise { - return new Promise((resolve, reject) => { - if (!this.available) { - reject(new Error('packetBackend 当前不可用!')); - } - let hash = createHash('md5').update(trace_data).digest('hex'); - const timeoutHandle = setTimeout(() => { - this.cb.delete(hash + 'send'); - this.cb.delete(hash + 'recv'); - reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with hash ${hash}`)); + const task = new CancelableTask((resolve, reject, onCancel) => { + const timeoutId = setTimeout(() => { + reject(new Error(`[PacketClient] sendPacket 超时 cmd=${cmd} timeout=${timeout}ms`)); }, timeout); - this.cb.set(hash + 'send', async (json: RecvPacketData) => { - sendcb(json); - if (!rsp) { - clearTimeout(timeoutHandle); - resolve(json); - } + + onCancel(() => { + clearTimeout(timeoutId); }); - if (rsp) { - this.cb.set(hash + 'recv', async (json: RecvPacketData) => { - clearTimeout(timeoutHandle); - resolve(json); + this.napcore.sendSsoCmdReqByContend(cmd, Buffer.from(data, 'hex')) + .then(ret => { + clearTimeout(timeoutId); + const result = ret as { rspbuffer: Buffer }; + resolve({ + seq: 0, + cmd: cmd, + data: result.rspbuffer + }); + }) + .catch(err => { + clearTimeout(timeoutId); + reject(err); }); - } - this.sendCommandImpl(cmd, data, hash, timeout); }); + + return await task; } - async sendPacket(cmd: string, data: PacketHexStr, rsp = false, timeout = 20000): Promise { - const md5 = crypto.createHash('md5').update(data).digest('hex'); - const trace_data = (randText(4) + md5 + data).slice(0, data.length / 2);// trace_data - return this.sendCommand(cmd, data, trace_data, rsp, timeout, async () => { - await this.napcore.sendSsoCmdReqByContend(cmd, trace_data); - }); - } - - async sendOidbPacket(pkt: OidbPacket, rsp = false, timeout = 20000): Promise { - return this.sendPacket(pkt.cmd, pkt.data, rsp, timeout); + async sendOidbPacket(pkt: OidbPacket, rsp = false, timeout = 5000): Promise { + return await this.sendPacket(pkt.cmd, pkt.data, rsp, timeout); } } diff --git a/src/core/packet/client/nativeClient.ts b/src/core/packet/client/nativeClient.ts index a594f3a6..d92ff492 100644 --- a/src/core/packet/client/nativeClient.ts +++ b/src/core/packet/client/nativeClient.ts @@ -1,4 +1,3 @@ -import { createHash } from 'crypto'; import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; @@ -10,16 +9,12 @@ import { PacketLogger } from '@/core/packet/context/loggerContext'; // 0 send 1 recv export interface NativePacketExportType { - InitHook?: (send: string, recv: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void, o3_hook: boolean) => boolean; - SendPacket?: (cmd: string, data: string, trace_id: string) => void; + initHook?: (send: string, recv: string) => boolean; } export class NativePacketClient extends IPacketClient { private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64']; private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} }; - private readonly sendEvent = new Map(); // seq - hash - private readonly timeEvent = new Map(); // hash - timeout - constructor(napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) { super(napCore, logger, logStack); } @@ -30,7 +25,7 @@ export class NativePacketClient extends IPacketClient { this.logStack.pushLogWarn(`NativePacketClient: 不支持的平台: ${platform}`); return false; } - const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node'); + const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/napi2native.' + platform + '.node'); if (!fs.existsSync(moehoo_path)) { this.logStack.pushLogWarn(`NativePacketClient: 缺失运行时文件: ${moehoo_path}`); return false; @@ -40,37 +35,12 @@ export class NativePacketClient extends IPacketClient { async init(_pid: number, recv: string, send: string): Promise { const platform = process.platform + '.' + process.arch; - const isNewQQ = this.napcore.basicInfo.requireMinNTQQBuild("36580"); - const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + (isNewQQ ? '.new' : '') + '.node'); - process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY); - - this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, _uin: string, cmd: string, seq: number, hex_data: string) => { - const hash = createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex'); - if (type === 0 && this.cb.get(hash + 'recv')) { - //此时为send 提取seq - this.sendEvent.set(seq, hash); - setTimeout(() => { - this.sendEvent.delete(seq); - this.timeEvent.delete(hash); - }, +(this.timeEvent.get(hash) ?? 20000)); - //正式send完成 无recv v - //均无异常 v - } - if (type === 1 && this.sendEvent.get(seq)) { - const hash = this.sendEvent.get(seq); - const callback = this.cb.get(hash + 'recv'); - callback?.({ seq, cmd, hex_data }); - } - this.logger.info(`[NativePacketClient] ${type === 0 ? 'Send' : 'Recv'} - CMD: ${cmd} SEQ: ${seq} DATA: ${hex_data}`);//TODO log - }, this.napcore.config.o3HookMode == 1); - this.available = true; - } - - sendCommandImpl(cmd: string, data: string, hash: string, timeout: number): void { - this.timeEvent.set(hash, setTimeout(() => { - this.timeEvent.delete(hash);//考虑情况为正式send都没进 - }, timeout)); - this.MoeHooExport.exports.SendPacket?.(cmd, data, hash); - this.cb.get(hash + 'send')?.({ seq: 0, cmd, hex_data: '' }); + const isNewQQ = this.napcore.basicInfo.requireMinNTQQBuild("40824"); + if (isNewQQ) { + const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/napi2native.' + platform + '.node'); + process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY); + this.MoeHooExport?.exports.initHook?.(send, recv); + this.available = true; + } } } diff --git a/src/core/packet/context/clientContext.ts b/src/core/packet/context/clientContext.ts index 585e4a77..4cef67ea 100644 --- a/src/core/packet/context/clientContext.ts +++ b/src/core/packet/context/clientContext.ts @@ -75,24 +75,24 @@ export class PacketClientContext { async sendOidbPacket(pkt: OidbPacket, rsp?: T, timeout?: number): Promise { const raw = await this._client.sendOidbPacket(pkt, rsp, timeout); - return (rsp ? Buffer.from(raw.hex_data, 'hex') : undefined) as T extends true ? Buffer : void; + return raw.data as T extends true ? Buffer : void; } private newClient(): IPacketClient { const prefer = this.napCore.config.packetBackend; let client: IPacketClient | null; switch (prefer) { - case 'native': - this.logger.info('使用指定的 NativePacketClient 作为后端'); - client = new NativePacketClient(this.napCore, this.logger, this.logStack); - break; - case 'auto': - case undefined: - client = this.judgeClient(); - break; - default: - this.logger.error(`未知的PacketBackend ${prefer},请检查配置文件!`); - client = null; + case 'native': + this.logger.info('使用指定的 NativePacketClient 作为后端'); + client = new NativePacketClient(this.napCore, this.logger, this.logStack); + break; + case 'auto': + case undefined: + client = this.judgeClient(); + break; + default: + this.logger.error(`未知的PacketBackend ${prefer},请检查配置文件!`); + client = null; } if (!client?.check()) { throw new Error('[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!'); diff --git a/src/core/packet/context/napCoreContext.ts b/src/core/packet/context/napCoreContext.ts index ff7faa75..00e3f67e 100644 --- a/src/core/packet/context/napCoreContext.ts +++ b/src/core/packet/context/napCoreContext.ts @@ -34,5 +34,5 @@ export class NapCoreContext { return this.core.configLoader.configData; } - sendSsoCmdReqByContend = (cmd: string, trace_id: string) => this.core.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id); + sendSsoCmdReqByContend = (cmd: string, data: Buffer) => this.core.context.session.getMsgService().sendSsoCmdReqByContend(cmd, data); } diff --git a/src/core/packet/context/operationContext.ts b/src/core/packet/context/operationContext.ts index e3ab7de6..00c4143b 100644 --- a/src/core/packet/context/operationContext.ts +++ b/src/core/packet/context/operationContext.ts @@ -122,28 +122,28 @@ export class PacketOperationContext { return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; } - async GetPttUrl(selfUid: string, node: NapProtoEncodeStructType,timeout: number = 20000) { + async GetPttUrl(selfUid: string, node: NapProtoEncodeStructType, timeout?: number) { const req = trans.DownloadPtt.build(selfUid, node); const resp = await this.context.client.sendOidbPacket(req, true, timeout); const res = trans.DownloadPtt.parse(resp); return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; } - async GetVideoUrl(selfUid: string, node: NapProtoEncodeStructType, timeout: number = 20000) { + async GetVideoUrl(selfUid: string, node: NapProtoEncodeStructType, timeout?: number) { const req = trans.DownloadVideo.build(selfUid, node); const resp = await this.context.client.sendOidbPacket(req, true, timeout); const res = trans.DownloadVideo.parse(resp); return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; } - async GetGroupImageUrl(groupUin: number, node: NapProtoEncodeStructType, timeout: number = 20000) { + async GetGroupImageUrl(groupUin: number, node: NapProtoEncodeStructType, timeout?: number) { const req = trans.DownloadGroupImage.build(groupUin, node); const resp = await this.context.client.sendOidbPacket(req, true, timeout); const res = trans.DownloadImage.parse(resp); return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; } - async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType, timeout: number = 20000) { + async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType, timeout?: number) { const req = trans.DownloadGroupPtt.build(groupUin, node); const resp = await this.context.client.sendOidbPacket(req, true, timeout); const res = trans.DownloadImage.parse(resp); @@ -243,14 +243,14 @@ export class PacketOperationContext { return res.rename.retCode; } - async GetGroupFileUrl(groupUin: number, fileUUID: string,timeout: number = 20000) { + async GetGroupFileUrl(groupUin: number, fileUUID: string, timeout?: number) { const req = trans.DownloadGroupFile.build(groupUin, fileUUID); const resp = await this.context.client.sendOidbPacket(req, true, timeout); const res = trans.DownloadGroupFile.parse(resp); return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`; } - async GetPrivateFileUrl(self_id: string, fileUUID: string, md5: string, timeout: number = 20000) { + async GetPrivateFileUrl(self_id: string, fileUUID: string, md5: string, timeout?: number) { const req = trans.DownloadPrivateFile.build(self_id, fileUUID, md5); const resp = await this.context.client.sendOidbPacket(req, true, timeout); const res = trans.DownloadPrivateFile.parse(resp); diff --git a/src/native/packet/MoeHoo.darwin.arm64.node b/src/native/packet/MoeHoo.darwin.arm64.node deleted file mode 100644 index 829749c4..00000000 Binary files a/src/native/packet/MoeHoo.darwin.arm64.node and /dev/null differ diff --git a/src/native/packet/MoeHoo.darwin.x64.node b/src/native/packet/MoeHoo.darwin.x64.node deleted file mode 100644 index 66cfff98..00000000 Binary files a/src/native/packet/MoeHoo.darwin.x64.node and /dev/null differ diff --git a/src/native/packet/MoeHoo.linux.arm64.node b/src/native/packet/MoeHoo.linux.arm64.node deleted file mode 100644 index 4f248425..00000000 Binary files a/src/native/packet/MoeHoo.linux.arm64.node and /dev/null differ diff --git a/src/native/packet/MoeHoo.linux.x64.node b/src/native/packet/MoeHoo.linux.x64.node deleted file mode 100644 index 98287ac2..00000000 Binary files a/src/native/packet/MoeHoo.linux.x64.node and /dev/null differ diff --git a/src/native/packet/MoeHoo.win32.x64.node b/src/native/packet/MoeHoo.win32.x64.node deleted file mode 100644 index 0c5db04e..00000000 Binary files a/src/native/packet/MoeHoo.win32.x64.node and /dev/null differ diff --git a/src/native/packet/napi2native.win32.x64.node b/src/native/packet/napi2native.win32.x64.node new file mode 100644 index 00000000..5663bd8b Binary files /dev/null and b/src/native/packet/napi2native.win32.x64.node differ