/* Scrape Open5e (SRD 5.1, CC-BY-4.0 / OGL) → normalized JSON in src/data/srd/. * Run: bunx tsx scripts/fetch_open5e.ts */ import { writeFileSync } from 'node:fs'; import { normalizeOpen5eClass, type Open5eClass } from '../src/lib/ruleset/normalize'; const BASE = 'https://api.open5e.com/v1'; async function all>(path: string): Promise { let url: string | null = `${BASE}/${path}/?limit=500`; const out: T[] = []; while (url) { const r = await fetch(url); if (!r.ok) throw new Error(`${path}: ${r.status}`); const j = (await r.json()) as { results: T[]; next: string | null }; out.push(...j.results); url = j.next; } return out; } const first = (s: string | undefined): string => { const p = (s ?? '').replace(/[#*_`>]/g, '').split('\n').map((x) => x.trim()).filter(Boolean)[0] ?? ''; return p.length > 500 ? `${p.slice(0, 497)}…` : p; }; async function main() { const classes = (await all('classes')).map(normalizeOpen5eClass).sort((a, b) => a.name.localeCompare(b.name)); writeFileSync('src/data/srd/classes.json', JSON.stringify(classes)); const races = (await all>('races')).map((r) => ({ slug: r.slug, name: r.name, desc: first(r.desc), asi: r.asi_desc ?? '', speed: r.speed_desc ?? '', vision: r.vision ?? '', traits: first(r.traits), })).sort((a, b) => a.name.localeCompare(b.name)); writeFileSync('src/data/srd/races.json', JSON.stringify(races)); const backgrounds = (await all>('backgrounds')).map((b) => ({ slug: b.slug, name: b.name, desc: first(b.desc), skills: b.skill_proficiencies ?? '', tools: b.tool_proficiencies ?? '', languages: b.languages ?? '', feature: b.feature ?? '', })).sort((a, b) => a.name.localeCompare(b.name)); writeFileSync('src/data/srd/backgrounds.json', JSON.stringify(backgrounds)); const feats = (await all>('feats')).map((f) => ({ slug: f.slug, name: f.name, desc: first(f.desc), prerequisite: f.prerequisite ?? '', })).sort((a, b) => a.name.localeCompare(b.name)); writeFileSync('src/data/srd/feats.json', JSON.stringify(feats)); console.log(`5e: classes ${classes.length}, races ${races.length}, backgrounds ${backgrounds.length}, feats ${feats.length}`); } main().catch((e) => { console.error(e); process.exit(1); });