fix: 修复 BangDream 活动背景选服
This commit is contained in:
parent
7a9d808f8b
commit
d429b799da
@ -312,6 +312,7 @@ export interface TsuguHook {
|
||||
- 已新增 `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 输出非空。
|
||||
- `models/event-data-repository.ts` 已改为 provider-injected repository,并为活动背景和 topscreen trim 增加路径工厂;活动详情背景现在跟随 `displayedServerList` 选服,不再无条件按全局默认服取底图,避免 `/查活动 ... jp` 这类指定服务器查询出现横幅和背景不一致。
|
||||
- 固化:Windows 下不要用反斜杠测试路径直接调用 Jest pattern,容易出现 `Pattern ... - 0 matches`;指定文件测试统一使用 `pnpm exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangDream/tsugu/<file>.spec.ts ...`,路径用正斜杠。
|
||||
|
||||
### Phase 4:搜索 specification 和 matcher
|
||||
|
||||
@ -459,7 +459,9 @@ export async function drawEventDetail(
|
||||
all.push(gachaImageList[i]);
|
||||
}
|
||||
|
||||
const BGimage = useEasyBG ? undefined : await event.getEventBGImage();
|
||||
const BGimage = useEasyBG
|
||||
? undefined
|
||||
: await event.getEventBGImage(displayedServerList);
|
||||
|
||||
return await createOutputFinalImages({
|
||||
useEasyBG,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Canvas, Image, loadImage } from 'skia-canvas';
|
||||
import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider';
|
||||
import { bangDreamMainDataRepository } from '@/qqbot/plugins/bangDream/tsugu/models/main-data-repository';
|
||||
import {
|
||||
getServerByPriority,
|
||||
Server,
|
||||
@ -35,7 +34,11 @@ type RewardWithId = {
|
||||
rewardType: string;
|
||||
};
|
||||
|
||||
class EventDataRepository {
|
||||
export class EventDataRepository {
|
||||
constructor(
|
||||
private readonly provider = bangDreamBestdoriProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取活动远端详情数据。
|
||||
*
|
||||
@ -44,12 +47,40 @@ class EventDataRepository {
|
||||
*/
|
||||
async getDetail(eventId: number, update = true): Promise<Record<string, any>> {
|
||||
const cacheTime = update ? 0 : 1 / 0;
|
||||
return await bangDreamBestdoriProvider.getJson<Record<string, any>>(
|
||||
return await this.provider.getJson<Record<string, any>>(
|
||||
`/api/events/${eventId}.json`,
|
||||
{ cacheTime },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活动背景资源路径。
|
||||
*
|
||||
* @param event - 活动资源上下文。
|
||||
* @param displayedServerList - 展示服务器优先级。
|
||||
*/
|
||||
getBackgroundImagePath(
|
||||
event: EventAssetContext,
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): string {
|
||||
const server = getServerByPriority(event.startAt, displayedServerList);
|
||||
return `/assets/${Server[server]}/event/${event.assetBundleName}/topscreen_rip/bg_eventtop.png`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活动主界面裁切图资源路径。
|
||||
*
|
||||
* @param event - 活动资源上下文。
|
||||
* @param displayedServerList - 展示服务器优先级。
|
||||
*/
|
||||
getTopscreenTrimImagePath(
|
||||
event: EventAssetContext,
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): string {
|
||||
const server = getServerByPriority(event.startAt, displayedServerList);
|
||||
return `/assets/${Server[server]}/event/${event.assetBundleName}/topscreen_rip/trim_eventtop.png`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活动横幅图,优先活动资源,失败时回退 homebanner。
|
||||
*
|
||||
@ -62,13 +93,13 @@ class EventDataRepository {
|
||||
): Promise<Image> {
|
||||
const server = getServerByPriority(event.startAt, displayedServerList);
|
||||
try {
|
||||
const bannerImageBuffer = await bangDreamBestdoriProvider.getAsset(
|
||||
const bannerImageBuffer = await this.provider.getAsset(
|
||||
`/assets/${Server[server]}/event/${event.assetBundleName}/images_rip/banner.png`,
|
||||
{ ignoreError: false },
|
||||
);
|
||||
return await loadImage(bannerImageBuffer);
|
||||
} catch {
|
||||
const bannerImageBuffer = await bangDreamBestdoriProvider.getAsset(
|
||||
const bannerImageBuffer = await this.provider.getAsset(
|
||||
`/assets/jp/homebanner_rip/${event.bannerAssetBundleName}.png`,
|
||||
);
|
||||
return await loadImage(bannerImageBuffer);
|
||||
@ -79,15 +110,21 @@ class EventDataRepository {
|
||||
* 获取活动背景图。
|
||||
*
|
||||
* @param event - 活动资源上下文。
|
||||
* @param displayedServerList - 展示服务器优先级。
|
||||
*/
|
||||
async getBackgroundImage(event: EventAssetContext): Promise<Image | Canvas> {
|
||||
const server = getServerByPriority(event.startAt);
|
||||
const bgImageBuffer = await bangDreamBestdoriProvider.getAsset(
|
||||
`/assets/${Server[server]}/event/${event.assetBundleName}/topscreen_rip/bg_eventtop.png`,
|
||||
async getBackgroundImage(
|
||||
event: EventAssetContext,
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): Promise<Image | Canvas> {
|
||||
const bgImageBuffer = await this.provider.getAsset(
|
||||
this.getBackgroundImagePath(event, displayedServerList),
|
||||
);
|
||||
const backgroundImage = await loadImage(bgImageBuffer);
|
||||
try {
|
||||
const trimImage = await this.getTopscreenTrimImage(event);
|
||||
const trimImage = await this.getTopscreenTrimImage(
|
||||
event,
|
||||
displayedServerList,
|
||||
);
|
||||
return this.mergeTopscreenImages(backgroundImage, trimImage);
|
||||
} catch {
|
||||
return backgroundImage;
|
||||
@ -110,7 +147,7 @@ class EventDataRepository {
|
||||
let ruleNumber = 1;
|
||||
while (true) {
|
||||
try {
|
||||
const slideImageBuffer = await bangDreamBestdoriProvider.getAsset(
|
||||
const slideImageBuffer = await this.provider.getAsset(
|
||||
`${basePath}rule${ruleNumber}.png`,
|
||||
{ ignoreError: false },
|
||||
);
|
||||
@ -127,11 +164,14 @@ class EventDataRepository {
|
||||
* 获取活动主界面裁切图。
|
||||
*
|
||||
* @param event - 活动资源上下文。
|
||||
* @param displayedServerList - 展示服务器优先级。
|
||||
*/
|
||||
async getTopscreenTrimImage(event: EventAssetContext): Promise<Image> {
|
||||
const server = getServerByPriority(event.startAt);
|
||||
const topscreenTrimImageBuffer = await bangDreamBestdoriProvider.getAsset(
|
||||
`/assets/${Server[server]}/event/${event.assetBundleName}/topscreen_rip/trim_eventtop.png`,
|
||||
async getTopscreenTrimImage(
|
||||
event: EventAssetContext,
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): Promise<Image> {
|
||||
const topscreenTrimImageBuffer = await this.provider.getAsset(
|
||||
this.getTopscreenTrimImagePath(event, displayedServerList),
|
||||
);
|
||||
return await loadImage(topscreenTrimImageBuffer);
|
||||
}
|
||||
@ -172,7 +212,7 @@ class EventDataRepository {
|
||||
tempServer: Server,
|
||||
): Promise<Image> {
|
||||
const server = getServerByPriority(event.startAt, [tempServer]);
|
||||
const logoImageBuffer = await bangDreamBestdoriProvider.getAsset(
|
||||
const logoImageBuffer = await this.provider.getAsset(
|
||||
`/assets/${Server[server]}/event/${event.assetBundleName}/images_rip/logo.png`,
|
||||
);
|
||||
return await loadImage(logoImageBuffer);
|
||||
@ -188,7 +228,7 @@ class EventDataRepository {
|
||||
event: EventRewardContext,
|
||||
server: Server,
|
||||
): Promise<Image | undefined> {
|
||||
const allStamps = await bangDreamBestdoriProvider.getJson<
|
||||
const allStamps = await this.provider.getJson<
|
||||
Record<string, any>
|
||||
>('/api/stamps/all.2.json');
|
||||
const rewardId = this.pickRewardId(event.pointRewards, 'stamp');
|
||||
@ -202,7 +242,7 @@ class EventDataRepository {
|
||||
|
||||
const serverName = this.pickReleasedServerName(event.startAt, server, 'jp');
|
||||
try {
|
||||
const stampBuffer = await bangDreamBestdoriProvider.getAsset(
|
||||
const stampBuffer = await this.provider.getAsset(
|
||||
`/assets/${serverName}/stamp/01_rip/${stampAssetName}.png`,
|
||||
{ ignoreError: false },
|
||||
);
|
||||
@ -227,6 +267,9 @@ class EventDataRepository {
|
||||
const rewardId = this.pickRewardId(event.rankingRewards, 'deco_pins');
|
||||
if (rewardId === undefined) return undefined;
|
||||
|
||||
const { bangDreamMainDataRepository } = await import(
|
||||
'@/qqbot/plugins/bangDream/tsugu/models/main-data-repository'
|
||||
);
|
||||
const allDeco = bangDreamMainDataRepository.getCollection<
|
||||
Record<string, any>
|
||||
>('deco');
|
||||
@ -235,7 +278,7 @@ class EventDataRepository {
|
||||
|
||||
const serverName = this.pickReleasedServerName(event.startAt, server, 'cn');
|
||||
try {
|
||||
const decoBuffer = await bangDreamBestdoriProvider.getAsset(
|
||||
const decoBuffer = await this.provider.getAsset(
|
||||
`/assets/${serverName}/deco/pins_rip/${decoAssetName}.png`,
|
||||
{ ignoreError: false },
|
||||
);
|
||||
|
||||
@ -214,8 +214,13 @@ export class Event {
|
||||
*
|
||||
* @returns 异步处理结果。
|
||||
*/
|
||||
async getEventBGImage(): Promise<Image | Canvas> {
|
||||
return await eventDataRepository.getBackgroundImage(this);
|
||||
async getEventBGImage(
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): Promise<Image | Canvas> {
|
||||
return await eventDataRepository.getBackgroundImage(
|
||||
this,
|
||||
displayedServerList,
|
||||
);
|
||||
}
|
||||
//活动规则轮播图
|
||||
/**
|
||||
@ -233,8 +238,13 @@ export class Event {
|
||||
*
|
||||
* @returns 异步处理结果。
|
||||
*/
|
||||
async getEventTopscreenTrimImage(): Promise<Image> {
|
||||
return await eventDataRepository.getTopscreenTrimImage(this);
|
||||
async getEventTopscreenTrimImage(
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): Promise<Image> {
|
||||
return await eventDataRepository.getTopscreenTrimImage(
|
||||
this,
|
||||
displayedServerList,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 在 Event 模型中获取活动Logo图片。
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import {
|
||||
EventDataRepository,
|
||||
type EventAssetContext,
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/models/event-data-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}`),
|
||||
};
|
||||
}
|
||||
|
||||
function createEventAssetContext(): EventAssetContext {
|
||||
return {
|
||||
assetBundleName: 'event_test',
|
||||
bannerAssetBundleName: 'homebanner_test',
|
||||
startAt: [1, null, null, 2, null],
|
||||
};
|
||||
}
|
||||
|
||||
describe('BangDream event data repository', () => {
|
||||
it('builds event background paths from the requested server priority', () => {
|
||||
const repository = new EventDataRepository(createProviderMock());
|
||||
const event = createEventAssetContext();
|
||||
|
||||
expect(repository.getBackgroundImagePath(event, [Server.jp])).toBe(
|
||||
'/assets/jp/event/event_test/topscreen_rip/bg_eventtop.png',
|
||||
);
|
||||
expect(repository.getTopscreenTrimImagePath(event, [Server.jp])).toBe(
|
||||
'/assets/jp/event/event_test/topscreen_rip/trim_eventtop.png',
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to released servers when requested server has no event assets', () => {
|
||||
const repository = new EventDataRepository(createProviderMock());
|
||||
const event = createEventAssetContext();
|
||||
|
||||
expect(repository.getBackgroundImagePath(event, [Server.en])).toBe(
|
||||
'/assets/cn/event/event_test/topscreen_rip/bg_eventtop.png',
|
||||
);
|
||||
});
|
||||
|
||||
it('routes event detail requests through the provider cache policy', async () => {
|
||||
const provider = createProviderMock();
|
||||
provider.getJson.mockResolvedValue({ eventName: ['test'] });
|
||||
const repository = new EventDataRepository(provider);
|
||||
|
||||
await expect(repository.getDetail(50, false)).resolves.toEqual({
|
||||
eventName: ['test'],
|
||||
});
|
||||
|
||||
expect(provider.getJson).toHaveBeenCalledWith('/api/events/50.json', {
|
||||
cacheTime: Infinity,
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user