diff --git a/src/common/cancel-task.ts b/src/common/cancel-task.ts new file mode 100644 index 000000000..315a151f1 --- /dev/null +++ b/src/common/cancel-task.ts @@ -0,0 +1,107 @@ +type TaskExecutor = (resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void, onCancel: (callback: () => void) => void) => void; + +export class CancelableTask { + private promise: Promise; + private cancelCallback: (() => void) | null = null; + private isCanceled = false; + private cancelListeners: Array<() => void> = []; + + constructor(executor: TaskExecutor) { + this.promise = new Promise((resolve, reject) => { + const onCancel = (callback: () => void) => { + this.cancelCallback = callback; + }; + + executor( + (value) => { + if (!this.isCanceled) { + resolve(value); + } + }, + (reason) => { + if (!this.isCanceled) { + reject(reason); + } + }, + onCancel + ); + }); + } + + public cancel() { + if (this.cancelCallback) { + this.cancelCallback(); + } + this.isCanceled = true; + this.cancelListeners.forEach(listener => listener()); + } + + public isTaskCanceled(): boolean { + return this.isCanceled; + } + + public onCancel(listener: () => void) { + this.cancelListeners.push(listener); + } + + public then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null + ): Promise { + return this.promise.then(onfulfilled, onrejected); + } + + public catch( + onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null + ): Promise { + return this.promise.catch(onrejected); + } + + public finally(onfinally?: (() => void) | undefined | null): Promise { + return this.promise.finally(onfinally); + } + + [Symbol.asyncIterator]() { + return { + next: () => this.promise.then(value => ({ value, done: true })), + }; + } +} + + +async function demoAwait() { + const executor: TaskExecutor = (resolve, reject, onCancel) => { + let count = 0; + const intervalId = setInterval(() => { + count++; + console.log(`Task is running... Count: ${count}`); + if (count === 5) { + clearInterval(intervalId); + resolve(count); + } + }, 1000); + + onCancel(() => { + clearInterval(intervalId); + console.log('Task has been canceled.'); + reject(new Error('Task was canceled')); + }); + }; + + const task = new CancelableTask(executor); + + task.onCancel(() => { + console.log('Cancel listener triggered.'); + }); + + setTimeout(() => { + task.cancel(); // 取消任务 + }, 6000); + + try { + const result = await task; + console.log(`Task completed with result: ${result}`); + } catch (error) { + console.error('Task failed:', error); + } +} \ No newline at end of file diff --git a/src/common/fall-back.ts b/src/common/fall-back.ts new file mode 100644 index 000000000..c85e6b459 --- /dev/null +++ b/src/common/fall-back.ts @@ -0,0 +1,26 @@ +type Handler = () => T | Promise; + +export class Fallback { + private handlers: Handler[] = []; + + add(handler: Handler): this { + this.handlers.push(handler); + return this; + } + + // 执行处理程序链 + async run(): Promise { + const errors: Error[] = []; + for (const handler of this.handlers) { + try { + const result = await handler(); + if (result !== undefined) { + return result; + } + } catch (error) { + errors.push(error instanceof Error ? error : new Error(String(error))); + } + } + throw new AggregateError(errors, 'All handlers failed'); + } +} \ No newline at end of file diff --git a/src/core/apis/user.ts b/src/core/apis/user.ts index 010318d15..636c05b01 100644 --- a/src/core/apis/user.ts +++ b/src/core/apis/user.ts @@ -4,6 +4,7 @@ import { InstanceContext, NapCatCore, ProfileBizType } from '..'; import { solveAsyncProblem } from '@/common/helper'; import { promisify } from 'node:util'; import { LRUCache } from '@/common/lru-cache'; +import { Fallback } from '@/common/fall-back'; export class NTQQUserApi { context: InstanceContext; @@ -169,46 +170,44 @@ export class NTQQUserApi { return skey; } - async getUidByUinV2(Uin: string) { - if (!Uin) { + async getUidByUinV2(uin: string) { + if (!uin) { return ''; } - const services = [ - () => this.context.session.getUixConvertService().getUid([Uin]).then((data) => data.uidInfo.get(Uin)).catch(() => undefined), - () => promisify> - (this.context.session.getProfileService().getUidByUin)('FriendsServiceImpl', [Uin]).then((data) => data.get(Uin)).catch(() => undefined), - () => this.context.session.getGroupService().getUidByUins([Uin]).then((data) => data.uids.get(Uin)).catch(() => undefined), - () => this.getUserDetailInfoByUin(Uin).then((data) => data.detail.uid).catch(() => undefined), - ]; - let uid: string | undefined = undefined; - for (const service of services) { - uid = await service(); - if (uid && uid.indexOf('*') == -1 && uid !== '') { - break; - } + + let isValidUin = (uin: string | undefined) => { + if (uin !== undefined && uin !== '0' && uin !== '') { return uin; } throw new Error('uin is invalid'); } + + const fallback = new Fallback() + .add(async () => isValidUin(await this.context.session.getUixConvertService().getUid([uin]).then((data) => data.uidInfo.get(uin)))) + .add(() =>isValidUin(this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [uin]).get(uin))) + .add(async () => isValidUin(await this.context.session.getGroupService().getUidByUins([uin]).then((data) => data.uids.get(uin)))) + .add(async () => isValidUin(await this.getUserDetailInfoByUin(uin).then((data) => data.detail.uid))); + + const uid = await fallback.run().catch(() => '0'); return uid ?? ''; } - async getUinByUidV2(Uid: string) { - if (!Uid) { + async getUinByUidV2(uid: string) { + if (!uid) { return '0'; } - const services = [ - () => this.context.session.getUixConvertService().getUin([Uid]).then((data) => data.uinInfo.get(Uid)).catch(() => undefined), - () => this.context.session.getGroupService().getUinByUids([Uid]).then((data) => data.uins.get(Uid)).catch(() => undefined), - () => promisify> - (this.context.session.getProfileService().getUinByUid)('FriendsServiceImpl', [Uid]).then((data) => data.get(Uid)).catch(() => undefined), - () => this.core.apis.FriendApi.getBuddyIdMap(true).then((data) => data.getKey(Uid)).catch(() => undefined), - () => this.getUserDetailInfo(Uid).then((data) => data.uin).catch(() => undefined), - ]; - let uin: string | undefined = undefined; - for (const service of services) { - uin = await service(); - if (uin && uin !== '0' && uin !== '') { - break; + + let isValidUid = (uid: string | undefined) => { + if (uid !== undefined && uid.indexOf('*') === -1 && uid !== '') { + return uid; } + throw new Error('uid is invalid'); } + + const fallback = new Fallback() + .add(async () => isValidUid(await this.context.session.getUixConvertService().getUin([uid]).then((data) => data.uinInfo.get(uid)))) + .add(() =>isValidUid(this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [uid]).get(uid))) + .add(async () => isValidUid(await this.context.session.getGroupService().getUinByUids([uid]).then((data) => data.uins.get(uid)))) + .add(async () => isValidUid(await this.getUserDetailInfo(uid).then((data) => data.uin))); + + const uin = await fallback.run().catch(() => '0'); return uin ?? '0'; }