mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-12-19 13:10:16 +08:00
feat: new napcat-4.9.0-beta
This commit is contained in:
parent
f9c9b3a852
commit
6778bd69de
@ -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 {
|
||||
|
||||
@ -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(
|
||||
{
|
||||
|
||||
@ -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';
|
||||
|
||||
2
src/core/external/appid.json
vendored
2
src/core/external/appid.json
vendored
@ -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"
|
||||
},
|
||||
|
||||
6
src/core/external/napi2native.json
vendored
Normal file
6
src/core/external/napi2native.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"9.9.22-40990-x64": {
|
||||
"send": "1B5699C",
|
||||
"recv": "1D8CA9D"
|
||||
}
|
||||
}
|
||||
@ -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<void>;
|
||||
|
||||
abstract sendCommandImpl(cmd: string, data: string, hash: string, timeout: number): void;
|
||||
|
||||
private async sendCommand(cmd: string, data: string, trace_data: string, rsp: boolean = false, timeout: number = 20000, sendcb: (json: RecvPacketData) => void = () => {
|
||||
}): Promise<RecvPacketData> {
|
||||
return new Promise<RecvPacketData>((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}`));
|
||||
}, timeout);
|
||||
this.cb.set(hash + 'send', async (json: RecvPacketData) => {
|
||||
sendcb(json);
|
||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false, timeout = 5000): Promise<RecvPacketData> {
|
||||
if (!rsp) {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
}
|
||||
});
|
||||
|
||||
if (rsp) {
|
||||
this.cb.set(hash + 'recv', async (json: RecvPacketData) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
});
|
||||
}
|
||||
this.sendCommandImpl(cmd, data, hash, timeout);
|
||||
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) };
|
||||
}
|
||||
|
||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false, timeout = 20000): Promise<RecvPacketData> {
|
||||
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);
|
||||
const task = new CancelableTask<RecvPacketData>((resolve, reject, onCancel) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
reject(new Error(`[PacketClient] sendPacket 超时 cmd=${cmd} timeout=${timeout}ms`));
|
||||
}, timeout);
|
||||
|
||||
onCancel(() => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
return await task;
|
||||
}
|
||||
|
||||
async sendOidbPacket(pkt: OidbPacket, rsp = false, timeout = 20000): Promise<RecvPacketData> {
|
||||
return this.sendPacket(pkt.cmd, pkt.data, rsp, timeout);
|
||||
async sendOidbPacket(pkt: OidbPacket, rsp = false, timeout = 5000): Promise<RecvPacketData> {
|
||||
return await this.sendPacket(pkt.cmd, pkt.data, rsp, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<number, string>(); // seq - hash
|
||||
private readonly timeEvent = new Map<string, NodeJS.Timeout>(); // 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<void> {
|
||||
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');
|
||||
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, (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.MoeHooExport?.exports.initHook?.(send, recv);
|
||||
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: '' });
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ export class PacketClientContext {
|
||||
|
||||
async sendOidbPacket<T extends boolean = false>(pkt: OidbPacket, rsp?: T, timeout?: number): Promise<T extends true ? Buffer : void> {
|
||||
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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<typeof IndexNode>,timeout: number = 20000) {
|
||||
async GetPttUrl(selfUid: string, node: NapProtoEncodeStructType<typeof IndexNode>, 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<typeof IndexNode>, timeout: number = 20000) {
|
||||
async GetVideoUrl(selfUid: string, node: NapProtoEncodeStructType<typeof IndexNode>, 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<typeof IndexNode>, timeout: number = 20000) {
|
||||
async GetGroupImageUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>, 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<typeof IndexNode>, timeout: number = 20000) {
|
||||
async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>, 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);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/native/packet/napi2native.win32.x64.node
Normal file
BIN
src/native/packet/napi2native.win32.x64.node
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user