From 0b6afb66d966be8f8060087cabb4db0b10c491f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Thu, 29 Jan 2026 21:58:27 +0800 Subject: [PATCH] Add session proxy with event wrapper integration Introduces a session proxy mechanism in napcat-core that intercepts service method calls and routes them through an event wrapper when enabled via the NAPCAT_SESSION_PROXY environment variable. Adds helper functions for creating proxied sessions and updates NapCatCore to support the new proxy integration. --- packages/napcat-core/helper/session-proxy.ts | 140 +++++++++++++++++++ packages/napcat-core/index.ts | 16 ++- 2 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 packages/napcat-core/helper/session-proxy.ts diff --git a/packages/napcat-core/helper/session-proxy.ts b/packages/napcat-core/helper/session-proxy.ts new file mode 100644 index 00000000..920dab36 --- /dev/null +++ b/packages/napcat-core/helper/session-proxy.ts @@ -0,0 +1,140 @@ +import { NodeIQQNTWrapperSession } from '@/napcat-core/wrapper'; +import { ServiceNamingMapping } from '@/napcat-core/services/index'; +import { NTEventWrapper } from './event'; + +/** + * 创建 Service 方法的代理 + * 拦截所有方法调用,通过 EventWrapper 进行调用 + */ +function createServiceMethodProxy( + serviceName: S, + originalService: ServiceNamingMapping[S], + eventWrapper: NTEventWrapper +): ServiceNamingMapping[S] { + return new Proxy(originalService as object, { + get(target, prop, receiver) { + const originalValue = Reflect.get(target, prop, receiver); + + // 如果不是函数,直接返回原始值 + if (typeof originalValue !== 'function') { + return originalValue; + } + + const methodName = prop as string; + + // 返回一个包装函数,通过 EventWrapper 调用 + return function (this: unknown, ...args: unknown[]) { + // 构造 EventWrapper 需要的路径格式: ServiceName/MethodName + const eventPath = `${serviceName}/${methodName}`; + + // 尝试通过 EventWrapper 调用 + try { + // 使用 callNoListenerEvent 的底层实现逻辑 + const eventFunc = (eventWrapper as any).createEventFunction(eventPath); + if (eventFunc) { + return eventFunc(...args); + } + } catch { + // 如果 EventWrapper 调用失败,回退到原始调用 + } + + // 回退到原始方法调用 + return originalValue.apply(originalService, args); + }; + }, + }) as ServiceNamingMapping[S]; +} + +/** + * 创建 Session 的双层代理 + * 第一层:拦截 getXXXService 方法 + * 第二层:拦截 Service 上的具体方法调用 + */ +export function createSessionProxy( + session: NodeIQQNTWrapperSession, + eventWrapper: NTEventWrapper +): NodeIQQNTWrapperSession { + // 缓存已代理的 Service,避免重复创建 + const serviceProxyCache = new Map(); + + return new Proxy(session, { + get(target, prop, receiver) { + const propName = prop as string; + + // 检查是否是 getXXXService 方法 + if (typeof propName === 'string' && propName.startsWith('get') && propName.endsWith('Service')) { + // 提取 Service 名称: getMsgService -> NodeIKernelMsgService + const servicePart = propName.slice(3); // 移除 'get' 前缀 + const serviceName = `NodeIKernel${servicePart}` as keyof ServiceNamingMapping; + + // 返回一个函数,该函数返回代理后的 Service + return function () { + // 检查缓存 + if (serviceProxyCache.has(serviceName)) { + return serviceProxyCache.get(serviceName); + } + + // 获取原始 Service + const originalGetter = Reflect.get(target, prop, receiver) as () => unknown; + const originalService = originalGetter.call(target); + + // 检查是否在 ServiceNamingMapping 中定义 + if (isKnownService(serviceName)) { + // 创建 Service 方法代理 + const proxiedService = createServiceMethodProxy( + serviceName, + originalService as ServiceNamingMapping[typeof serviceName], + eventWrapper + ); + serviceProxyCache.set(serviceName, proxiedService); + return proxiedService; + } + + // 未知的 Service,直接返回原始对象 + serviceProxyCache.set(serviceName, originalService); + return originalService; + }; + } + + // 非 getXXXService 方法,直接返回原始值 + return Reflect.get(target, prop, receiver); + }, + }); +} + +/** + * 检查 Service 名称是否在已知的映射中 + */ +function isKnownService(serviceName: string): serviceName is keyof ServiceNamingMapping { + const knownServices: string[] = [ + 'NodeIKernelAvatarService', + 'NodeIKernelBuddyService', + 'NodeIKernelFileAssistantService', + 'NodeIKernelGroupService', + 'NodeIKernelLoginService', + 'NodeIKernelMsgService', + 'NodeIKernelOnlineStatusService', + 'NodeIKernelProfileLikeService', + 'NodeIKernelProfileService', + 'NodeIKernelTicketService', + 'NodeIKernelStorageCleanService', + 'NodeIKernelRobotService', + 'NodeIKernelRichMediaService', + 'NodeIKernelDbToolsService', + 'NodeIKernelTipOffService', + 'NodeIKernelSearchService', + 'NodeIKernelCollectionService', + ]; + return knownServices.includes(serviceName); +} + +/** + * 创建带有 EventWrapper 集成的 InstanceContext + * 这是推荐的使用方式,在创建 context 时自动代理 session + */ +export function createProxiedSession( + session: NodeIQQNTWrapperSession, + eventWrapper: NTEventWrapper +): NodeIQQNTWrapperSession { + return createSessionProxy(session, eventWrapper); +} diff --git a/packages/napcat-core/index.ts b/packages/napcat-core/index.ts index 83fb4bb1..3fe915c6 100644 --- a/packages/napcat-core/index.ts +++ b/packages/napcat-core/index.ts @@ -25,6 +25,7 @@ import path from 'node:path'; import fs from 'node:fs'; import { hostname, systemName, systemVersion } from 'napcat-common/src/system'; import { NTEventWrapper } from '@/napcat-core/helper/event'; +import { createSessionProxy } from '@/napcat-core/helper/session-proxy'; import { KickedOffLineInfo, RawMessage, SelfInfo, SelfStatusInfo } from '@/napcat-core/types'; import { NapCatConfigLoader, NapcatConfigSchema } from '@/napcat-core/helper/config'; import os from 'node:os'; @@ -45,6 +46,7 @@ export * from './helper/qq-basic-info'; export * from './helper/event'; export * from './helper/config'; export * from './helper/proxy-handler'; +export * from './helper/session-proxy'; export enum NapCatCoreWorkingEnv { Unknown = 0, @@ -118,9 +120,19 @@ export class NapCatCore { // 通过构造器递过去的 runtime info 应该尽量少 constructor (context: InstanceContext, selfInfo: SelfInfo) { this.selfInfo = selfInfo; - this.context = context; - this.util = this.context.wrapper.NodeQQNTWrapperUtil; + // 先用原始 session 创建 eventWrapper this.eventWrapper = new NTEventWrapper(context.session); + // 通过环境变量 NAPCAT_SESSION_PROXY 开启 session 代理 + if (process.env['NAPCAT_SESSION_PROXY'] === '1') { + const proxiedSession = createSessionProxy(context.session, this.eventWrapper); + this.context = { + ...context, + session: proxiedSession, + }; + } else { + this.context = context; + } + this.util = this.context.wrapper.NodeQQNTWrapperUtil; this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath, NapcatConfigSchema); this.apis = { FileApi: new NTQQFileApi(this.context, this),