refactor: 收口 BangDream 卡牌边框资源仓储

This commit is contained in:
sunlei 2026-06-07 02:39:50 +08:00
parent 60d1950af4
commit ac5812e079
6 changed files with 116 additions and 44 deletions

View File

@ -306,6 +306,7 @@ export interface TsuguHook {
- 已新增 `models/band-resource-repository.ts`,把 `Band` 的乐队 Logo 和乐队图标 SVG 资源路径从 `bestdoriUrl + downloadFileCache` 收口到 provider-backed repository`band-resource-repository.spec.ts` 覆盖两类资源路径和下载调用,本地 `/查角色 1` 图片 smoke 保持非空输出。
- 已新增 `models/attribute-resource-repository.ts`,把 `Attribute` 的属性图标 SVG 资源路径从 `bestdoriUrl + downloadFileCache` 收口到 provider-backed repository`attribute-resource-repository.spec.ts` 覆盖属性图标路径和下载调用,本地 `/查卡 472` 图片 smoke 保持非空输出。
- 已新增 `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 保持非空输出。
- 固化Windows 下不要用反斜杠测试路径直接调用 Jest pattern容易出现 `Pattern ... - 0 matches`;指定文件测试统一使用 `pnpm exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangDream/tsugu/<file>.spec.ts ...`,路径用正斜杠。
### Phase 4搜索 specification 和 matcher

View File

@ -0,0 +1,45 @@
import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider';
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
import type { BangDreamCardArtAttribute } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/card-art-spec';
import {
createCardIconFramePath,
createCardIllustrationFramePath,
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/card-art-spec';
export class CardArtResourceRepository {
constructor(
private readonly provider: BangDreamDataProvider = bangDreamBestdoriProvider,
) {}
/**
*
*
* @param rarity -
* @param attribute -
*/
async getIconFrameBuffer(
rarity: number,
attribute: BangDreamCardArtAttribute,
): Promise<Buffer> {
return await this.provider.getAsset(
createCardIconFramePath(rarity, attribute),
);
}
/**
*
*
* @param rarity -
* @param attribute -
*/
async getIllustrationFrameBuffer(
rarity: number,
attribute: BangDreamCardArtAttribute,
): Promise<Buffer> {
return await this.provider.getAsset(
createCardIllustrationFramePath(rarity, attribute),
);
}
}
export const cardArtResourceRepository = new CardArtResourceRepository();

View File

@ -45,49 +45,43 @@ export const BANGDREAM_CARD_ART_SPEC = {
} as const;
/**
* URL
*
*
* @param baseUrl - Bestdori
* @param rarity -
* @param attribute -
*/
export function createCardIconFrameUrl(
baseUrl: string,
export function createCardIconFramePath(
rarity: number,
attribute: BangDreamCardArtAttribute,
): string {
return createCardFrameUrl(baseUrl, 'card', rarity, attribute);
return createCardFramePath('card', rarity, attribute);
}
/**
* URL
*
*
* @param baseUrl - Bestdori
* @param rarity -
* @param attribute -
*/
export function createCardIllustrationFrameUrl(
baseUrl: string,
export function createCardIllustrationFramePath(
rarity: number,
attribute: BangDreamCardArtAttribute,
): string {
return createCardFrameUrl(baseUrl, 'frame', rarity, attribute);
return createCardFramePath('frame', rarity, attribute);
}
/**
* Bestdori URL
* Bestdori
*
* @param baseUrl - Bestdori
* @param prefix -
* @param rarity -
* @param attribute -
*/
function createCardFrameUrl(
baseUrl: string,
function createCardFramePath(
prefix: 'card' | 'frame',
rarity: number,
attribute: BangDreamCardArtAttribute,
): string {
const frameName = rarity === 1 ? `${rarity}-${attribute}` : `${rarity}`;
return `${baseUrl}/res/image/${prefix}-${frameName}.png`;
return `/res/image/${prefix}-${frameName}.png`;
}

View File

@ -3,19 +3,16 @@ import { Band } from '@/qqbot/plugins/bangDream/tsugu/models/band';
import { Attribute } from '@/qqbot/plugins/bangDream/tsugu/models/attribute';
import { Card } from '@/qqbot/plugins/bangDream/tsugu/models/card';
import { Image, Canvas, loadImage } from 'skia-canvas';
import { downloadFileCache } from '@/qqbot/plugins/bangDream/tsugu/data-clients/asset-cache-client';
import { drawCardIconSkill } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/skill-text';
import { Skill } from '@/qqbot/plugins/bangDream/tsugu/models/skill';
import { bestdoriUrl } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
import { loadImageFromPath } from '@/qqbot/plugins/bangDream/tsugu/canvas/image-utils';
import { getBangDreamAssetPath } from '@/qqbot/plugins/bangDream/tsugu/runtime/asset-manifest';
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
import {
BANGDREAM_CARD_ART_SPEC,
BangDreamCardArtAttribute,
createCardIconFrameUrl,
createCardIllustrationFrameUrl,
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/card-art-spec';
import { cardArtResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/card-art-resource-repository';
const cardTypeIconList: { [type: string]: Image } = {};
const starList: { [type: string]: Image } = {};
@ -60,8 +57,10 @@ async function getCardIconFrame(
rarity: number,
attribute: BangDreamCardArtAttribute,
): Promise<Image> {
const imageUrl = createCardIconFrameUrl(bestdoriUrl, rarity, attribute);
const imageBuffer = await downloadFileCache(imageUrl);
const imageBuffer = await cardArtResourceRepository.getIconFrameBuffer(
rarity,
attribute,
);
return await loadImage(imageBuffer);
}
@ -77,12 +76,11 @@ async function getCardIllustrationFrame(
rarity: number,
attribute: BangDreamCardArtAttribute,
): Promise<Image> {
const imageUrl = createCardIllustrationFrameUrl(
bestdoriUrl,
rarity,
attribute,
);
const imageBuffer = await downloadFileCache(imageUrl);
const imageBuffer =
await cardArtResourceRepository.getIllustrationFrameBuffer(
rarity,
attribute,
);
return await loadImage(imageBuffer);
}

View File

@ -0,0 +1,40 @@
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
import { CardArtResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/card-art-resource-repository';
function createProviderMock(): jest.Mocked<BangDreamDataProvider> {
return {
getAsset: jest.fn(),
getJson: jest.fn(),
getTracker: jest.fn(),
name: 'MockBestdori',
resolveUrl: jest.fn((pathOrUrl) => `https://bestdori.example${pathOrUrl}`),
};
}
describe('BangDream card art resource repository', () => {
it('downloads card icon and illustration frames through the provider', async () => {
const provider = createProviderMock();
const iconFrameBuffer = Buffer.from('icon-frame');
const illustrationFrameBuffer = Buffer.from('illustration-frame');
provider.getAsset
.mockResolvedValueOnce(iconFrameBuffer)
.mockResolvedValueOnce(illustrationFrameBuffer);
const repository = new CardArtResourceRepository(provider);
await expect(repository.getIconFrameBuffer(1, 'cool')).resolves.toBe(
iconFrameBuffer,
);
await expect(
repository.getIllustrationFrameBuffer(5, 'happy'),
).resolves.toBe(illustrationFrameBuffer);
expect(provider.getAsset).toHaveBeenNthCalledWith(
1,
'/res/image/card-1-cool.png',
);
expect(provider.getAsset).toHaveBeenNthCalledWith(
2,
'/res/image/frame-5.png',
);
});
});

View File

@ -1,29 +1,23 @@
import {
BANGDREAM_CARD_ART_SPEC,
createCardIconFrameUrl,
createCardIllustrationFrameUrl,
createCardIconFramePath,
createCardIllustrationFramePath,
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/card-art-spec';
describe('BangDream card art spec', () => {
it('creates icon frame urls with Bestdori rarity rules', () => {
const baseUrl = 'https://bestdori.com';
expect(createCardIconFrameUrl(baseUrl, 1, 'cool')).toBe(
'https://bestdori.com/res/image/card-1-cool.png',
);
expect(createCardIconFrameUrl(baseUrl, 5, 'happy')).toBe(
'https://bestdori.com/res/image/card-5.png',
it('creates icon frame paths with Bestdori rarity rules', () => {
expect(createCardIconFramePath(1, 'cool')).toBe(
'/res/image/card-1-cool.png',
);
expect(createCardIconFramePath(5, 'happy')).toBe('/res/image/card-5.png');
});
it('creates illustration frame urls with Bestdori rarity rules', () => {
const baseUrl = 'https://bestdori.com';
expect(createCardIllustrationFrameUrl(baseUrl, 1, 'pure')).toBe(
'https://bestdori.com/res/image/frame-1-pure.png',
it('creates illustration frame paths with Bestdori rarity rules', () => {
expect(createCardIllustrationFramePath(1, 'pure')).toBe(
'/res/image/frame-1-pure.png',
);
expect(createCardIllustrationFrameUrl(baseUrl, 4, 'powerful')).toBe(
'https://bestdori.com/res/image/frame-4.png',
expect(createCardIllustrationFramePath(4, 'powerful')).toBe(
'/res/image/frame-4.png',
);
});