stremio/apps/master-api/src/viewer/event-fallbacks.ts
Jos Vooges | STH bc847b222d Fix empty Ziggo Sport event covers by keeping FAAB live thumbs.
Stop replacing working FAAB thumbs with Gracenote URLs that 404, and only enrich highlights after verifying EventImages exist.
2026-09-24 02:02:23 +02:00

98 lines
3.2 KiB
TypeScript

/**
* Category backdrop fallbacks when an event has no real cover art.
* Served from /assets/event-fallbacks/{sport}.jpg on master-api.
*/
import { isChannelLogoUrl, isLiveChannelThumb, type EnrichableEvent } from "./ziggo-epg-art";
const FALLBACK_FILE: Record<string, string> = {
football: "football.jpg",
racing: "racing.jpg",
tennis: "tennis.jpg",
golf: "golf.jpg",
darts: "darts.jpg",
snooker: "snooker.jpg",
basketball: "basketball.jpg",
waterpolo: "waterpolo.jpg",
sailing: "sailing.jpg",
hockey: "hockey.jpg",
cycling: "cycling.jpg",
other: "other.jpg",
};
export function fallbackFilenameForSport(sport: string | null | undefined): string {
const s = (sport || "other").toLowerCase();
return FALLBACK_FILE[s] ?? FALLBACK_FILE.other;
}
export function eventFallbackUrl(publicBase: string, sport: string | null | undefined): string {
const base = publicBase.replace(/\/+$/, "");
return `${base}/assets/event-fallbacks/${fallbackFilenameForSport(sport)}`;
}
export function isCategoryFallbackUrl(url: string | null | undefined): boolean {
if (!url) return false;
return /\/assets\/event-fallbacks\/[a-z]+\.jpg(\?|$)/i.test(url);
}
/** True when landscape is missing or only a channel logo (not usable cover). */
export function needsCategoryFallback(e: {
name?: string | null;
imageLandscape?: string | null;
images?: string[];
}): boolean {
const land = e.imageLandscape;
if (!land) return true;
// FAAB live channel thumbs are real card art — keep them.
// Highlights/replays with a live snap are wrong sport; treat as weak.
if (isLiveChannelThumb(land)) {
return /\b(samenvatting|hoogtepunten|highlights?|herhaling|replay)\b/i.test(
e.name ?? ""
);
}
if (isChannelLogoUrl(land)) return true;
if (isCategoryFallbackUrl(land)) return false;
// Already has real art
return false;
}
/**
* Fill empty/weak covers with sport-category backdrops and preserve channel logos in logoUrl.
*/
export function applyCategoryFallbacks<T extends EnrichableEvent & { provider?: string; sportBucket?: string }>(
events: T[],
publicBase: string
): T[] {
if (!publicBase) return events;
return events.map((e) => {
if (!needsCategoryFallback(e)) {
// Still ensure logoUrl when we only have channel logo in images
if (!e.logoUrl) {
const logo = e.images.find((u) => isChannelLogoUrl(u));
if (logo) return { ...e, logoUrl: logo };
}
return e;
}
const fallback = eventFallbackUrl(publicBase, e.sportBucket);
const logoFromImages = e.images.find((u) => isChannelLogoUrl(u) && !isLiveChannelThumb(u));
const logoFromLandscape =
e.imageLandscape && isChannelLogoUrl(e.imageLandscape) ? e.imageLandscape : null;
const logoUrl = e.logoUrl || logoFromLandscape || logoFromImages || null;
const cleaned = e.images.filter(
(u) => !isLiveChannelThumb(u) && !isCategoryFallbackUrl(u)
);
const images = [fallback, ...cleaned.filter((u) => u !== fallback)];
return {
...e,
imageLandscape: fallback,
imageBackdrop: e.imageBackdrop && !isLiveChannelThumb(e.imageBackdrop) && !isChannelLogoUrl(e.imageBackdrop)
? e.imageBackdrop
: fallback,
logoUrl,
images,
};
});
}