// `archilyzer wayback refresh [--titles ] [--dry-run]` — every // Wayback Machine copy of a channel brought up to the Wayback rules // (controller/waybackRefresh.ts): its `wayback.json`, its dir under its // canonical id (through the snapshot's own reconcile pass, roster moved with // it), and with `--titles` a file mapping id → {title, upload_date} for a raw // file that has neither. Offline. Prints old → new per record; a second run // changes nothing. import { readFile } from "node:fs/promises"; import { isValidChannelSlug } from "../controller/channels"; import { refreshWaybackRecords, type WaybackTitle } from "../controller/waybackRefresh"; import { getPaths, type Paths } from "../lib/paths"; async function readTitles(file: string): Promise> { const v = JSON.parse(await readFile(file, "utf8")) as unknown; if (!v || typeof v !== "object" || Array.isArray(v)) { throw new Error(`${file}: expected an object of id → {title, upload_date}`); } const out: Record = {}; for (const [id, raw] of Object.entries(v as Record)) { if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`${file}: "${id}" is not an object`); const r = raw as Record; out[id] = { ...(typeof r.title === "string" ? { title: r.title } : {}), ...(typeof r.upload_date === "string" ? { upload_date: r.upload_date } : {}), }; } return out; } export async function main(opts: { slug: string; dryRun: boolean; titlesFile?: string; paths?: Paths; }): Promise { if (!isValidChannelSlug(opts.slug)) { console.error(`wayback refresh: "${opts.slug}" is not a channel slug`); return 2; } try { const titles = opts.titlesFile ? await readTitles(opts.titlesFile) : undefined; const r = await refreshWaybackRecords({ slug: opts.slug, paths: opts.paths ?? getPaths(), dryRun: opts.dryRun, titles, onLog: (line) => console.log(line.replace(/\n$/, "")), }); const would = opts.dryRun ? "would be " : ""; const renamed = r.records.filter((x) => x.rename === "renamed" || x.rename === "merged").length; const held = r.records.filter((x) => x.rename === "held" || x.rename === "conflict").length; const sidecars = r.records.filter((x) => x.sidecar).length; const patched = r.records.filter((x) => Object.keys(x.changes).length > 0).length; for (const id of r.unmatchedTitles) console.log(`--titles: no Wayback record "${id}"`); for (const f of r.failed) console.error(`${f.id}: failed — ${f.error}`); console.log( `${opts.dryRun ? "dry run: " : ""}${r.records.length} Wayback records, ` + `${renamed} ${would}renamed, ${sidecars} wayback.json ${would}written, ` + `${patched} ${would}retitled` + (held > 0 ? `, ${held} not renamed` : "") + (r.failed.length > 0 ? `, ${r.failed.length} failed` : "") + ".", ); return r.failed.length > 0 ? 1 : 0; } catch (err) { console.error(`wayback refresh: ${(err as Error).message}`); return 1; } }