mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2024-11-21 09:36:35 +00:00
260 lines
11 KiB
TypeScript
260 lines
11 KiB
TypeScript
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
|
||
import { randomUUID } from 'crypto';
|
||
import { ListenerNamingMapping, ServiceNamingMapping } from '@/core';
|
||
|
||
interface InternalMapKey {
|
||
timeout: number;
|
||
createtime: number;
|
||
func: (...arg: any[]) => any;
|
||
checker: ((...args: any[]) => boolean) | undefined;
|
||
}
|
||
|
||
type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
|
||
|
||
type FuncKeys<T> = Extract<
|
||
{
|
||
[K in keyof T]: EnsureFunc<T[K]> extends never ? never : K;
|
||
}[keyof T],
|
||
string
|
||
>;
|
||
|
||
export type ListenerClassBase = Record<string, string>;
|
||
|
||
export class NTEventWrapper {
|
||
private readonly WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession
|
||
private readonly listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
|
||
private readonly EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||
|
||
constructor(
|
||
wrapperSession: NodeIQQNTWrapperSession,
|
||
) {
|
||
this.WrapperSession = wrapperSession;
|
||
}
|
||
|
||
createProxyDispatch(ListenerMainName: string) {
|
||
const dispatcherListenerFunc = this.dispatcherListener.bind(this);
|
||
return new Proxy(
|
||
{},
|
||
{
|
||
get(target: any, prop: any, receiver: any) {
|
||
if (typeof target[prop] === 'undefined') {
|
||
// 如果方法不存在,返回一个函数,这个函数调用existentMethod
|
||
return (...args: any[]) => {
|
||
dispatcherListenerFunc(ListenerMainName, prop, ...args).then();
|
||
};
|
||
}
|
||
// 如果方法存在,正常返回
|
||
return Reflect.get(target, prop, receiver);
|
||
},
|
||
},
|
||
);
|
||
}
|
||
|
||
createEventFunction<
|
||
Service extends keyof ServiceNamingMapping,
|
||
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||
T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||
>(eventName: `${Service}/${ServiceMethod}`): T | undefined {
|
||
const eventNameArr = eventName.split('/');
|
||
type eventType = {
|
||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> };
|
||
};
|
||
if (eventNameArr.length > 1) {
|
||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
|
||
const eventName = eventNameArr[1];
|
||
const services = (this.WrapperSession as unknown as eventType)[serviceName]();
|
||
let event = services[eventName];
|
||
//重新绑定this
|
||
event = event.bind(services);
|
||
if (event) {
|
||
return event as T;
|
||
}
|
||
return undefined;
|
||
}
|
||
}
|
||
|
||
createListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
|
||
const existListener = this.listenerManager.get(listenerMainName + uniqueCode);
|
||
if (!existListener) {
|
||
const Listener = this.createProxyDispatch(listenerMainName);
|
||
const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1];
|
||
const Service = `NodeIKernel${ServiceSubName}Service/addKernel${ServiceSubName}Listener`;
|
||
// eslint-disable-next-line
|
||
// @ts-ignore
|
||
this.createEventFunction(Service)(Listener as T);
|
||
this.listenerManager.set(listenerMainName + uniqueCode, Listener);
|
||
return Listener as T;
|
||
}
|
||
return existListener as T;
|
||
}
|
||
|
||
//统一回调清理事件
|
||
async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
|
||
this.EventTask.get(ListenerMainName)
|
||
?.get(ListenerSubName)
|
||
?.forEach((task, uuid) => {
|
||
if (task.createtime + task.timeout < Date.now()) {
|
||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid);
|
||
return;
|
||
}
|
||
if (task?.checker?.(...args)) {
|
||
task.func(...args);
|
||
}
|
||
});
|
||
}
|
||
|
||
async callNoListenerEvent<
|
||
Service extends keyof ServiceNamingMapping,
|
||
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||
>(
|
||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||
...args: Parameters<EventType>
|
||
): Promise<Awaited<ReturnType<EventType>>> {
|
||
return (this.createEventFunction(serviceAndMethod))!(...args);
|
||
}
|
||
|
||
async registerListen<
|
||
Listener extends keyof ListenerNamingMapping,
|
||
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
|
||
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>,
|
||
>(
|
||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||
waitTimes = 1,
|
||
timeout = 5000,
|
||
) {
|
||
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
|
||
const ListenerNameList = listenerAndMethod.split('/');
|
||
const ListenerMainName = ListenerNameList[0];
|
||
const ListenerSubName = ListenerNameList[1];
|
||
const id = randomUUID();
|
||
let complete = 0;
|
||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||
|
||
function sendDataCallback() {
|
||
if (complete == 0) {
|
||
reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout'));
|
||
} else {
|
||
resolve(retData!);
|
||
}
|
||
}
|
||
|
||
const timeoutRef = setTimeout(sendDataCallback, timeout);
|
||
const eventCallback = {
|
||
timeout: timeout,
|
||
createtime: Date.now(),
|
||
checker: checker,
|
||
func: (...args: Parameters<ListenerType>) => {
|
||
complete++;
|
||
retData = args;
|
||
if (complete >= waitTimes) {
|
||
clearTimeout(timeoutRef);
|
||
sendDataCallback();
|
||
}
|
||
},
|
||
};
|
||
if (!this.EventTask.get(ListenerMainName)) {
|
||
this.EventTask.set(ListenerMainName, new Map());
|
||
}
|
||
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
|
||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||
}
|
||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||
this.createListenerFunction(ListenerMainName);
|
||
});
|
||
}
|
||
|
||
async callNormalEventV2<
|
||
Service extends keyof ServiceNamingMapping,
|
||
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||
Listener extends keyof ListenerNamingMapping,
|
||
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
|
||
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
|
||
>(
|
||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||
args: Parameters<EventType>,
|
||
checkerEvent: (ret: Awaited<ReturnType<EventType>>) => boolean = () => true,
|
||
checkerListener: (...args: Parameters<ListenerType>) => boolean = () => true,
|
||
callbackTimesToWait = 1,
|
||
timeout = 5000,
|
||
) {
|
||
const id = randomUUID();
|
||
let complete = 0;
|
||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||
let retEvent: any = {};
|
||
|
||
function sendDataCallback(resolve: any, reject: any) {
|
||
if (complete == 0) {
|
||
reject(
|
||
new Error(
|
||
'Timeout: NTEvent serviceAndMethod:' +
|
||
serviceAndMethod +
|
||
' ListenerName:' +
|
||
listenerAndMethod +
|
||
' EventRet:\n' +
|
||
JSON.stringify(retEvent, null, 4) +
|
||
'\n',
|
||
),
|
||
);
|
||
} else {
|
||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||
}
|
||
}
|
||
|
||
const ListenerNameList = listenerAndMethod.split('/');
|
||
const ListenerMainName = ListenerNameList[0];
|
||
const ListenerSubName = ListenerNameList[1];
|
||
|
||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||
(resolve, reject) => {
|
||
const timeoutRef = setTimeout(() => sendDataCallback(resolve, reject), timeout);
|
||
|
||
const eventCallback = {
|
||
timeout: timeout,
|
||
createtime: Date.now(),
|
||
checker: checkerListener,
|
||
func: (...args: any[]) => {
|
||
complete++;
|
||
retData = args as Parameters<ListenerType>;
|
||
if (complete >= callbackTimesToWait) {
|
||
clearTimeout(timeoutRef);
|
||
sendDataCallback(resolve, reject);
|
||
}
|
||
},
|
||
};
|
||
if (!this.EventTask.get(ListenerMainName)) {
|
||
this.EventTask.set(ListenerMainName, new Map());
|
||
}
|
||
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
|
||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||
}
|
||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||
this.createListenerFunction(ListenerMainName);
|
||
|
||
this.createEventFunction(serviceAndMethod)!(...(args))
|
||
.then((eventResult: any) => {
|
||
retEvent = eventResult;
|
||
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||
clearTimeout(timeoutRef);
|
||
reject(
|
||
new Error(
|
||
'EventChecker Failed: NTEvent serviceAndMethod:' +
|
||
serviceAndMethod +
|
||
' ListenerName:' +
|
||
listenerAndMethod +
|
||
' EventRet:\n' +
|
||
JSON.stringify(retEvent, null, 4) +
|
||
'\n',
|
||
),
|
||
);
|
||
}
|
||
})
|
||
.catch(reject);
|
||
},
|
||
);
|
||
}
|
||
}
|