import type { ScheduleEventPublic } from "../viewer/schedule-events"; import { resolveF1tvImageUrl } from "./client"; import { loadF1tvConfigOnly } from "./settings"; import type { F1tvMeetingSnapshot, F1tvSessionSnapshot } from "./tweaks"; function phaseFor( startMs: number, endMs: number, state: string | null, nowMs: number ): "live" | "upcoming" | "ended" { const st = (state || "").toLowerCase(); if (st === "live" || (st.includes("live") && !st.includes("pre"))) { if (nowMs <= endMs + 15 * 60_000) return "live"; } if (nowMs < startMs) return "upcoming"; if (nowMs > endMs) return "ended"; return "live"; } function toEventId(contentId: number): string { return `f1tv:${contentId}`; } export function sessionToPublic( session: F1tvSessionSnapshot, nowMs: number ): ScheduleEventPublic { const start = new Date(session.startMs).toISOString(); const stop = new Date(session.endMs).toISOString(); const phase = phaseFor(session.startMs, session.endMs, session.state, nowMs); const playable = session.kind === "live_event" && phase === "live"; const image = resolveF1tvImageUrl(session.imageUrl); return { id: toEventId(session.contentId), name: session.title, description: session.meetingName, provider: "f1tv", category: "racing", eventType: session.kind === "replay" ? "replay" : "live", subtitle: session.series, channel: "F1 TV", network: "F1 TV", isLive: phase === "live", phase, playable, status: session.state, start, stop, startLocal: null, stopLocal: null, sportBucket: "racing", sportLabel: "Racing", imageLandscape: image, imageBackdrop: image, imagePortrait: null, logoUrl: null, images: image ? [image] : [], audioLanguages: [], }; } /** Upcoming + live F1 sessions for the main Events tab (no replays). */ export async function listF1tvScheduleEvents( nowMs = Date.now() ): Promise { const config = await loadF1tvConfigOnly(); if (!config?.sessions?.length) return []; const out: ScheduleEventPublic[] = []; for (const session of config.sessions) { if (session.kind !== "live_event") continue; const pub = sessionToPublic(session, nowMs); if (pub.phase === "ended") continue; out.push(pub); } return out; } export async function getF1tvScheduleEvent( eventId: string, nowMs = Date.now() ): Promise { const m = /^f1tv:(\d+)$/i.exec(eventId.trim()); if (!m) return null; const contentId = Number(m[1]); const config = await loadF1tvConfigOnly(); const session = config?.sessions.find((s) => s.contentId === contentId); if (!session) return null; return sessionToPublic(session, nowMs); } export type F1tvHomePayload = { generatedAt: string; live: ScheduleEventPublic[]; upcoming: ScheduleEventPublic[]; recentReplays: ScheduleEventPublic[]; meetings: F1tvMeetingSnapshot[]; nextMeeting: F1tvMeetingSnapshot | null; }; export async function getF1tvHome(nowMs = Date.now()): Promise { const config = await loadF1tvConfigOnly(); const sessions = config?.sessions ?? []; const meetings = config?.meetings ?? []; const live: ScheduleEventPublic[] = []; const upcoming: ScheduleEventPublic[] = []; const recentReplays: ScheduleEventPublic[] = []; for (const session of sessions) { const pub = sessionToPublic(session, nowMs); if (session.kind === "replay") { if (pub.phase === "ended" || session.startMs < nowMs) { recentReplays.push(pub); } continue; } if (pub.phase === "live") live.push(pub); else if (pub.phase === "upcoming") upcoming.push(pub); } recentReplays.sort((a, b) => Date.parse(b.start) - Date.parse(a.start)); upcoming.sort((a, b) => Date.parse(a.start) - Date.parse(b.start)); let nextMeeting: F1tvMeetingSnapshot | null = null; const upcomingMeetingKeys = new Set(upcoming.map((e) => { const s = sessions.find((x) => toEventId(x.contentId) === e.id); return s?.meetingKey; })); for (const meeting of meetings) { if (upcomingMeetingKeys.has(meeting.meetingKey)) { nextMeeting = meeting; break; } } if (!nextMeeting && meetings.length) { nextMeeting = meetings[meetings.length - 1] ?? null; } return { generatedAt: new Date(nowMs).toISOString(), live, upcoming: upcoming.slice(0, 40), recentReplays: recentReplays.slice(0, 24), meetings, nextMeeting, }; } export async function getF1tvMeetingDetail( meetingKey: string, nowMs = Date.now() ): Promise<{ meeting: F1tvMeetingSnapshot | null; sessions: ScheduleEventPublic[]; } | null> { const config = await loadF1tvConfigOnly(); if (!config) return null; const key = meetingKey.trim(); const meeting = config.meetings.find((m) => m.meetingKey === key) ?? null; const sessions = config.sessions .filter((s) => s.meetingKey === key) .map((s) => sessionToPublic(s, nowMs)) .sort((a, b) => Date.parse(a.start) - Date.parse(b.start)); if (!meeting && !sessions.length) return null; return { meeting, sessions }; }