fix: 按BangDream能力懒加载目录数据
This commit is contained in:
parent
4e5ed876c3
commit
0fe3f99c36
@ -58,9 +58,9 @@ export class BangDreamCommandContext {
|
||||
}
|
||||
|
||||
async checkHealth() {
|
||||
await waitForBangDreamCatalogReady();
|
||||
const data = bangdreamCatalogCache as { cards?: unknown; songs?: unknown };
|
||||
if (!data.songs || !data.cards) {
|
||||
await waitForBangDreamCatalogReady(['songs']);
|
||||
const data = bangdreamCatalogCache as { songs?: unknown };
|
||||
if (!data.songs) {
|
||||
throw new Error('BangDream 数据配置未加载');
|
||||
}
|
||||
fuzzySearch('夏祭り');
|
||||
|
||||
@ -11,15 +11,16 @@ import {
|
||||
sleepBangDreamRuntime,
|
||||
} from '@/modules/qqbot/plugins/bangdream/src/infrastructure/integration/runtime-io';
|
||||
|
||||
const bangdreamCatalogCache: Record<string, any> = {};
|
||||
export type BangDreamCatalogKey = keyof typeof bestdoriApiPath;
|
||||
|
||||
const bangdreamCatalogCache: Record<string, any> = Object.fromEntries(
|
||||
Object.keys(bestdoriApiPath).map((key) => [key, {}]),
|
||||
);
|
||||
const REQUIRED_CATALOG_KEYS = [
|
||||
'cards',
|
||||
'characters',
|
||||
'events',
|
||||
'gacha',
|
||||
'songs',
|
||||
];
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
const DEFAULT_CATALOG_READY_TIMEOUT_MS = 15000;
|
||||
const catalogLoadPromises = new Map<BangDreamCatalogKey, Promise<void>>();
|
||||
|
||||
function getCatalogReadyTimeoutMs(): number {
|
||||
return normalizeBangDreamPositiveInteger(
|
||||
@ -28,8 +29,22 @@ function getCatalogReadyTimeoutMs(): number {
|
||||
);
|
||||
}
|
||||
|
||||
function isCatalogReady(): boolean {
|
||||
return REQUIRED_CATALOG_KEYS.every((key) => {
|
||||
function normalizeCatalogKeys(
|
||||
keys: readonly BangDreamCatalogKey[] = REQUIRED_CATALOG_KEYS,
|
||||
): BangDreamCatalogKey[] {
|
||||
return [...new Set(keys)].filter((key) => key in bestdoriApiPath);
|
||||
}
|
||||
|
||||
function isCatalogKeyReady(key: BangDreamCatalogKey): boolean {
|
||||
const collection = bangdreamCatalogCache[key];
|
||||
if (!collection) return false;
|
||||
return typeof collection === 'object'
|
||||
? Object.keys(collection).length > 0
|
||||
: true;
|
||||
}
|
||||
|
||||
function isCatalogReady(keys?: readonly BangDreamCatalogKey[]): boolean {
|
||||
return normalizeCatalogKeys(keys).every((key) => {
|
||||
const collection = bangdreamCatalogCache[key];
|
||||
return collection && Object.keys(collection).length > 0;
|
||||
});
|
||||
@ -45,91 +60,126 @@ async function rejectAfter(ms: number): Promise<never> {
|
||||
*
|
||||
* @param useCache - use缓存参数,未传入时使用默认值。
|
||||
*/
|
||||
async function loadCatalogData(useCache: boolean = false) {
|
||||
async function loadCatalogData(
|
||||
keys?: readonly BangDreamCatalogKey[],
|
||||
useCache: boolean = false,
|
||||
) {
|
||||
const catalogKeys = normalizeCatalogKeys(keys);
|
||||
logger('catalog', 'loading catalog...');
|
||||
const promiseAll = Object.keys(bestdoriApiPath).map(async (key) => {
|
||||
if (useCache) {
|
||||
return (bangdreamCatalogCache[key] =
|
||||
await bangdreamBestdoriProvider.getJson(bestdoriApiPath[key], {
|
||||
cacheTime: 1 / 0,
|
||||
}));
|
||||
} else {
|
||||
try {
|
||||
return (bangdreamCatalogCache[key] =
|
||||
await bangdreamBestdoriProvider.getJson(bestdoriApiPath[key]));
|
||||
} catch {
|
||||
logger('catalog', `load ${key} failed`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promiseAll);
|
||||
for (const key of catalogKeys) {
|
||||
await loadCatalogKey(key, useCache);
|
||||
}
|
||||
|
||||
const cardsCnFix =
|
||||
await bangdreamStaticPatchProvider.readJson<Record<string, unknown>>(
|
||||
'cards-cn-fix.json',
|
||||
);
|
||||
for (const key in cardsCnFix) {
|
||||
bangdreamCatalogCache['cards'][key] = cardsCnFix[key];
|
||||
}
|
||||
const skillsCnFix =
|
||||
await bangdreamStaticPatchProvider.readJson<Record<string, unknown>>(
|
||||
'skills-cn-fix.json',
|
||||
);
|
||||
for (const key in skillsCnFix) {
|
||||
bangdreamCatalogCache['skills'][key] = skillsCnFix[key];
|
||||
}
|
||||
const areaItemFix =
|
||||
await bangdreamStaticPatchProvider.readJson<Record<string, unknown>>(
|
||||
'area-item-fix.json',
|
||||
);
|
||||
for (const key in areaItemFix) {
|
||||
if (bangdreamCatalogCache['areaItems'][key] == undefined) {
|
||||
bangdreamCatalogCache['areaItems'][key] = areaItemFix[key];
|
||||
}
|
||||
}
|
||||
try {
|
||||
const songNickname = await bangdreamStaticPatchProvider.readExcelRows<{
|
||||
Id: number;
|
||||
Nickname: string;
|
||||
}>('nickname-song.xlsx');
|
||||
for (let i = 0; i < songNickname.length; i++) {
|
||||
const element = songNickname[i];
|
||||
if (bangdreamCatalogCache['songs'][element['Id'].toString()]) {
|
||||
bangdreamCatalogCache['songs'][element['Id'].toString()]['nickname'] =
|
||||
element['Nickname'];
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger('catalog', '读取 nickname-song.xlsx 失败');
|
||||
}
|
||||
await applyStaticPatches(catalogKeys);
|
||||
logger('catalog', 'catalog loaded');
|
||||
}
|
||||
|
||||
let initialLoadPromise: Promise<void> | undefined;
|
||||
async function loadCatalogKey(key: BangDreamCatalogKey, useCache: boolean) {
|
||||
if (isCatalogKeyReady(key)) return;
|
||||
const pending = catalogLoadPromises.get(key);
|
||||
if (pending) return await pending;
|
||||
|
||||
function ensureCatalogInitialLoad() {
|
||||
if (!initialLoadPromise) {
|
||||
logger('catalog', 'initializing...');
|
||||
initialLoadPromise = loadCatalogData(true).then(() => {
|
||||
logger('catalog', 'initializing done');
|
||||
});
|
||||
const promise = (async () => {
|
||||
if (useCache) {
|
||||
bangdreamCatalogCache[key] = await bangdreamBestdoriProvider.getJson(
|
||||
bestdoriApiPath[key],
|
||||
{
|
||||
cacheTime: 1 / 0,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
bangdreamCatalogCache[key] = await bangdreamBestdoriProvider.getJson(
|
||||
bestdoriApiPath[key],
|
||||
);
|
||||
} catch {
|
||||
logger('catalog', `load ${key} failed`);
|
||||
}
|
||||
})();
|
||||
catalogLoadPromises.set(key, promise);
|
||||
try {
|
||||
await promise;
|
||||
} finally {
|
||||
catalogLoadPromises.delete(key);
|
||||
}
|
||||
return initialLoadPromise;
|
||||
}
|
||||
|
||||
async function applyStaticPatches(keys: readonly BangDreamCatalogKey[]) {
|
||||
const keySet = new Set(keys);
|
||||
if (keySet.has('cards')) {
|
||||
const cardsCnFix =
|
||||
await bangdreamStaticPatchProvider.readJson<Record<string, unknown>>(
|
||||
'cards-cn-fix.json',
|
||||
);
|
||||
for (const key in cardsCnFix) {
|
||||
bangdreamCatalogCache['cards'][key] = cardsCnFix[key];
|
||||
}
|
||||
}
|
||||
if (keySet.has('skills')) {
|
||||
const skillsCnFix =
|
||||
await bangdreamStaticPatchProvider.readJson<Record<string, unknown>>(
|
||||
'skills-cn-fix.json',
|
||||
);
|
||||
for (const key in skillsCnFix) {
|
||||
bangdreamCatalogCache['skills'][key] = skillsCnFix[key];
|
||||
}
|
||||
}
|
||||
if (keySet.has('areaItems')) {
|
||||
const areaItemFix =
|
||||
await bangdreamStaticPatchProvider.readJson<Record<string, unknown>>(
|
||||
'area-item-fix.json',
|
||||
);
|
||||
for (const key in areaItemFix) {
|
||||
if (bangdreamCatalogCache['areaItems'][key] == undefined) {
|
||||
bangdreamCatalogCache['areaItems'][key] = areaItemFix[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keySet.has('songs')) {
|
||||
try {
|
||||
const songNickname = await bangdreamStaticPatchProvider.readExcelRows<{
|
||||
Id: number;
|
||||
Nickname: string;
|
||||
}>('nickname-song.xlsx');
|
||||
for (let i = 0; i < songNickname.length; i++) {
|
||||
const element = songNickname[i];
|
||||
if (bangdreamCatalogCache['songs'][element['Id'].toString()]) {
|
||||
bangdreamCatalogCache['songs'][element['Id'].toString()]['nickname'] =
|
||||
element['Nickname'];
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger('catalog', '读取 nickname-song.xlsx 失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureCatalogInitialLoad(keys?: readonly BangDreamCatalogKey[]) {
|
||||
const catalogKeys = normalizeCatalogKeys(keys);
|
||||
if (isCatalogReady(catalogKeys)) return;
|
||||
logger('catalog', 'initializing...');
|
||||
await loadCatalogData(catalogKeys, true);
|
||||
logger('catalog', 'initializing done');
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待 BangDream 目录数据完成首次加载。
|
||||
*/
|
||||
export async function waitForBangDreamCatalogReady(): Promise<void> {
|
||||
if (isCatalogReady()) {
|
||||
export async function waitForBangDreamCatalogReady(
|
||||
keys?: readonly BangDreamCatalogKey[],
|
||||
): Promise<void> {
|
||||
const catalogKeys = normalizeCatalogKeys(keys);
|
||||
if (isCatalogReady(catalogKeys)) {
|
||||
return;
|
||||
}
|
||||
await Promise.race([
|
||||
ensureCatalogInitialLoad(),
|
||||
ensureCatalogInitialLoad(catalogKeys),
|
||||
rejectAfter(getCatalogReadyTimeoutMs()),
|
||||
]);
|
||||
if (!isCatalogReady()) {
|
||||
if (!isCatalogReady(catalogKeys)) {
|
||||
throw new Error('BangDream 主数据未完成关键集合加载');
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,6 +46,7 @@ type BangDreamManifestOperation = {
|
||||
};
|
||||
|
||||
type BangDreamResolvedOperation = BangDreamManifestOperation & {
|
||||
catalogKeys?: BangDreamOperationModule['catalogKeys'];
|
||||
execute: BangDreamOperationModule['execute'];
|
||||
};
|
||||
|
||||
@ -131,6 +132,7 @@ function resolveBangDreamOperations(operations: BangDreamManifestOperation[]) {
|
||||
operation.key,
|
||||
{
|
||||
...operation,
|
||||
catalogKeys: operationModule.catalogKeys,
|
||||
execute: operationModule.execute,
|
||||
},
|
||||
] as const;
|
||||
@ -153,9 +155,6 @@ async function executeBangDreamOperation(options: {
|
||||
await options.lifecycle.beforeParse(operationContext);
|
||||
|
||||
try {
|
||||
operationContext.stage = 'catalog';
|
||||
await waitForBangDreamCatalogReady();
|
||||
|
||||
operationContext.stage = 'operation';
|
||||
const operation = options.operationsByKey.get(options.operationKey);
|
||||
if (!operation) {
|
||||
@ -164,6 +163,9 @@ async function executeBangDreamOperation(options: {
|
||||
operationContext.handlerName = operation.handlerName;
|
||||
await options.lifecycle.afterResolve(operationContext);
|
||||
|
||||
operationContext.stage = 'catalog';
|
||||
await waitForBangDreamCatalogReady(operation.catalogKeys);
|
||||
|
||||
operationContext.stage = 'handler';
|
||||
await options.lifecycle.beforeRender(operationContext);
|
||||
const output = await operation.execute(options.input, options.context);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { Card } from '@/modules/qqbot/plugins/bangdream/src/domain/card/card.model';
|
||||
import { BANGDREAM_CARD_ILLUSTRATION_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const cardIllustrationOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_CARD_ILLUSTRATION_CATALOG_KEYS,
|
||||
handlerName: 'getCardIllustration',
|
||||
execute: async (input, context) => {
|
||||
const cardId = context.requireNumber(
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { drawCardDetail } from '@/modules/qqbot/plugins/bangdream/src/domain/card/card-detail.renderer';
|
||||
import { drawCardList } from '@/modules/qqbot/plugins/bangdream/src/domain/card/card-search.renderer';
|
||||
import { BANGDREAM_CARD_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const cardSearchOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_CARD_CATALOG_KEYS,
|
||||
handlerName: 'searchCard',
|
||||
execute: async (input, context) => {
|
||||
const query = context.requireText(input, '请提供卡牌关键词或卡牌 ID');
|
||||
|
||||
@ -0,0 +1,105 @@
|
||||
import type { BangDreamCatalogKey } from '@/modules/qqbot/plugins/bangdream/src/application/catalog/bangdream-catalog-cache';
|
||||
|
||||
export const BANGDREAM_BAND_CATALOG_KEYS = [
|
||||
'singer',
|
||||
'bands',
|
||||
'characters',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_SONG_CATALOG_KEYS = [
|
||||
'songs',
|
||||
'meta',
|
||||
...BANGDREAM_BAND_CATALOG_KEYS,
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_SONG_SEARCH_CATALOG_KEYS = [
|
||||
...BANGDREAM_SONG_CATALOG_KEYS,
|
||||
'events',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_CARD_ILLUSTRATION_CATALOG_KEYS = [
|
||||
'cards',
|
||||
'skills',
|
||||
'characters',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_CARD_CATALOG_KEYS = [
|
||||
...BANGDREAM_CARD_ILLUSTRATION_CATALOG_KEYS,
|
||||
'singer',
|
||||
'bands',
|
||||
'events',
|
||||
'gacha',
|
||||
'costumes',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_CHARACTER_CATALOG_KEYS = [
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_EVENT_CATALOG_KEYS = [
|
||||
'events',
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
'cards',
|
||||
'skills',
|
||||
'gacha',
|
||||
'songs',
|
||||
'meta',
|
||||
'degrees',
|
||||
'deco',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_EVENT_STAGE_CATALOG_KEYS = [
|
||||
'events',
|
||||
'characters',
|
||||
'songs',
|
||||
'meta',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_GACHA_CATALOG_KEYS = [
|
||||
'gacha',
|
||||
'cards',
|
||||
'skills',
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
'events',
|
||||
'items',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_GACHA_SIMULATE_CATALOG_KEYS = [
|
||||
'gacha',
|
||||
'cards',
|
||||
'skills',
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_CUTOFF_BASE_CATALOG_KEYS = [
|
||||
'events',
|
||||
'characters',
|
||||
'rates',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_CUTOFF_DETAIL_CATALOG_KEYS = [
|
||||
...BANGDREAM_CUTOFF_BASE_CATALOG_KEYS,
|
||||
'cards',
|
||||
'skills',
|
||||
'singer',
|
||||
'bands',
|
||||
'degrees',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
|
||||
export const BANGDREAM_PLAYER_CATALOG_KEYS = [
|
||||
'cards',
|
||||
'skills',
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
'degrees',
|
||||
'areaItems',
|
||||
] as const satisfies readonly BangDreamCatalogKey[];
|
||||
@ -1,8 +1,10 @@
|
||||
import { drawCharacterDetail } from '@/modules/qqbot/plugins/bangdream/src/domain/character/character-detail.renderer';
|
||||
import { drawCharacterList } from '@/modules/qqbot/plugins/bangdream/src/domain/character/character-search.renderer';
|
||||
import { BANGDREAM_CHARACTER_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const characterSearchOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_CHARACTER_CATALOG_KEYS,
|
||||
handlerName: 'searchCharacter',
|
||||
execute: async (input, context) => {
|
||||
const query = context.requireText(input, '请提供角色关键词或角色 ID');
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { drawCutoffAll } from '@/modules/qqbot/plugins/bangdream/src/domain/cutoff/cutoff-all.renderer';
|
||||
import { getPresentEvent } from '@/modules/qqbot/plugins/bangdream/src/domain/event/event.model';
|
||||
import { BANGDREAM_CUTOFF_BASE_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const cutoffAllOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_CUTOFF_BASE_CATALOG_KEYS,
|
||||
handlerName: 'getCutoffAll',
|
||||
execute: async (input, context) => {
|
||||
const tokens = context.getTokens(input);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { drawCutoffDetail } from '@/modules/qqbot/plugins/bangdream/src/domain/cutoff/cutoff-detail.renderer';
|
||||
import { drawCutoffEventTop } from '@/modules/qqbot/plugins/bangdream/src/domain/cutoff/cutoff-event-top.renderer';
|
||||
import { getPresentEvent } from '@/modules/qqbot/plugins/bangdream/src/domain/event/event.model';
|
||||
import { BANGDREAM_CUTOFF_DETAIL_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
const CUTOFF_DETAIL_COMMAND_ALIASES = new Set([
|
||||
@ -12,6 +13,7 @@ const CUTOFF_DETAIL_COMMAND_ALIASES = new Set([
|
||||
const DEFAULT_CUTOFF_DETAIL_TIER = 1000;
|
||||
|
||||
export const cutoffDetailOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_CUTOFF_DETAIL_CATALOG_KEYS,
|
||||
handlerName: 'getCutoffDetail',
|
||||
execute: async (input, context) => {
|
||||
const tokens = context.getTokens(input);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { drawCutoffListOfRecentEvent } from '@/modules/qqbot/plugins/bangdream/src/domain/cutoff/cutoff-recent.renderer';
|
||||
import { getPresentEvent } from '@/modules/qqbot/plugins/bangdream/src/domain/event/event.model';
|
||||
import { BANGDREAM_CUTOFF_BASE_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const cutoffRecentOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_CUTOFF_BASE_CATALOG_KEYS,
|
||||
handlerName: 'getCutoffRecent',
|
||||
execute: async (input, context) => {
|
||||
const tokens = context.getTokens(input);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { drawEventDetail } from '@/modules/qqbot/plugins/bangdream/src/domain/event/event-detail.renderer';
|
||||
import { drawEventList } from '@/modules/qqbot/plugins/bangdream/src/domain/event/event-search.renderer';
|
||||
import { BANGDREAM_EVENT_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const eventSearchOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_EVENT_CATALOG_KEYS,
|
||||
handlerName: 'searchEvent',
|
||||
execute: async (input, context) => {
|
||||
const query = context.requireText(input, '请提供活动关键词或活动 ID');
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { getPresentEvent } from '@/modules/qqbot/plugins/bangdream/src/domain/event/event.model';
|
||||
import { drawEventStage } from '@/modules/qqbot/plugins/bangdream/src/domain/event/event-stage.renderer';
|
||||
import { BANGDREAM_EVENT_STAGE_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const eventStageOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_EVENT_STAGE_CATALOG_KEYS,
|
||||
expectedImageCount: 5,
|
||||
handlerName: 'getEventStage',
|
||||
execute: async (input, context) => {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { drawGachaDetail } from '@/modules/qqbot/plugins/bangdream/src/domain/gacha/gacha-detail.renderer';
|
||||
import { BANGDREAM_GACHA_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const gachaSearchOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_GACHA_CATALOG_KEYS,
|
||||
handlerName: 'searchGacha',
|
||||
execute: async (input, context) => {
|
||||
const gachaId = context.requireNumber(
|
||||
|
||||
@ -7,9 +7,11 @@ import {
|
||||
BANGDREAM_GACHA_DEFAULT_SPIN_COUNT,
|
||||
isBirthdayGachaType,
|
||||
} from '@/modules/qqbot/plugins/bangdream/src/domain/policy/gacha.policy';
|
||||
import { BANGDREAM_GACHA_SIMULATE_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const gachaSimulateOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_GACHA_SIMULATE_CATALOG_KEYS,
|
||||
handlerName: 'simulateGacha',
|
||||
execute: async (input, context) => {
|
||||
const tokens = context.getTokens(input);
|
||||
|
||||
@ -4,6 +4,7 @@ import type {
|
||||
BangDreamCommandOutput,
|
||||
BangDreamOperationHandlerName,
|
||||
} from '@/modules/qqbot/plugins/bangdream/src/domain/common/bangdream.types';
|
||||
import type { BangDreamCatalogKey } from '@/modules/qqbot/plugins/bangdream/src/application/catalog/bangdream-catalog-cache';
|
||||
|
||||
export type BangDreamOperationExecute = (
|
||||
input: BangDreamCommandInput,
|
||||
@ -11,6 +12,7 @@ export type BangDreamOperationExecute = (
|
||||
) => Promise<BangDreamCommandOutput>;
|
||||
|
||||
export type BangDreamOperationModule = {
|
||||
catalogKeys?: readonly BangDreamCatalogKey[];
|
||||
execute: BangDreamOperationExecute;
|
||||
expectedImageCount?: number;
|
||||
handlerName: BangDreamOperationHandlerName;
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { drawPlayerDetail } from '@/modules/qqbot/plugins/bangdream/src/domain/player/player-detail.renderer';
|
||||
import { BANGDREAM_PLAYER_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const playerSearchOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_PLAYER_CATALOG_KEYS,
|
||||
handlerName: 'searchPlayer',
|
||||
execute: async (input, context) => {
|
||||
const tokens = context.getTokens(input);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { drawSongChart } from '@/modules/qqbot/plugins/bangdream/src/domain/song/song-chart.renderer';
|
||||
import { BANGDREAM_SONG_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const songChartOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_SONG_CATALOG_KEYS,
|
||||
handlerName: 'getSongChart',
|
||||
execute: async (input, context) => {
|
||||
const tokens = context.getTokens(input);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { Server } from '@/modules/qqbot/plugins/bangdream/src/domain/catalog/server.model';
|
||||
import { drawSongMetaList } from '@/modules/qqbot/plugins/bangdream/src/domain/song/song-meta.renderer';
|
||||
import { BANGDREAM_SONG_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const songMetaOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_SONG_CATALOG_KEYS,
|
||||
handlerName: 'getSongMeta',
|
||||
execute: async (input, context) => {
|
||||
const mainServer = context.pickMainServer(input, context.getTokens(input));
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { Server } from '@/modules/qqbot/plugins/bangdream/src/domain/catalog/server.model';
|
||||
import { fuzzySearch } from '@/modules/qqbot/plugins/bangdream/src/domain/search/fuzzy-search';
|
||||
import { drawSongRandom } from '@/modules/qqbot/plugins/bangdream/src/domain/song/song-random.renderer';
|
||||
import { BANGDREAM_SONG_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const songRandomOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_SONG_CATALOG_KEYS,
|
||||
handlerName: 'randomSong',
|
||||
execute: async (input, context) => {
|
||||
const query = context.pickText(input);
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { Song } from '@/modules/qqbot/plugins/bangdream/src/domain/song/song.model';
|
||||
import { drawSongDetail } from '@/modules/qqbot/plugins/bangdream/src/domain/song/song-detail.renderer';
|
||||
import { drawSongList } from '@/modules/qqbot/plugins/bangdream/src/domain/song/song-search.renderer';
|
||||
import { BANGDREAM_SONG_SEARCH_CATALOG_KEYS } from '@/modules/qqbot/plugins/bangdream/src/operations/catalog-keys';
|
||||
import type { BangDreamOperationModule } from '@/modules/qqbot/plugins/bangdream/src/operations/operation';
|
||||
|
||||
export const songSearchOperation: BangDreamOperationModule = {
|
||||
catalogKeys: BANGDREAM_SONG_SEARCH_CATALOG_KEYS,
|
||||
handlerName: 'searchSong',
|
||||
execute: async (input, context) => {
|
||||
const query = context.requireText(input, '请提供歌曲名或歌曲 ID');
|
||||
|
||||
@ -0,0 +1,129 @@
|
||||
import { getBangDreamOperationsByHandlerName } from '@/modules/qqbot/plugins/bangdream/src/operations';
|
||||
|
||||
describe('BangDream operation catalog keys', () => {
|
||||
it('declares the catalog keys required by each operation', () => {
|
||||
const operations = getBangDreamOperationsByHandlerName();
|
||||
|
||||
expectCatalogKeys(operations.get('searchSong')?.catalogKeys, [
|
||||
'songs',
|
||||
'meta',
|
||||
'singer',
|
||||
'bands',
|
||||
'characters',
|
||||
'events',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('getSongChart')?.catalogKeys, [
|
||||
'songs',
|
||||
'meta',
|
||||
'singer',
|
||||
'bands',
|
||||
'characters',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('randomSong')?.catalogKeys, [
|
||||
'songs',
|
||||
'meta',
|
||||
'singer',
|
||||
'bands',
|
||||
'characters',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('getSongMeta')?.catalogKeys, [
|
||||
'songs',
|
||||
'meta',
|
||||
'singer',
|
||||
'bands',
|
||||
'characters',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('searchCard')?.catalogKeys, [
|
||||
'cards',
|
||||
'skills',
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
'events',
|
||||
'gacha',
|
||||
'costumes',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('getCardIllustration')?.catalogKeys, [
|
||||
'cards',
|
||||
'skills',
|
||||
'characters',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('searchCharacter')?.catalogKeys, [
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('searchEvent')?.catalogKeys, [
|
||||
'events',
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
'cards',
|
||||
'skills',
|
||||
'gacha',
|
||||
'songs',
|
||||
'meta',
|
||||
'degrees',
|
||||
'deco',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('getEventStage')?.catalogKeys, [
|
||||
'events',
|
||||
'characters',
|
||||
'songs',
|
||||
'meta',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('searchPlayer')?.catalogKeys, [
|
||||
'cards',
|
||||
'skills',
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
'degrees',
|
||||
'areaItems',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('searchGacha')?.catalogKeys, [
|
||||
'gacha',
|
||||
'cards',
|
||||
'skills',
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
'events',
|
||||
'items',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('simulateGacha')?.catalogKeys, [
|
||||
'gacha',
|
||||
'cards',
|
||||
'skills',
|
||||
'characters',
|
||||
'singer',
|
||||
'bands',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('getCutoffDetail')?.catalogKeys, [
|
||||
'events',
|
||||
'characters',
|
||||
'rates',
|
||||
'cards',
|
||||
'skills',
|
||||
'singer',
|
||||
'bands',
|
||||
'degrees',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('getCutoffAll')?.catalogKeys, [
|
||||
'events',
|
||||
'characters',
|
||||
'rates',
|
||||
]);
|
||||
expectCatalogKeys(operations.get('getCutoffRecent')?.catalogKeys, [
|
||||
'events',
|
||||
'characters',
|
||||
'rates',
|
||||
]);
|
||||
|
||||
expect(operations.size).toBe(15);
|
||||
});
|
||||
});
|
||||
|
||||
function expectCatalogKeys(actual: readonly string[] | undefined, keys: string[]) {
|
||||
expect(actual).toEqual(keys);
|
||||
}
|
||||
@ -51,6 +51,17 @@ jest.mock('@/modules/qqbot/plugins/bangdream/src/operations', () => ({
|
||||
return [
|
||||
handlerName,
|
||||
{
|
||||
catalogKeys:
|
||||
key === 'bangdream.song.search'
|
||||
? [
|
||||
'songs',
|
||||
'meta',
|
||||
'singer',
|
||||
'bands',
|
||||
'characters',
|
||||
'events',
|
||||
]
|
||||
: undefined,
|
||||
execute,
|
||||
handlerName,
|
||||
},
|
||||
@ -109,7 +120,14 @@ describe('BangDream package entry', () => {
|
||||
});
|
||||
|
||||
expect(mockContext.refreshDictionaryCache).toHaveBeenCalledTimes(1);
|
||||
expect(waitForBangDreamCatalogReady).toHaveBeenCalledTimes(1);
|
||||
expect(waitForBangDreamCatalogReady).toHaveBeenCalledWith([
|
||||
'songs',
|
||||
'meta',
|
||||
'singer',
|
||||
'bands',
|
||||
'characters',
|
||||
'events',
|
||||
]);
|
||||
expect(songSearch).toHaveBeenCalledWith(
|
||||
{ text: '夏祭り' },
|
||||
mockContext,
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
describe('BangDream catalog cache partial loading', () => {
|
||||
it('loads only requested catalog keys and their matching static patches', async () => {
|
||||
const getJson = jest.fn<
|
||||
Promise<Record<string, { path: string }>>,
|
||||
[string, { cacheTime?: number }?]
|
||||
>(async (path) => ({
|
||||
1: { path },
|
||||
}));
|
||||
const readJson = jest.fn(async () => ({}));
|
||||
const readExcelRows = jest.fn(async () => []);
|
||||
|
||||
jest.resetModules();
|
||||
jest.doMock(
|
||||
'@/modules/qqbot/plugins/bangdream/src/infrastructure/integration/bestdori.provider',
|
||||
() => ({
|
||||
bangdreamBestdoriProvider: {
|
||||
getJson,
|
||||
},
|
||||
}),
|
||||
);
|
||||
jest.doMock(
|
||||
'@/modules/qqbot/plugins/bangdream/src/infrastructure/storage/static-patch.provider',
|
||||
() => ({
|
||||
bangdreamStaticPatchProvider: {
|
||||
readExcelRows,
|
||||
readJson,
|
||||
},
|
||||
}),
|
||||
);
|
||||
jest.doMock(
|
||||
'@/modules/qqbot/plugins/bangdream/src/application/bangdream-logger',
|
||||
() => ({
|
||||
logger: jest.fn(),
|
||||
}),
|
||||
);
|
||||
jest.doMock(
|
||||
'@/modules/qqbot/plugins/bangdream/src/infrastructure/integration/runtime-io',
|
||||
() => ({
|
||||
readBangDreamRuntimeConfig: jest.fn(() => undefined),
|
||||
sleepBangDreamRuntime: jest.fn(() => new Promise(() => undefined)),
|
||||
}),
|
||||
);
|
||||
|
||||
const { BANGDREAM_BESTDORI_API_PATHS } = await import(
|
||||
'@/modules/qqbot/plugins/bangdream/src/domain/common/bangdream-protocol'
|
||||
);
|
||||
const { waitForBangDreamCatalogReady } = await import(
|
||||
'@/modules/qqbot/plugins/bangdream/src/application/catalog/bangdream-catalog-cache'
|
||||
);
|
||||
|
||||
await waitForBangDreamCatalogReady(['songs', 'meta']);
|
||||
|
||||
expect(getJson).toHaveBeenCalledTimes(2);
|
||||
expect(getJson.mock.calls.map(([path]) => path).sort()).toEqual(
|
||||
[
|
||||
BANGDREAM_BESTDORI_API_PATHS.meta,
|
||||
BANGDREAM_BESTDORI_API_PATHS.songs,
|
||||
].sort(),
|
||||
);
|
||||
expect(readJson).not.toHaveBeenCalled();
|
||||
expect(readExcelRows).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@ -51,9 +51,13 @@ describe('BangDream catalog cache', () => {
|
||||
});
|
||||
|
||||
it('loads each Bestdori catalog path only once during the initial ready wait', async () => {
|
||||
await waitForBangDreamCatalogReady!();
|
||||
|
||||
const catalogPaths = Object.values(BANGDREAM_BESTDORI_API_PATHS);
|
||||
await waitForBangDreamCatalogReady(
|
||||
Object.keys(BANGDREAM_BESTDORI_API_PATHS) as Array<
|
||||
keyof typeof BANGDREAM_BESTDORI_API_PATHS
|
||||
>,
|
||||
);
|
||||
|
||||
expect(mockGetJson).toHaveBeenCalledTimes(catalogPaths.length);
|
||||
expect(mockGetJson.mock.calls.map(([path]) => path).sort()).toEqual(
|
||||
[...catalogPaths].sort(),
|
||||
@ -66,4 +70,5 @@ describe('BangDream catalog cache', () => {
|
||||
expect(mockReadJson).toHaveBeenCalledTimes(3);
|
||||
expect(mockReadExcelRows).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user