Files
SixFlagsSuperCalendar/scripts/scrape.ts
T
josh 4652a92c29 refactor: hardcode API IDs and coaster lists, remove Playwright discovery
Embed Six Flags API IDs directly in the park registry and snapshot
coaster lists from park-meta.json into a TypeScript module. This
eliminates the Playwright-based discovery script, RCDB scraper, and
runtime dependency on park-meta.json — preparing for the backend
API transition.

- Add apiId field to Park type and all 24 park entries
- Create lib/coaster-data.ts with hardcoded coaster lists
- Update page components to use park.apiId and new getCoasterSet()
- Remove scripts/discover.ts, lib/scrapers/rcdb.ts, lib/park-meta.ts
- Remove data/park-meta.json from shared volume
- Remove playwright devDependency and discover npm script
- Simplify scripts/scrape.ts (no RCDB, no discovery checks)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:25:53 -04:00

108 lines
3.0 KiB
TypeScript

/**
* 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<void>((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);
});