From 98e26616bb6b8e4c3be15e4d182ec1e4678ae996 Mon Sep 17 00:00:00 2001 From: sunlei Date: Fri, 19 Jun 2026 19:15:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=9D=E6=8C=81Bilibili=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E4=BA=8B=E4=BB=B6=E5=BC=82=E5=B8=B8=E4=B8=8D=E5=A4=96?= =?UTF-8?q?=E6=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/bilibili-card-application.ts | 46 ++++++++-- .../bilibili-card-application.spec.ts | 87 +++++++++++++++++++ 2 files changed, 124 insertions(+), 9 deletions(-) diff --git a/src/modules/qqbot/plugins/bilibili-card/src/application/bilibili-card-application.ts b/src/modules/qqbot/plugins/bilibili-card/src/application/bilibili-card-application.ts index 61aaf53..8aeefa5 100644 --- a/src/modules/qqbot/plugins/bilibili-card/src/application/bilibili-card-application.ts +++ b/src/modules/qqbot/plugins/bilibili-card/src/application/bilibili-card-application.ts @@ -122,14 +122,19 @@ export class BilibiliCardApplication { if (cached && cached.expiresAt > current) return cached.value; const config = readBilibiliCardRuntimeConfig(this.host); - const value = ( - await this.host.getBoundEventPluginKeys(normalizedSelfId) - ).includes(this.manifest.pluginKey); - this.boundCache.set(normalizedSelfId, { - expiresAt: current + Math.min(config.dedupeTtlMs, 60000), - value, - }); - return value; + try { + const value = ( + await this.host.getBoundEventPluginKeys(normalizedSelfId) + ).includes(this.manifest.pluginKey); + this.boundCache.set(normalizedSelfId, { + expiresAt: current + Math.min(config.dedupeTtlMs, 60000), + value, + }); + return value; + } catch (error) { + this.warn(`Bilibili 事件绑定查询失败: ${normalizeError(error)}`); + return false; + } } /** @@ -147,7 +152,14 @@ export class BilibiliCardApplication { * @param message - Warning message safe for platform logs. */ private warn(message: string) { - this.host.warn?.(message); + try { + const result = this.host.warn?.(message) as unknown; + if (isThenable(result)) { + result.catch(() => undefined); + } + } catch { + return; + } } } @@ -182,6 +194,22 @@ function isB23ShortLink(url: string) { } } +/** + * Detects promise-like warning results so rejected async loggers cannot escape later. + * @param value - Return value from the optional host warning hook. + * @returns `true` when the value exposes a callable `catch` method. + */ +function isThenable( + value: unknown, +): value is { catch: (handler: () => void) => unknown } { + return ( + typeof value === 'object' && + value !== null && + 'catch' in value && + typeof value.catch === 'function' + ); +} + /** * Converts thrown values to stable warning text. * @param error - Error or arbitrary thrown value from host or domain code. diff --git a/test/modules/qqbot/plugins/bilibili-card/bilibili-card-application.spec.ts b/test/modules/qqbot/plugins/bilibili-card/bilibili-card-application.spec.ts index 5e610c4..2ae3396 100644 --- a/test/modules/qqbot/plugins/bilibili-card/bilibili-card-application.spec.ts +++ b/test/modules/qqbot/plugins/bilibili-card/bilibili-card-application.spec.ts @@ -25,6 +25,29 @@ describe('Bilibili card application', () => { expect(host.sendText).not.toHaveBeenCalled(); }); + it('warns and returns false when binding lookup fails', async () => { + const host = createHost({ + getBoundEventPluginKeys: jest + .fn() + .mockRejectedValue(new Error('binding down')), + }); + const application = new BilibiliCardApplication(host, createManifest()); + + await expect( + application.handleMessage( + createMessage({ + messageText: 'https://www.bilibili.com/video/BV1xx411c7mD', + }), + ), + ).resolves.toBe(false); + + expect(host.warn).toHaveBeenCalledWith( + expect.stringContaining('binding down'), + ); + expect(host.requestJson).not.toHaveBeenCalled(); + expect(host.sendText).not.toHaveBeenCalled(); + }); + it('ignores messages sent by the bot itself', async () => { const host = createHost({ getBoundEventPluginKeys: jest.fn().mockResolvedValue(['bilibili-card']), @@ -169,6 +192,70 @@ describe('Bilibili card application', () => { expect(host.sendText).not.toHaveBeenCalled(); }); + it('returns false when warning throws during binding lookup failure', async () => { + const host = createHost({ + getBoundEventPluginKeys: jest + .fn() + .mockRejectedValue(new Error('binding down')), + warn: jest.fn(() => { + throw new Error('logger down'); + }), + }); + const application = new BilibiliCardApplication(host, createManifest()); + + await expect( + application.handleMessage( + createMessage({ + messageText: 'https://www.bilibili.com/video/BV1xx411c7mD', + }), + ), + ).resolves.toBe(false); + }); + + it('returns false when warning throws during short-link failure', async () => { + const host = createHost({ + getBoundEventPluginKeys: jest.fn().mockResolvedValue(['bilibili-card']), + resolveRedirect: jest.fn().mockRejectedValue(new Error('redirect down')), + warn: jest.fn(() => { + throw new Error('logger down'); + }), + }); + const application = new BilibiliCardApplication(host, createManifest()); + + await expect( + application.handleMessage( + createMessage({ + messageText: 'https://b23.tv/abc123', + }), + ), + ).resolves.toBe(false); + }); + + it('attaches a rejection handler when warning returns a promise', async () => { + const catchSpy = jest.fn(); + const host = createHost({ + getBoundEventPluginKeys: jest.fn().mockResolvedValue(['bilibili-card']), + resolveRedirect: jest.fn().mockRejectedValue(new Error('redirect down')), + warn: jest.fn( + () => + ({ + catch: catchSpy, + }) as unknown as void, + ), + }); + const application = new BilibiliCardApplication(host, createManifest()); + + await expect( + application.handleMessage( + createMessage({ + messageText: 'https://b23.tv/abc123', + }), + ), + ).resolves.toBe(false); + + expect(catchSpy).toHaveBeenCalledWith(expect.any(Function)); + }); + it('warns and returns false when short-link resolution fails', async () => { const host = createHost({ getBoundEventPluginKeys: jest.fn().mockResolvedValue(['bilibili-card']),