refactor: 收口 BangDream 档线榜资源仓储
This commit is contained in:
parent
dbc2fab5af
commit
fe8542c687
@ -309,6 +309,7 @@ export interface TsuguHook {
|
||||
- 已新增 `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 输出非空。
|
||||
- 已新增 `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 输出非空。
|
||||
- 固化: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,59 @@
|
||||
import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider';
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
|
||||
export interface CutoffEventTopPoint {
|
||||
time: number;
|
||||
uid: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface CutoffEventTopUser {
|
||||
currentPt: number;
|
||||
degrees: number[];
|
||||
introduction: string;
|
||||
name: string;
|
||||
rank: number;
|
||||
ranking: number;
|
||||
sid: number;
|
||||
strained: number;
|
||||
uid: number;
|
||||
}
|
||||
|
||||
export interface CutoffEventTopData {
|
||||
points: CutoffEventTopPoint[];
|
||||
users: CutoffEventTopUser[];
|
||||
}
|
||||
|
||||
export class CutoffEventTopRepository {
|
||||
constructor(
|
||||
private readonly provider: BangDreamDataProvider = bangDreamBestdoriProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取活动前十榜数据路径。
|
||||
*
|
||||
* @param eventId - 活动 ID。
|
||||
* @param server - 目标服务器。
|
||||
*/
|
||||
getTopDataPath(eventId: number, server: Server): string {
|
||||
return `/api/eventtop/data?server=${server}&event=${eventId}&mid=0&interval=3600000`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活动前十榜数据。
|
||||
*
|
||||
* @param eventId - 活动 ID。
|
||||
* @param server - 目标服务器。
|
||||
*/
|
||||
async getTopData(
|
||||
eventId: number,
|
||||
server: Server,
|
||||
): Promise<CutoffEventTopData | undefined> {
|
||||
return await this.provider.getJson<CutoffEventTopData>(
|
||||
this.getTopDataPath(eventId, server),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const cutoffEventTopRepository = new CutoffEventTopRepository();
|
||||
@ -1,8 +1,11 @@
|
||||
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
import { Event } from '@/qqbot/plugins/bangDream/tsugu/models/event';
|
||||
import { callAPIAndCacheResponse } from '@/qqbot/plugins/bangDream/tsugu/data-clients/api-cache-client';
|
||||
import { bestdoriUrl } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||
import { BangDreamEventStatus } from '@/qqbot/plugins/bangDream/tsugu/models/bangdream-constants';
|
||||
import {
|
||||
cutoffEventTopRepository,
|
||||
type CutoffEventTopPoint,
|
||||
type CutoffEventTopUser,
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/models/cutoff-event-top-repository';
|
||||
|
||||
export class CutoffEventTop {
|
||||
eventId: number;
|
||||
@ -12,22 +15,8 @@ export class CutoffEventTop {
|
||||
status: BangDreamEventStatus;
|
||||
isInitfull: boolean = false;
|
||||
isExist = false;
|
||||
points: {
|
||||
time: number;
|
||||
uid: number;
|
||||
value: number;
|
||||
}[];
|
||||
users: {
|
||||
uid: number;
|
||||
name: string;
|
||||
introduction: string;
|
||||
rank: number;
|
||||
sid: number;
|
||||
strained: number;
|
||||
degrees: number[];
|
||||
ranking: number;
|
||||
currentPt: number;
|
||||
}[];
|
||||
points: CutoffEventTopPoint[];
|
||||
users: CutoffEventTopUser[];
|
||||
/**
|
||||
* 构造 CutoffEventTop 实例,并初始化该模型的本地基础字段。
|
||||
*
|
||||
@ -64,30 +53,17 @@ export class CutoffEventTop {
|
||||
if (this.isInitfull) {
|
||||
return;
|
||||
}
|
||||
const topData = await callAPIAndCacheResponse(
|
||||
`${bestdoriUrl}/api/eventtop/data?server=${<number>this.server}&event=${this.eventId}&mid=0&interval=3600000`,
|
||||
const topData = await cutoffEventTopRepository.getTopData(
|
||||
this.eventId,
|
||||
this.server,
|
||||
);
|
||||
if (topData == undefined) {
|
||||
this.isExist = false;
|
||||
return;
|
||||
}
|
||||
this.isExist = true;
|
||||
this.points = topData['points'] as {
|
||||
time: number;
|
||||
uid: number;
|
||||
value: number;
|
||||
}[];
|
||||
this.users = topData['users'] as {
|
||||
uid: number;
|
||||
name: string;
|
||||
introduction: string;
|
||||
rank: number;
|
||||
sid: number;
|
||||
strained: number;
|
||||
degrees: number[];
|
||||
ranking: number;
|
||||
currentPt: number;
|
||||
}[];
|
||||
this.points = topData.points;
|
||||
this.users = topData.users;
|
||||
if (this.points.length == 0 || this.users.length == 0) {
|
||||
//如果没有数据,返回不存在
|
||||
this.isExist = false;
|
||||
|
||||
@ -6,8 +6,7 @@ import { drawDegree } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/degree
|
||||
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
import { Degree } from '@/qqbot/plugins/bangDream/tsugu/models/degree';
|
||||
import { drawText } from '@/qqbot/plugins/bangDream/tsugu/canvas/text';
|
||||
import { downloadFileCache } from '@/qqbot/plugins/bangDream/tsugu/data-clients/asset-cache-client';
|
||||
import { bestdoriUrl } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||
import { playerRankingResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/player-ranking-resource-repository';
|
||||
|
||||
interface User {
|
||||
uid: number;
|
||||
@ -55,9 +54,11 @@ export async function drawPlayerRankingInList(
|
||||
if (user.ranking == undefined) {
|
||||
return;
|
||||
} else if (user.ranking > 0 && user.ranking <= 3) {
|
||||
const rankIamgeBuffer = await downloadFileCache(
|
||||
`${bestdoriUrl}/res/image/${Server[server]}_${user.ranking}.png`,
|
||||
);
|
||||
const rankIamgeBuffer =
|
||||
await playerRankingResourceRepository.getRankImageBuffer(
|
||||
server,
|
||||
user.ranking,
|
||||
);
|
||||
rankingImage = await loadImage(rankIamgeBuffer);
|
||||
ctx.drawImage(rankingImage, 12, 45, 45, 21);
|
||||
} else {
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider';
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
|
||||
export class PlayerRankingResourceRepository {
|
||||
constructor(
|
||||
private readonly provider: BangDreamDataProvider = bangDreamBestdoriProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取玩家排名徽章资源路径。
|
||||
*
|
||||
* @param server - 目标服务器。
|
||||
* @param ranking - 排名。
|
||||
*/
|
||||
getRankImagePath(server: Server, ranking: number): string {
|
||||
return `/res/image/${Server[server]}_${ranking}.png`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载玩家排名徽章资源。
|
||||
*
|
||||
* @param server - 目标服务器。
|
||||
* @param ranking - 排名。
|
||||
*/
|
||||
async getRankImageBuffer(server: Server, ranking: number): Promise<Buffer> {
|
||||
return await this.provider.getAsset(this.getRankImagePath(server, ranking));
|
||||
}
|
||||
}
|
||||
|
||||
export const playerRankingResourceRepository =
|
||||
new PlayerRankingResourceRepository();
|
||||
@ -0,0 +1,41 @@
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import { CutoffEventTopRepository } from '@/qqbot/plugins/bangDream/tsugu/models/cutoff-event-top-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 cutoff event top repository', () => {
|
||||
it('builds event top data paths', () => {
|
||||
const repository = new CutoffEventTopRepository(createProviderMock());
|
||||
|
||||
expect(repository.getTopDataPath(50, Server.cn)).toBe(
|
||||
'/api/eventtop/data?server=3&event=50&mid=0&interval=3600000',
|
||||
);
|
||||
});
|
||||
|
||||
it('routes event top data requests through the provider', async () => {
|
||||
const provider = createProviderMock();
|
||||
provider.getJson.mockResolvedValue({
|
||||
points: [{ time: 1, uid: 2, value: 3 }],
|
||||
users: [],
|
||||
});
|
||||
const repository = new CutoffEventTopRepository(provider);
|
||||
|
||||
await expect(repository.getTopData(50, Server.cn)).resolves.toEqual({
|
||||
points: [{ time: 1, uid: 2, value: 3 }],
|
||||
users: [],
|
||||
});
|
||||
|
||||
expect(provider.getJson).toHaveBeenCalledWith(
|
||||
'/api/eventtop/data?server=3&event=50&mid=0&interval=3600000',
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,36 @@
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
import { PlayerRankingResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/player-ranking-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 player ranking resource repository', () => {
|
||||
it('builds player ranking image paths', () => {
|
||||
const repository = new PlayerRankingResourceRepository(createProviderMock());
|
||||
|
||||
expect(repository.getRankImagePath(Server.cn, 1)).toBe(
|
||||
'/res/image/cn_1.png',
|
||||
);
|
||||
});
|
||||
|
||||
it('downloads player ranking images through the provider', async () => {
|
||||
const provider = createProviderMock();
|
||||
const rankBuffer = Buffer.from('rank');
|
||||
provider.getAsset.mockResolvedValue(rankBuffer);
|
||||
const repository = new PlayerRankingResourceRepository(provider);
|
||||
|
||||
await expect(repository.getRankImageBuffer(Server.cn, 1)).resolves.toBe(
|
||||
rankBuffer,
|
||||
);
|
||||
|
||||
expect(provider.getAsset).toHaveBeenCalledWith('/res/image/cn_1.png');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user