/** * Scrape job — fetches 2026 operating hours for all parks from the Six Flags API. * * npm run scrape — skips months scraped within the last 72h * npm run scrape:force — re-scrapes everything */ import { openDb, upsertDay, isMonthScraped } from "../lib/db"; import { PARKS } from "../lib/parks"; import { scrapeMonth, fetchToday, RateLimitError } from "../lib/scrapers/sixflags"; const YEAR = 2026; const MONTHS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; const DELAY_MS = 1000; const FORCE = process.argv.includes("--rescrape"); async function sleep(ms: number) { return new Promise((r) => setTimeout(r, ms)); } async function main() { const db = openDb(); console.log(`Scraping ${YEAR} — ${PARKS.length} parks\n`); let totalFetched = 0; let totalSkipped = 0; let totalErrors = 0; for (const park of PARKS) { const label = park.shortName.padEnd(22); let openDays = 0; let fetched = 0; let skipped = 0; let errors = 0; process.stdout.write(` ${label} `); for (const month of MONTHS) { if (!FORCE && isMonthScraped(db, park.id, YEAR, month)) { process.stdout.write("·"); skipped++; continue; } try { const days = await scrapeMonth(park.apiId, YEAR, month); db.transaction(() => { for (const d of days) upsertDay(db, park.id, d.date, d.isOpen, d.hoursLabel, d.specialType); })(); openDays += days.filter((d) => d.isOpen).length; fetched++; process.stdout.write("█"); if (fetched + skipped + errors < MONTHS.length) await sleep(DELAY_MS); } catch (err) { if (err instanceof RateLimitError) { process.stdout.write("✗"); } else { process.stdout.write("✗"); console.error(`\n error: ${err instanceof Error ? err.message : err}`); } errors++; } } totalFetched += fetched; totalSkipped += skipped; totalErrors += errors; if (errors > 0) { console.log(` ${errors} error(s)`); } else if (skipped === MONTHS.length) { console.log(" up to date"); } else { console.log(` ${openDays} open days`); } } console.log(`\n ${totalFetched} fetched ${totalSkipped} skipped ${totalErrors} errors`); if (totalErrors > 0) console.log(" Re-run to retry failed months."); // ── Today scrape (always fresh — dateless endpoint returns current day) ──── console.log("\n── Today's data ──"); for (const park of PARKS) { process.stdout.write(` ${park.shortName.padEnd(22)} `); try { const today = await fetchToday(park.apiId); if (today) { upsertDay(db, park.id, today.date, today.isOpen, today.hoursLabel, today.specialType); console.log(today.isOpen ? `open ${today.hoursLabel ?? ""}` : "closed"); } else { console.log("no data"); } } catch { console.log("error"); } await sleep(500); } db.close(); } main().catch((err) => { console.error("Fatal:", err); process.exit(1); });