mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-12-20 13:40:10 +08:00
feat: raw包能力增强完成
This commit is contained in:
parent
c5db525f4a
commit
dc51d01351
@ -30,6 +30,7 @@ import os from 'node:os';
|
|||||||
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
||||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||||
import { NTQQPacketApi } from './apis/packet';
|
import { NTQQPacketApi } from './apis/packet';
|
||||||
|
import { NativePacketHandler } from './packet/handler/client';
|
||||||
export * from './wrapper';
|
export * from './wrapper';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './services';
|
export * from './services';
|
||||||
@ -258,6 +259,7 @@ export interface InstanceContext {
|
|||||||
readonly loginService: NodeIKernelLoginService;
|
readonly loginService: NodeIKernelLoginService;
|
||||||
readonly basicInfoWrapper: QQBasicInfoWrapper;
|
readonly basicInfoWrapper: QQBasicInfoWrapper;
|
||||||
readonly pathWrapper: NapCatPathWrapper;
|
readonly pathWrapper: NapCatPathWrapper;
|
||||||
|
readonly packetHandler: NativePacketHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StableNTApiWrapper {
|
export interface StableNTApiWrapper {
|
||||||
|
|||||||
214
src/core/packet/handler/client.ts
Normal file
214
src/core/packet/handler/client.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';
|
|||||||
import { InitWebUi, WebUiConfig, webUiRuntimePort } from '@/webui';
|
import { InitWebUi, WebUiConfig, webUiRuntimePort } from '@/webui';
|
||||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||||
import { FFmpegService } from '@/common/ffmpeg';
|
import { FFmpegService } from '@/common/ffmpeg';
|
||||||
|
import { NativePacketHandler } from '@/core/packet/handler/client';
|
||||||
|
|
||||||
//Framework ES入口文件
|
//Framework ES入口文件
|
||||||
export async function getWebUiUrl() {
|
export async function getWebUiUrl() {
|
||||||
@ -37,6 +38,12 @@ export async function NCoreInitFramework(
|
|||||||
const logger = new LogWrapper(pathWrapper.logsPath);
|
const logger = new LogWrapper(pathWrapper.logsPath);
|
||||||
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
||||||
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion());
|
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 服务
|
// 初始化 FFmpeg 服务
|
||||||
await FFmpegService.init(pathWrapper.binaryPath, logger);
|
await FFmpegService.init(pathWrapper.binaryPath, logger);
|
||||||
@ -65,7 +72,7 @@ export async function NCoreInitFramework(
|
|||||||
// 过早进入会导致addKernelMsgListener等Listener添加失败
|
// 过早进入会导致addKernelMsgListener等Listener添加失败
|
||||||
// await sleep(2500);
|
// await sleep(2500);
|
||||||
// 初始化 NapCatFramework
|
// 初始化 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();
|
await loaderObject.core.initCore();
|
||||||
|
|
||||||
//启动WebUi
|
//启动WebUi
|
||||||
@ -86,8 +93,10 @@ export class NapCatFramework {
|
|||||||
selfInfo: SelfInfo,
|
selfInfo: SelfInfo,
|
||||||
basicInfoWrapper: QQBasicInfoWrapper,
|
basicInfoWrapper: QQBasicInfoWrapper,
|
||||||
pathWrapper: NapCatPathWrapper,
|
pathWrapper: NapCatPathWrapper,
|
||||||
|
packetHandler: NativePacketHandler,
|
||||||
) {
|
) {
|
||||||
this.context = {
|
this.context = {
|
||||||
|
packetHandler,
|
||||||
workingEnv: NapCatCoreWorkingEnv.Framework,
|
workingEnv: NapCatCoreWorkingEnv.Framework,
|
||||||
wrapper,
|
wrapper,
|
||||||
session,
|
session,
|
||||||
|
|||||||
@ -102,7 +102,6 @@ export class NapCatOneBot11Adapter {
|
|||||||
async InitOneBot () {
|
async InitOneBot () {
|
||||||
const selfInfo = this.core.selfInfo;
|
const selfInfo = this.core.selfInfo;
|
||||||
const ob11Config = this.configLoader.configData;
|
const ob11Config = this.configLoader.configData;
|
||||||
|
|
||||||
this.core.apis.UserApi.getUserDetailInfo(selfInfo.uid, false)
|
this.core.apis.UserApi.getUserDetailInfo(selfInfo.uid, false)
|
||||||
.then(async (user) => {
|
.then(async (user) => {
|
||||||
selfInfo.nick = user.nick;
|
selfInfo.nick = user.nick;
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import { NodeIO3MiscListener } from '@/core/listeners/NodeIO3MiscListener';
|
|||||||
import { sleep } from '@/common/helper';
|
import { sleep } from '@/common/helper';
|
||||||
import { FFmpegService } from '@/common/ffmpeg';
|
import { FFmpegService } from '@/common/ffmpeg';
|
||||||
import { connectToNamedPipe } from '@/shell/pipe';
|
import { connectToNamedPipe } from '@/shell/pipe';
|
||||||
|
import { NativePacketHandler } from '@/core/packet/handler/client';
|
||||||
// NapCat Shell App ES 入口文件
|
// NapCat Shell App ES 入口文件
|
||||||
async function handleUncaughtExceptions(logger: LogWrapper) {
|
async function handleUncaughtExceptions(logger: LogWrapper) {
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', (err) => {
|
||||||
@ -319,6 +320,12 @@ export async function NCoreInitShell() {
|
|||||||
await connectToNamedPipe(logger).catch(e => logger.logError('命名管道连接失败', e));
|
await connectToNamedPipe(logger).catch(e => logger.logError('命名管道连接失败', e));
|
||||||
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
||||||
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion());
|
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();
|
const o3Service = wrapper.NodeIO3MiscService.get();
|
||||||
o3Service.addO3MiscListener(new NodeIO3MiscListener());
|
o3Service.addO3MiscListener(new NodeIO3MiscListener());
|
||||||
@ -385,6 +392,7 @@ export async function NCoreInitShell() {
|
|||||||
selfInfo,
|
selfInfo,
|
||||||
basicInfoWrapper,
|
basicInfoWrapper,
|
||||||
pathWrapper,
|
pathWrapper,
|
||||||
|
nativePacketHandler
|
||||||
).InitNapCat();
|
).InitNapCat();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,8 +409,10 @@ export class NapCatShell {
|
|||||||
selfInfo: SelfInfo,
|
selfInfo: SelfInfo,
|
||||||
basicInfoWrapper: QQBasicInfoWrapper,
|
basicInfoWrapper: QQBasicInfoWrapper,
|
||||||
pathWrapper: NapCatPathWrapper,
|
pathWrapper: NapCatPathWrapper,
|
||||||
|
packetHandler: NativePacketHandler,
|
||||||
) {
|
) {
|
||||||
this.context = {
|
this.context = {
|
||||||
|
packetHandler,
|
||||||
workingEnv: NapCatCoreWorkingEnv.Shell,
|
workingEnv: NapCatCoreWorkingEnv.Shell,
|
||||||
wrapper,
|
wrapper,
|
||||||
session,
|
session,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user