feat: raw包能力增强完成

This commit is contained in:
手瓜一十雪 2025-10-30 10:58:02 +08:00
parent c5db525f4a
commit dc51d01351
6 changed files with 239 additions and 5 deletions

View File

@ -30,6 +30,7 @@ import os from 'node:os';
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
import { proxiedListenerOf } from '@/common/proxy-handler';
import { NTQQPacketApi } from './apis/packet';
import { NativePacketHandler } from './packet/handler/client';
export * from './wrapper';
export * from './types';
export * from './services';
@ -258,6 +259,7 @@ export interface InstanceContext {
readonly loginService: NodeIKernelLoginService;
readonly basicInfoWrapper: QQBasicInfoWrapper;
readonly pathWrapper: NapCatPathWrapper;
readonly packetHandler: NativePacketHandler;
}
export interface StableNTApiWrapper {

View File

@ -0,0 +1,214 @@
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
import { constants } from 'node:os';
import { LogWrapper } from '@/common/log';
import offset from '@/core/external/offset.json';
interface OffsetType {
[key: string]: {
recv: string;
send: string;
};
}
const typedOffset: OffsetType = offset;
// 0 send 1 recv
export interface NativePacketExportType {
initHook?: (send: string, recv: string, callback: (type: PacketType, uin: string, cmd: string, seq: number, hex_data: string) => void, o3_hook: boolean) => boolean;
}
export type PacketType = 0 | 1; // 0: send, 1: recv
export type PacketCallback = (data: { type: PacketType, uin: string, cmd: string, seq: number, hex_data: string }) => void;
interface ListenerEntry {
callback: PacketCallback;
once: boolean;
}
export class NativePacketHandler {
private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64'];
private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
protected readonly logger: LogWrapper;
// 统一的监听器存储 - key: 'all' | 'type:0' | 'type:1' | 'cmd:xxx' | 'exact:type:cmd'
private readonly listeners: Map<string, Set<ListenerEntry>> = new Map();
constructor({ logger }: { logger: LogWrapper }) {
this.logger = logger;
}
/**
*
*/
private addListener(key: string, callback: PacketCallback, once: boolean = false): () => void {
if (!this.listeners.has(key)) {
this.listeners.set(key, new Set());
}
const entry: ListenerEntry = { callback, once };
this.listeners.get(key)!.add(entry);
return () => this.removeListener(key, callback);
}
/**
*
*/
private removeListener(key: string, callback: PacketCallback): boolean {
const entries = this.listeners.get(key);
if (!entries) return false;
for (const entry of entries) {
if (entry.callback === callback) {
return entries.delete(entry);
}
}
return false;
}
// ===== 永久监听器 =====
/** 监听所有数据包 */
onAll(callback: PacketCallback): () => void {
return this.addListener('all', callback);
}
/** 监听特定类型的数据包 (0: send, 1: recv) */
onType(type: PacketType, callback: PacketCallback): () => void {
return this.addListener(`type:${type}`, callback);
}
/** 监听所有发送的数据包 */
onSend(callback: PacketCallback): () => void {
return this.onType(0, callback);
}
/** 监听所有接收的数据包 */
onRecv(callback: PacketCallback): () => void {
return this.onType(1, callback);
}
/** 监听特定cmd的数据包(不限type) */
onCmd(cmd: string, callback: PacketCallback): () => void {
return this.addListener(`cmd:${cmd}`, callback);
}
/** 监听特定type和cmd的数据包(精确匹配) */
onExact(type: PacketType, cmd: string, callback: PacketCallback): () => void {
return this.addListener(`exact:${type}:${cmd}`, callback);
}
// ===== 一次性监听器 =====
/** 一次性监听所有数据包 */
onceAll(callback: PacketCallback): () => void {
return this.addListener('all', callback, true);
}
/** 一次性监听特定类型的数据包 */
onceType(type: PacketType, callback: PacketCallback): () => void {
return this.addListener(`type:${type}`, callback, true);
}
/** 一次性监听所有发送的数据包 */
onceSend(callback: PacketCallback): () => void {
return this.onceType(0, callback);
}
/** 一次性监听所有接收的数据包 */
onceRecv(callback: PacketCallback): () => void {
return this.onceType(1, callback);
}
/** 一次性监听特定cmd的数据包 */
onceCmd(cmd: string, callback: PacketCallback): () => void {
return this.addListener(`cmd:${cmd}`, callback, true);
}
/** 一次性监听特定type和cmd的数据包 */
onceExact(type: PacketType, cmd: string, callback: PacketCallback): () => void {
return this.addListener(`exact:${type}:${cmd}`, callback, true);
}
// ===== 移除监听器 =====
/** 移除特定的全局监听器 */
off(key: string, callback: PacketCallback): boolean {
return this.removeListener(key, callback);
}
/** 移除特定key下的所有监听器 */
offAll(key: string): void {
this.listeners.delete(key);
}
/** 移除所有监听器 */
removeAllListeners(): void {
this.listeners.clear();
}
/**
* - 按优先级触发: 精确匹配 > cmd匹配 > type匹配 >
*/
private emitPacket(type: PacketType, uin: string, cmd: string, seq: number, hex_data: string): void {
const keys = [
`exact:${type}:${cmd}`, // 精确匹配
`cmd:${cmd}`, // cmd匹配
`type:${type}`, // type匹配
'all' // 全局
];
for (const key of keys) {
const entries = this.listeners.get(key);
if (!entries) continue;
const toRemove: ListenerEntry[] = [];
for (const entry of entries) {
try {
entry.callback({ type, uin, cmd, seq, hex_data });
if (entry.once) {
toRemove.push(entry);
}
} catch (error) {
this.logger.logError('监听器回调执行出错:', error);
}
}
// 移除一次性监听器
for (const entry of toRemove) {
entries.delete(entry);
}
}
}
async init(version: string): Promise<boolean> {
const version_arch = version + '-' + process.arch;
try {
const send = typedOffset[version_arch]?.send;
const recv = typedOffset[version_arch]?.recv;
if (!send || !recv) {
this.logger.logWarn(`NativePacketClient: 未找到对应版本的偏移数据: ${version_arch}`);
return false;
}
const platform = process.platform + '.' + process.arch;
if (!this.supportedPlatforms.includes(platform)) {
this.logger.logWarn(`NativePacketClient: 不支持的平台: ${platform}`);
return false;
}
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './native/packet/MoeHoo.' + platform + '.node');
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
if (!fs.existsSync(moehoo_path)) {
this.logger.logWarn(`NativePacketClient: 缺失运行时文件: ${moehoo_path}`);
return false;
}
this.MoeHooExport.exports.initHook?.(send, recv, (type: PacketType, uin: string, cmd: string, seq: number, hex_data: string) => {
this.emitPacket(type, uin, cmd, seq, hex_data);
}, true);
return true;
}
catch (error) {
this.logger.logError('NativePacketClient 初始化出错:', error);
return false;
}
}
}

View File

@ -10,6 +10,7 @@ import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';
import { InitWebUi, WebUiConfig, webUiRuntimePort } from '@/webui';
import { NapCatOneBot11Adapter } from '@/onebot';
import { FFmpegService } from '@/common/ffmpeg';
import { NativePacketHandler } from '@/core/packet/handler/client';
//Framework ES入口文件
export async function getWebUiUrl() {
@ -37,7 +38,13 @@ export async function NCoreInitFramework(
const logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion());
const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用
nativePacketHandler.onAll((packet) => {
console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data);
});
await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion());
// 在 init 之后注册监听器
// 初始化 FFmpeg 服务
await FFmpegService.init(pathWrapper.binaryPath, logger);
//直到登录成功后,执行下一步
@ -65,7 +72,7 @@ export async function NCoreInitFramework(
// 过早进入会导致addKernelMsgListener等Listener添加失败
// await sleep(2500);
// 初始化 NapCatFramework
const loaderObject = new NapCatFramework(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper, pathWrapper);
const loaderObject = new NapCatFramework(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper, pathWrapper, nativePacketHandler);
await loaderObject.core.initCore();
//启动WebUi
@ -86,8 +93,10 @@ export class NapCatFramework {
selfInfo: SelfInfo,
basicInfoWrapper: QQBasicInfoWrapper,
pathWrapper: NapCatPathWrapper,
packetHandler: NativePacketHandler,
) {
this.context = {
packetHandler,
workingEnv: NapCatCoreWorkingEnv.Framework,
wrapper,
session,

View File

@ -102,7 +102,6 @@ export class NapCatOneBot11Adapter {
async InitOneBot () {
const selfInfo = this.core.selfInfo;
const ob11Config = this.configLoader.configData;
this.core.apis.UserApi.getUserDetailInfo(selfInfo.uid, false)
.then(async (user) => {
selfInfo.nick = user.nick;

View File

@ -33,6 +33,7 @@ import { NodeIO3MiscListener } from '@/core/listeners/NodeIO3MiscListener';
import { sleep } from '@/common/helper';
import { FFmpegService } from '@/common/ffmpeg';
import { connectToNamedPipe } from '@/shell/pipe';
import { NativePacketHandler } from '@/core/packet/handler/client';
// NapCat Shell App ES 入口文件
async function handleUncaughtExceptions(logger: LogWrapper) {
process.on('uncaughtException', (err) => {
@ -312,13 +313,19 @@ export async function NCoreInitShell() {
const pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath);
handleUncaughtExceptions(logger);
// 初始化 FFmpeg 服务
await FFmpegService.init(pathWrapper.binaryPath, logger);
await connectToNamedPipe(logger).catch(e => logger.logError('命名管道连接失败', e));
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion());
const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用
nativePacketHandler.onAll((packet) => {
console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data);
});
await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion());
const o3Service = wrapper.NodeIO3MiscService.get();
o3Service.addO3MiscListener(new NodeIO3MiscListener());
@ -385,6 +392,7 @@ export async function NCoreInitShell() {
selfInfo,
basicInfoWrapper,
pathWrapper,
nativePacketHandler
).InitNapCat();
}
@ -401,8 +409,10 @@ export class NapCatShell {
selfInfo: SelfInfo,
basicInfoWrapper: QQBasicInfoWrapper,
pathWrapper: NapCatPathWrapper,
packetHandler: NativePacketHandler,
) {
this.context = {
packetHandler,
workingEnv: NapCatCoreWorkingEnv.Shell,
wrapper,
session,