refactor: 收口 BangDream 乐队编成等级资源
This commit is contained in:
parent
5b0d2f754d
commit
7a9d808f8b
@ -311,6 +311,7 @@ export interface TsuguHook {
|
||||
- 已新增 `models/item-resource-repository.ts`,把 `Item` 的道具缩略图路径从 `bestdoriUrl + downloadFileCache` 收口到 provider-backed repository;`item-resource-repository.spec.ts` 覆盖 material、star 和 common item 三类路径,本地 `/查卡池 259` 图片 smoke 输出非空。
|
||||
- 已新增 `models/cutoff-event-top-repository.ts` 和 `render-blocks/player-ranking-resource-repository.ts`,把前十榜 eventtop 数据请求与排名徽章素材路径从 `bestdoriUrl + callAPIAndCacheResponse/downloadFileCache` 收口到 provider-backed repository;相关 spec 覆盖 eventtop query path、排名徽章路径和 provider 调用,本地 `/ycx 10 100 cn` 图片 smoke 输出非空。
|
||||
- 已新增 `models/degree-resource-repository.ts`,把 `Degree` 的称号缩略图、称号框、称号图标、动态称号脚本和纹理素材路径从 `bestdoriUrl + downloadFile/downloadFileCache` 收口到 provider-backed repository;`degree-resource-repository.spec.ts` 覆盖旧/新缩略图 fallback、动态称号旧纹理白名单和 provider 调用,本地 `/ycx 10 100 cn` 图片 smoke 输出非空。
|
||||
- 已新增 `render-blocks/deck-rank-resource-repository.ts`,把玩家详情“乐队编成等级”Rank 图片从 `list-band-detail.ts` 的本地路径和 Bestdori URL 直拼收口到 repository,渲染层只负责 `Buffer -> Image`;`deck-rank-resource-repository.spec.ts` 覆盖本地素材优先、远端 `/res/icon/*.png` 兜底和 provider 调用,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
|
||||
- 固化:Windows 下不要用反斜杠测试路径直接调用 Jest pattern,容易出现 `Pattern ... - 0 matches`;指定文件测试统一使用 `pnpm exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangDream/tsugu/<file>.spec.ts ...`,路径用正斜杠。
|
||||
|
||||
### Phase 4:搜索 specification 和 matcher
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider';
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import { assetsRootPath } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||
|
||||
export class DeckRankResourceRepository {
|
||||
constructor(
|
||||
private readonly provider: BangDreamDataProvider = bangDreamBestdoriProvider,
|
||||
private readonly localRankRootPath: string = path.join(
|
||||
assetsRootPath,
|
||||
'Rank',
|
||||
),
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取本地乐队编成等级图片路径。
|
||||
*
|
||||
* @param rankImageName - Rank 图片名称。
|
||||
*/
|
||||
getLocalRankImagePath(rankImageName: string): string {
|
||||
return path.join(this.localRankRootPath, `${rankImageName}.png`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取远端乐队编成等级图片路径。
|
||||
*
|
||||
* @param rankImageName - Rank 图片名称。
|
||||
*/
|
||||
getRemoteRankImagePath(rankImageName: string): string {
|
||||
return `/res/icon/${rankImageName}.png`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取乐队编成等级图片,优先本地素材,缺失时回退到 Bestdori 资源。
|
||||
*
|
||||
* @param rankImageName - Rank 图片名称。
|
||||
*/
|
||||
async getRankImageBuffer(rankImageName: string): Promise<Buffer> {
|
||||
const localImagePath = this.getLocalRankImagePath(rankImageName);
|
||||
if (existsSync(localImagePath)) {
|
||||
return readFileSync(localImagePath);
|
||||
}
|
||||
return await this.provider.getAsset(
|
||||
this.getRemoteRankImagePath(rankImageName),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const deckRankResourceRepository = new DeckRankResourceRepository();
|
||||
@ -1,19 +1,16 @@
|
||||
import { Player } from '@/qqbot/plugins/bangDream/tsugu/models/player';
|
||||
import { Canvas, Image } from 'skia-canvas';
|
||||
import { Canvas, Image, loadImage } from 'skia-canvas';
|
||||
import { drawList } from './list-frame';
|
||||
import { resizeImage } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/image-stack';
|
||||
import { Band } from '@/qqbot/plugins/bangDream/tsugu/models/band';
|
||||
import { drawTextWithImages } from '@/qqbot/plugins/bangDream/tsugu/canvas/text';
|
||||
import { starList } from './list-rarity';
|
||||
import mainAPI from '@/qqbot/plugins/bangDream/tsugu/models/main-data-store';
|
||||
import { bestdoriUrl } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||
import { assetsRootPath } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||
import * as path from 'path';
|
||||
import { loadImageFromPath } from '@/qqbot/plugins/bangDream/tsugu/canvas/image-utils';
|
||||
import {
|
||||
BANGDREAM_DECK_TOTAL_RATING_ID,
|
||||
BANGDREAM_STAGE_CHALLENGE_BAND_ID,
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/models/bangdream-constants';
|
||||
import { deckRankResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/deck-rank-resource-repository';
|
||||
|
||||
interface drawBandDetailsInListOptions {
|
||||
[bandId: number]: Array<Canvas | Image | string>;
|
||||
@ -114,7 +111,7 @@ export async function drawPlayerStageChallengeRankInList(
|
||||
}
|
||||
|
||||
//画玩家信息内乐队卡组最高等级
|
||||
const rankImage: { [rankImageName: string]: Image } = {};
|
||||
const rankImageCache: { [rankImageName: string]: Image } = {};
|
||||
/**
|
||||
* 在图片布局层中加载Rank图片。
|
||||
*
|
||||
@ -122,18 +119,12 @@ const rankImage: { [rankImageName: string]: Image } = {};
|
||||
* @returns 异步处理结果。
|
||||
*/
|
||||
async function loadRankImage(rankImageName: string): Promise<Image> {
|
||||
if (rankImage[rankImageName] == undefined) {
|
||||
try {
|
||||
rankImage[rankImageName] = await loadImageFromPath(
|
||||
path.join(assetsRootPath, `/Rank/${rankImageName}.png`),
|
||||
);
|
||||
} catch {
|
||||
rankImage[rankImageName] = await loadImageFromPath(
|
||||
`${bestdoriUrl}/res/icon/${rankImageName}.png`,
|
||||
);
|
||||
}
|
||||
if (rankImageCache[rankImageName] == undefined) {
|
||||
const rankImageBuffer =
|
||||
await deckRankResourceRepository.getRankImageBuffer(rankImageName);
|
||||
rankImageCache[rankImageName] = await loadImage(rankImageBuffer);
|
||||
}
|
||||
return rankImage[rankImageName];
|
||||
return rankImageCache[rankImageName];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import { DeckRankResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/deck-rank-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 deck rank resource repository', () => {
|
||||
it('builds local and remote rank image paths', () => {
|
||||
const repository = new DeckRankResourceRepository(
|
||||
createProviderMock(),
|
||||
'D:/KT/assets/Rank',
|
||||
);
|
||||
|
||||
expect(repository.getLocalRankImagePath('rank_4_1')).toBe(
|
||||
path.join('D:/KT/assets/Rank', 'rank_4_1.png'),
|
||||
);
|
||||
expect(repository.getRemoteRankImagePath('rank_4_1')).toBe(
|
||||
'/res/icon/rank_4_1.png',
|
||||
);
|
||||
});
|
||||
|
||||
it('uses local rank image buffers before remote fallback', async () => {
|
||||
const provider = createProviderMock();
|
||||
const localRootPath = mkdtempSync(path.join(tmpdir(), 'kt-rank-'));
|
||||
const localImagePath = path.join(localRootPath, 'rank_1.png');
|
||||
const localBuffer = Buffer.from('local-rank');
|
||||
writeFileSync(localImagePath, localBuffer);
|
||||
const repository = new DeckRankResourceRepository(
|
||||
provider,
|
||||
localRootPath,
|
||||
);
|
||||
|
||||
try {
|
||||
await expect(repository.getRankImageBuffer('rank_1')).resolves.toEqual(
|
||||
localBuffer,
|
||||
);
|
||||
expect(provider.getAsset).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
rmSync(localRootPath, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('downloads rank image buffers through the provider when local asset is missing', async () => {
|
||||
const provider = createProviderMock();
|
||||
const remoteBuffer = Buffer.from('remote-rank');
|
||||
provider.getAsset.mockResolvedValue(remoteBuffer);
|
||||
const repository = new DeckRankResourceRepository(
|
||||
provider,
|
||||
path.join(tmpdir(), 'missing-rank-root'),
|
||||
);
|
||||
|
||||
await expect(repository.getRankImageBuffer('rank_7')).resolves.toBe(
|
||||
remoteBuffer,
|
||||
);
|
||||
|
||||
expect(provider.getAsset).toHaveBeenCalledWith('/res/icon/rank_7.png');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user