mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-12-18 20:30:08 +08:00
Introduced a new eslint.config.js using neostandard and added related devDependencies. Updated codebase for consistent formatting, spacing, and function declarations. Minor refactoring and cleanup across multiple files to improve readability and maintain code style compliance.
273 lines
9.6 KiB
TypeScript
273 lines
9.6 KiB
TypeScript
import { NodeIQQNTWrapperSession } from '@/napcat-core/wrapper';
|
||
import { randomUUID } from 'crypto';
|
||
import { ListenerNamingMapping, ServiceNamingMapping } from '@/napcat-core/index';
|
||
|
||
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]?.();
|
||
if (!services || !eventName) {
|
||
return undefined;
|
||
}
|
||
let event = services[eventName];
|
||
|
||
// 重新绑定this
|
||
event = event?.bind(services);
|
||
if (event) {
|
||
return event as T;
|
||
}
|
||
return undefined;
|
||
}
|
||
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`;
|
||
|
||
// @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;
|
||
|
||
function sendDataCallback () {
|
||
if (complete === 0) {
|
||
reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout'));
|
||
} else {
|
||
resolve(retData!);
|
||
}
|
||
}
|
||
|
||
const timeoutRef = setTimeout(sendDataCallback, timeout);
|
||
const eventCallback = {
|
||
timeout,
|
||
createtime: Date.now(),
|
||
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;
|
||
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,
|
||
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);
|
||
|
||
const eventResult = this.createEventFunction(serviceAndMethod)!(...(args));
|
||
|
||
const eventRetHandle = (eventData: any) => {
|
||
retEvent = eventData;
|
||
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'
|
||
)
|
||
);
|
||
}
|
||
};
|
||
if (eventResult instanceof Promise) {
|
||
eventResult.then((eventResult: any) => {
|
||
eventRetHandle(eventResult);
|
||
})
|
||
.catch(reject);
|
||
} else {
|
||
eventRetHandle(eventResult);
|
||
}
|
||
}
|
||
);
|
||
}
|
||
}
|