/** * 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 = { 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( 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, }; }); }