mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-04 01:29:03 +08:00
* feat: pnpm new * Refactor build and release workflows, update dependencies Switch build scripts and workflows from npm to pnpm, update build and artifact paths, and simplify release workflow by removing version detection and changelog steps. Add new dependencies (silk-wasm, express, ws, node-pty-prebuilt-multiarch), update exports in package.json files, and add vite config for napcat-framework. Also, rename manifest.json for framework package and fix static asset copying in shell build config.
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';
|
||
|
||
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);
|
||
}
|
||
}
|
||
);
|
||
}
|
||
}
|