Files
SubMiner/stats/src/lib/cover-images.ts
T
sudacode e18ccfe288 feat(stats): add Hide Kana filter and fix vocabulary exclusion matching
- Remove overlay mining image toast; OSD card notifications no longer flash a frame screenshot
- Add Hide Kana toggle to frequency rank table to filter kana-only headwords
- Fix vocab exclusions to deduplicate and match token variants (e.g. ない / 無い) under one exclusion entry
- Skip cover image fetching when the overview tab is inactive
2026-06-06 01:50:15 -07:00

81 lines
2.0 KiB
TypeScript

import type { SessionSummary, StatsCoverImagesData } from '../types/stats';
export type CoverImageKind = 'anime' | 'media';
export type CoverImageMap = Record<string, string>;
export interface CoverImageRequest {
animeIds: number[];
videoIds: number[];
}
function normalizePositiveIds(ids: Iterable<number | null | undefined>): number[] {
const uniqueIds = new Set<number>();
for (const id of ids) {
if (typeof id === 'number' && Number.isFinite(id) && id > 0) {
uniqueIds.add(Math.floor(id));
}
}
return Array.from(uniqueIds).sort((a, b) => a - b);
}
export function getCoverImageKey(kind: CoverImageKind, id: number): string {
return `${kind}:${id}`;
}
export function buildCoverImageRequestKey(
animeIds: number[],
videoIds: number[],
refreshToken = 0,
): string {
return `a:${animeIds.join(',')}|m:${videoIds.join(',')}|r:${refreshToken}`;
}
export function collectSessionCoverRequests(
sessions: Pick<SessionSummary, 'animeId' | 'videoId'>[],
): CoverImageRequest {
const animeIds: number[] = [];
const videoIds: number[] = [];
for (const session of sessions) {
if (session.animeId != null) {
animeIds.push(session.animeId);
} else if (session.videoId != null) {
videoIds.push(session.videoId);
}
}
return {
animeIds: normalizePositiveIds(animeIds),
videoIds: normalizePositiveIds(videoIds),
};
}
export function mergeCoverImageData(
previous: CoverImageMap,
data: StatsCoverImagesData,
): CoverImageMap {
const next = { ...previous };
for (const [id, image] of Object.entries(data.anime)) {
if (image?.dataUrl) {
next[getCoverImageKey('anime', Number(id))] = image.dataUrl;
}
}
for (const [id, image] of Object.entries(data.media)) {
if (image?.dataUrl) {
next[getCoverImageKey('media', Number(id))] = image.dataUrl;
}
}
return next;
}
export function getCoverImageSrc(
images: CoverImageMap,
kind: CoverImageKind,
id: number | null,
): string | null {
return id == null ? null : (images[getCoverImageKey(kind, id)] ?? null);
}