From dbc2fab5afab4750adf8a621ffdcdb2723647648 Mon Sep 17 00:00:00 2001 From: sunlei Date: Sun, 7 Jun 2026 02:55:25 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=B6=E5=8F=A3=20BangDream=20?= =?UTF-8?q?=E9=81=93=E5=85=B7=E8=B5=84=E6=BA=90=E4=BB=93=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ot-bangdream-tsugu-global-refactor-plan.md | 1 + .../tsugu/models/item-resource-repository.ts | 51 ++++++++++++++++ .../plugins/bangDream/tsugu/models/item.ts | 26 ++------ .../tsugu/item-resource-repository.spec.ts | 60 +++++++++++++++++++ 4 files changed, 118 insertions(+), 20 deletions(-) create mode 100644 src/qqbot/plugins/bangDream/tsugu/models/item-resource-repository.ts create mode 100644 test/qqbot/plugins/bangDream/tsugu/item-resource-repository.spec.ts diff --git a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md index ed83964..599323f 100644 --- a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md +++ b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md @@ -308,6 +308,7 @@ export interface TsuguHook { - 已新增 `models/server-resource-repository.ts`,把 `Server` 的服务器图标 SVG 资源路径从 `bestdoriUrl + downloadFileCache` 收口到 provider-backed repository,并把台服本地图标路径改走 asset manifest;`server-resource-repository.spec.ts` 覆盖服务器图标路径、台服本地图标路径和下载调用,本地 `/查卡 472` 图片 smoke 保持非空输出。 - 已新增 `render-blocks/card-art-resource-repository.ts`,把 `card-art.ts` 的卡牌小图/插画边框资源下载从 `bestdoriUrl + downloadFileCache` 收口到 provider-backed repository,`card-art-spec.ts` 改为只生成 `/res/image/...` 相对资源路径;`card-art-resource-repository.spec.ts` 覆盖边框下载调用,本地 `/查卡 472` 图片 smoke 保持非空输出。 - 已新增 `models/costume-resource-repository.ts`,把 `Costume` 的服装详情请求和 Live2D 缩略图素材路径从 `bestdoriUrl + callAPIAndCacheResponse/downloadFile` 收口到 provider-backed repository;`costume-resource-repository.spec.ts` 覆盖详情路径、`sdchara.png` 路径和非内存缓存下载调用,本地 `/查卡 472` 图片 smoke 保持演出缩略图正常输出。 +- 已新增 `models/item-resource-repository.ts`,把 `Item` 的道具缩略图路径从 `bestdoriUrl + downloadFileCache` 收口到 provider-backed repository;`item-resource-repository.spec.ts` 覆盖 material、star 和 common item 三类路径,本地 `/查卡池 259` 图片 smoke 输出非空。 - 固化:Windows 下不要用反斜杠测试路径直接调用 Jest pattern,容易出现 `Pattern ... - 0 matches`;指定文件测试统一使用 `pnpm exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangDream/tsugu/.spec.ts ...`,路径用正斜杠。 ### Phase 4:搜索 specification 和 matcher diff --git a/src/qqbot/plugins/bangDream/tsugu/models/item-resource-repository.ts b/src/qqbot/plugins/bangDream/tsugu/models/item-resource-repository.ts new file mode 100644 index 0000000..191625b --- /dev/null +++ b/src/qqbot/plugins/bangDream/tsugu/models/item-resource-repository.ts @@ -0,0 +1,51 @@ +import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider'; +import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider'; +import { formatNumber } from '@/qqbot/plugins/bangDream/tsugu/models/model-utils'; +import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server'; + +export interface ItemResourceSource { + resourceId: number; + typeName: string; +} + +function toServerCode(server: Server | undefined): string { + return server == null ? 'undefined' : Server[server]; +} + +export class ItemResourceRepository { + constructor( + private readonly provider: BangDreamDataProvider = bangDreamBestdoriProvider, + ) {} + + /** + * 获取道具缩略图资源路径。 + * + * @param source - 道具资源来源字段。 + * @param server - 目标服务器。 + */ + getImagePath(source: ItemResourceSource, server: Server | undefined): string { + const serverCode = toServerCode(server); + if (source.typeName === 'material') { + return `/assets/${serverCode}/thumb/material_rip/${source.typeName}${formatNumber(source.resourceId, 3)}.png`; + } + if (source.typeName === 'star') { + return `/assets/${serverCode}/thumb/common_rip/star.png`; + } + return `/assets/${serverCode}/thumb/common_rip/${source.typeName}${source.resourceId}.png`; + } + + /** + * 下载道具缩略图资源。 + * + * @param source - 道具资源来源字段。 + * @param server - 目标服务器。 + */ + async getImageBuffer( + source: ItemResourceSource, + server: Server | undefined, + ): Promise { + return await this.provider.getAsset(this.getImagePath(source, server)); + } +} + +export const itemResourceRepository = new ItemResourceRepository(); diff --git a/src/qqbot/plugins/bangDream/tsugu/models/item.ts b/src/qqbot/plugins/bangDream/tsugu/models/item.ts index 59239cb..25c0548 100644 --- a/src/qqbot/plugins/bangDream/tsugu/models/item.ts +++ b/src/qqbot/plugins/bangDream/tsugu/models/item.ts @@ -1,15 +1,11 @@ import { Image, loadImage } from 'skia-canvas'; -import { downloadFileCache } from '@/qqbot/plugins/bangDream/tsugu/data-clients/asset-cache-client'; import { Server, getServerByPriority, } from '@/qqbot/plugins/bangDream/tsugu/models/server'; -import { formatNumber } from '@/qqbot/plugins/bangDream/tsugu/models/model-utils'; import mainAPI from '@/qqbot/plugins/bangDream/tsugu/models/main-data-store'; -import { - globalDefaultServer, - bestdoriUrl, -} from '@/qqbot/plugins/bangDream/tsugu/runtime/config'; +import { itemResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/models/item-resource-repository'; +import { globalDefaultServer } from '@/qqbot/plugins/bangDream/tsugu/runtime/config'; import { BANGDREAM_ITEM_TYPE_PREFIXES } from '@/qqbot/plugins/bangDream/tsugu/models/bangdream-constants'; export class Item { @@ -82,20 +78,10 @@ export class Item { server = getServerByPriority(this.name, displayedServerList); } server = getServerByPriority(this.name, displayedServerList); - let itemImageBuffer: Buffer; - if (this.typeName == 'material') { - itemImageBuffer = await downloadFileCache( - `${bestdoriUrl}/assets/${Server[server]}/thumb/material_rip/${this.typeName}${formatNumber(this.resourceId, 3)}.png`, - ); - } else if (this.typeName == 'star') { - itemImageBuffer = await downloadFileCache( - `${bestdoriUrl}/assets/${Server[server]}/thumb/common_rip/star.png`, - ); - } else { - itemImageBuffer = await downloadFileCache( - `${bestdoriUrl}/assets/${Server[server]}/thumb/common_rip/${this.typeName}${this.resourceId}.png`, - ); - } + const itemImageBuffer = await itemResourceRepository.getImageBuffer( + this, + server, + ); return await loadImage(itemImageBuffer); } } diff --git a/test/qqbot/plugins/bangDream/tsugu/item-resource-repository.spec.ts b/test/qqbot/plugins/bangDream/tsugu/item-resource-repository.spec.ts new file mode 100644 index 0000000..7188edf --- /dev/null +++ b/test/qqbot/plugins/bangDream/tsugu/item-resource-repository.spec.ts @@ -0,0 +1,60 @@ +import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider'; +import { + ItemResourceRepository, + type ItemResourceSource, +} from '@/qqbot/plugins/bangDream/tsugu/models/item-resource-repository'; +import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server'; + +function createProviderMock(): jest.Mocked { + return { + getAsset: jest.fn(), + getJson: jest.fn(), + getTracker: jest.fn(), + name: 'MockBestdori', + resolveUrl: jest.fn((pathOrUrl) => `https://bestdori.example${pathOrUrl}`), + }; +} + +function createItemSource( + overrides: Partial = {}, +): ItemResourceSource { + return { + resourceId: 7, + typeName: 'gacha_ticket_', + ...overrides, + }; +} + +describe('BangDream item resource repository', () => { + it('builds material, star, and common item image paths', () => { + const repository = new ItemResourceRepository(createProviderMock()); + + expect( + repository.getImagePath( + createItemSource({ resourceId: 3, typeName: 'material' }), + Server.cn, + ), + ).toBe('/assets/cn/thumb/material_rip/material003.png'); + expect( + repository.getImagePath(createItemSource({ typeName: 'star' }), Server.jp), + ).toBe('/assets/jp/thumb/common_rip/star.png'); + expect(repository.getImagePath(createItemSource(), Server.cn)).toBe( + '/assets/cn/thumb/common_rip/gacha_ticket_7.png', + ); + }); + + it('downloads item images through the provider', async () => { + const provider = createProviderMock(); + const itemBuffer = Buffer.from('item'); + provider.getAsset.mockResolvedValue(itemBuffer); + const repository = new ItemResourceRepository(provider); + + await expect( + repository.getImageBuffer(createItemSource(), Server.cn), + ).resolves.toBe(itemBuffer); + + expect(provider.getAsset).toHaveBeenCalledWith( + '/assets/cn/thumb/common_rip/gacha_ticket_7.png', + ); + }); +});