import type { SourceModule } from "../registry"; import { BriefsConfig, BriefsPayload, type BriefsConfig as Config, type BriefsPayload as Payload, } from "@/lib/schemas/sources/briefs"; // Shape of a single category file on screamer.humdrum.one. interface RawBrief { date?: string; category?: string; stories?: Array<{ title?: string; summary?: string; articles?: Array<{ publication?: string; link?: string }>; }>; } // Newest-first list of dates that have at least one published brief. async function fetchDates(baseUrl: string, signal?: AbortSignal): Promise { try { const res = await fetch(`${baseUrl}/data/index.json`, { signal }); if (!res.ok) return []; const j = (await res.json()) as { dates?: string[] }; return Array.isArray(j.dates) ? j.dates : []; } catch { return []; } } // Fetch the newest available brief for one category, walking back through dates // until one resolves (briefs publish at staggered times overnight, so today's // may not exist yet at 6 AM). Returns null if none found in the window. async function fetchCategory( baseUrl: string, key: string, dates: string[], signal?: AbortSignal, ): Promise<{ date: string; raw: RawBrief } | null> { for (const date of dates) { try { const res = await fetch(`${baseUrl}/data/${date}-${key}.json`, { signal }); if (!res.ok) continue; const raw = (await res.json()) as RawBrief; if (raw.stories && raw.stories.length > 0) return { date, raw }; } catch { // try the next date } } return null; } export const briefsModule: SourceModule = { kind: "briefs", label: "News Briefs", keyless: true, defaultRefreshSeconds: 3600, configSchema: BriefsConfig, payloadSchema: BriefsPayload, isConfigured: (config) => config.categories.length > 0, async fetch({ config, signal }) { const baseUrl = config.baseUrl.replace(/\/$/, ""); const allDates = await fetchDates(baseUrl, signal); // index.json is newest-first; cap how far back we'll look per category. const dates = (allDates.length ? allDates : []).slice(0, config.lookbackDays); const categories = await Promise.all( config.categories.map(async ({ key, name }) => { const hit = await fetchCategory(baseUrl, key, dates, signal); const stories = (hit?.raw.stories ?? []).map((s) => ({ title: s.title ?? "", summary: s.summary ?? "", sources: [ ...new Set((s.articles ?? []).map((a) => a.publication).filter((p): p is string => !!p)), ], link: (s.articles ?? []).find((a) => a.link)?.link, })); return { key, name, date: hit?.date ?? null, stories }; }), ); return { categories }; }, };