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 保持非空输出。
|
- 已新增 `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/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/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 ...`,路径用正斜杠。
|
- 固化:Windows 下不要用反斜杠测试路径直接调用 Jest pattern,容易出现 `Pattern ... - 0 matches`;指定文件测试统一使用 `pnpm exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangDream/tsugu/<file>.spec.ts ...`,路径用正斜杠。
|
||||||
|
|
||||||
### Phase 4:搜索 specification 和 matcher
|
### 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 { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||||
import { Event } from '@/qqbot/plugins/bangDream/tsugu/models/event';
|
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 { 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 {
|
export class CutoffEventTop {
|
||||||
eventId: number;
|
eventId: number;
|
||||||
@ -12,22 +15,8 @@ export class CutoffEventTop {
|
|||||||
status: BangDreamEventStatus;
|
status: BangDreamEventStatus;
|
||||||
isInitfull: boolean = false;
|
isInitfull: boolean = false;
|
||||||
isExist = false;
|
isExist = false;
|
||||||
points: {
|
points: CutoffEventTopPoint[];
|
||||||
time: number;
|
users: CutoffEventTopUser[];
|
||||||
uid: number;
|
|
||||||
value: number;
|
|
||||||
}[];
|
|
||||||
users: {
|
|
||||||
uid: number;
|
|
||||||
name: string;
|
|
||||||
introduction: string;
|
|
||||||
rank: number;
|
|
||||||
sid: number;
|
|
||||||
strained: number;
|
|
||||||
degrees: number[];
|
|
||||||
ranking: number;
|
|
||||||
currentPt: number;
|
|
||||||
}[];
|
|
||||||
/**
|
/**
|
||||||
* 构造 CutoffEventTop 实例,并初始化该模型的本地基础字段。
|
* 构造 CutoffEventTop 实例,并初始化该模型的本地基础字段。
|
||||||
*
|
*
|
||||||
@ -64,30 +53,17 @@ export class CutoffEventTop {
|
|||||||
if (this.isInitfull) {
|
if (this.isInitfull) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const topData = await callAPIAndCacheResponse(
|
const topData = await cutoffEventTopRepository.getTopData(
|
||||||
`${bestdoriUrl}/api/eventtop/data?server=${<number>this.server}&event=${this.eventId}&mid=0&interval=3600000`,
|
this.eventId,
|
||||||
|
this.server,
|
||||||
);
|
);
|
||||||
if (topData == undefined) {
|
if (topData == undefined) {
|
||||||
this.isExist = false;
|
this.isExist = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isExist = true;
|
this.isExist = true;
|
||||||
this.points = topData['points'] as {
|
this.points = topData.points;
|
||||||
time: number;
|
this.users = topData.users;
|
||||||
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;
|
|
||||||
}[];
|
|
||||||
if (this.points.length == 0 || this.users.length == 0) {
|
if (this.points.length == 0 || this.users.length == 0) {
|
||||||
//如果没有数据,返回不存在
|
//如果没有数据,返回不存在
|
||||||
this.isExist = false;
|
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 { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||||
import { Degree } from '@/qqbot/plugins/bangDream/tsugu/models/degree';
|
import { Degree } from '@/qqbot/plugins/bangDream/tsugu/models/degree';
|
||||||
import { drawText } from '@/qqbot/plugins/bangDream/tsugu/canvas/text';
|
import { drawText } from '@/qqbot/plugins/bangDream/tsugu/canvas/text';
|
||||||
import { downloadFileCache } from '@/qqbot/plugins/bangDream/tsugu/data-clients/asset-cache-client';
|
import { playerRankingResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/player-ranking-resource-repository';
|
||||||
import { bestdoriUrl } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
uid: number;
|
uid: number;
|
||||||
@ -55,8 +54,10 @@ export async function drawPlayerRankingInList(
|
|||||||
if (user.ranking == undefined) {
|
if (user.ranking == undefined) {
|
||||||
return;
|
return;
|
||||||
} else if (user.ranking > 0 && user.ranking <= 3) {
|
} else if (user.ranking > 0 && user.ranking <= 3) {
|
||||||
const rankIamgeBuffer = await downloadFileCache(
|
const rankIamgeBuffer =
|
||||||
`${bestdoriUrl}/res/image/${Server[server]}_${user.ranking}.png`,
|
await playerRankingResourceRepository.getRankImageBuffer(
|
||||||
|
server,
|
||||||
|
user.ranking,
|
||||||
);
|
);
|
||||||
rankingImage = await loadImage(rankIamgeBuffer);
|
rankingImage = await loadImage(rankIamgeBuffer);
|
||||||
ctx.drawImage(rankingImage, 12, 45, 45, 21);
|
ctx.drawImage(rankingImage, 12, 45, 45, 21);
|
||||||
|
|||||||
@ -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