mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-04 06:31:13 +00:00
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.
141 lines
4.7 KiB
TypeScript
141 lines
4.7 KiB
TypeScript
import { NodeIQQNTWrapperSession } from '@/napcat-core/wrapper';
|
||
import { ServiceNamingMapping } from '@/napcat-core/services/index';
|
||
import { NTEventWrapper } from './event';
|
||
|
||
/**
|
||
* 创建 Service 方法的代理
|
||
* 拦截所有方法调用,通过 EventWrapper 进行调用
|
||
*/
|
||
function createServiceMethodProxy<S extends keyof ServiceNamingMapping>(
|
||
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<string, unknown>();
|
||
|
||
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);
|
||
}
|