refactor: 收口 BangDream 服装资源仓储
This commit is contained in:
parent
ac5812e079
commit
4816354344
@ -307,6 +307,7 @@ export interface TsuguHook {
|
||||
- 已新增 `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 保持非空输出。
|
||||
- 已新增 `models/costume-resource-repository.ts`,把 `Costume` 的服装详情请求和 Live2D 缩略图素材路径从 `bestdoriUrl + callAPIAndCacheResponse/downloadFile` 收口到 provider-backed repository;`costume-resource-repository.spec.ts` 覆盖详情路径、`sdchara.png` 路径和非内存缓存下载调用,本地 `/查卡 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
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider';
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import {
|
||||
getServerByPriority,
|
||||
Server,
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
import { globalDefaultServer } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||
|
||||
export interface CostumeSdResourceSource {
|
||||
publishedAt: Array<number | null>;
|
||||
sdResourceName: string;
|
||||
}
|
||||
|
||||
function toServerCode(server: Server | undefined): string {
|
||||
return server == null ? 'undefined' : Server[server];
|
||||
}
|
||||
|
||||
export class CostumeResourceRepository {
|
||||
constructor(
|
||||
private readonly provider: BangDreamDataProvider = bangDreamBestdoriProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取服装详情。
|
||||
*
|
||||
* @param costumeId - 服装 ID。
|
||||
*/
|
||||
async getDetail(costumeId: number): Promise<Record<string, any>> {
|
||||
return await this.provider.getJson<Record<string, any>>(
|
||||
`/api/costumes/${costumeId}.json`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Live2D 缩略图资源路径。
|
||||
*
|
||||
* @param source - 服装资源来源字段。
|
||||
* @param displayedServerList - 可展示服务器优先级。
|
||||
*/
|
||||
getSdCharacterPath(
|
||||
source: CostumeSdResourceSource,
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): string {
|
||||
const serverCode = toServerCode(
|
||||
getServerByPriority(source.publishedAt, displayedServerList),
|
||||
);
|
||||
return `/assets/${serverCode}/characters/livesd/${source.sdResourceName}_rip/sdchara.png`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载 Live2D 缩略图资源。
|
||||
*
|
||||
* @param source - 服装资源来源字段。
|
||||
* @param displayedServerList - 可展示服务器优先级。
|
||||
*/
|
||||
async getSdCharacterBuffer(
|
||||
source: CostumeSdResourceSource,
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): Promise<Buffer> {
|
||||
return await this.provider.getAsset(
|
||||
this.getSdCharacterPath(source, displayedServerList),
|
||||
{ memoryCache: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const costumeResourceRepository = new CostumeResourceRepository();
|
||||
@ -1,16 +1,9 @@
|
||||
import { callAPIAndCacheResponse } from '@/qqbot/plugins/bangDream/tsugu/data-clients/api-cache-client';
|
||||
import { downloadFile } from '@/qqbot/plugins/bangDream/tsugu/data-clients/asset-cache-client';
|
||||
import {
|
||||
bestdoriUrl,
|
||||
globalDefaultServer,
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||
import { globalDefaultServer } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||
import mainAPI from '@/qqbot/plugins/bangDream/tsugu/models/main-data-store';
|
||||
import {
|
||||
Server,
|
||||
getServerByPriority,
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
import { Image, loadImage } from 'skia-canvas';
|
||||
import { stringToNumberArray } from '@/qqbot/plugins/bangDream/tsugu/models/model-utils';
|
||||
import { costumeResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/models/costume-resource-repository';
|
||||
|
||||
export class Costume {
|
||||
costumeId: number;
|
||||
@ -48,8 +41,8 @@ export class Costume {
|
||||
if (this.isInitfull) {
|
||||
return;
|
||||
}
|
||||
const costumeData = await callAPIAndCacheResponse(
|
||||
`${bestdoriUrl}/api/costumes/${this.costumeId}.json`,
|
||||
const costumeData = await costumeResourceRepository.getDetail(
|
||||
this.costumeId,
|
||||
);
|
||||
this.data = costumeData;
|
||||
this.isExist = true;
|
||||
@ -71,10 +64,11 @@ export class Costume {
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): Promise<Image> {
|
||||
if (!displayedServerList) displayedServerList = globalDefaultServer;
|
||||
const server = getServerByPriority(this.publishedAt, displayedServerList);
|
||||
const sdCharacterBuffer = await downloadFile(
|
||||
`${bestdoriUrl}/assets/${Server[server]}/characters/livesd/${this.sdResourceName}_rip/sdchara.png`,
|
||||
);
|
||||
const sdCharacterBuffer =
|
||||
await costumeResourceRepository.getSdCharacterBuffer(
|
||||
this,
|
||||
displayedServerList,
|
||||
);
|
||||
return await loadImage(sdCharacterBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import { CostumeResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/models/costume-resource-repository';
|
||||
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
|
||||
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 costume resource repository', () => {
|
||||
it('loads costume details through the provider', async () => {
|
||||
const provider = createProviderMock();
|
||||
const detail = { costumeId: 472, sdResourceName: 'sd_001' };
|
||||
provider.getJson.mockResolvedValue(detail);
|
||||
const repository = new CostumeResourceRepository(provider);
|
||||
|
||||
await expect(repository.getDetail(472)).resolves.toBe(detail);
|
||||
|
||||
expect(provider.getJson).toHaveBeenCalledWith('/api/costumes/472.json');
|
||||
});
|
||||
|
||||
it('builds and downloads sd character assets through the provider', async () => {
|
||||
const provider = createProviderMock();
|
||||
const sdCharacterBuffer = Buffer.from('sd-character');
|
||||
provider.getAsset.mockResolvedValue(sdCharacterBuffer);
|
||||
const repository = new CostumeResourceRepository(provider);
|
||||
const source = {
|
||||
publishedAt: [100, null, null, 200, null],
|
||||
sdResourceName: 'sd_001',
|
||||
};
|
||||
|
||||
expect(repository.getSdCharacterPath(source, [Server.cn])).toBe(
|
||||
'/assets/cn/characters/livesd/sd_001_rip/sdchara.png',
|
||||
);
|
||||
await expect(
|
||||
repository.getSdCharacterBuffer(source, [Server.cn]),
|
||||
).resolves.toBe(sdCharacterBuffer);
|
||||
|
||||
expect(provider.getAsset).toHaveBeenCalledWith(
|
||||
'/assets/cn/characters/livesd/sd_001_rip/sdchara.png',
|
||||
{ memoryCache: false },
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user