← Files aictrl.devARCHIVED FILE

scripts/scrape-text.cjs

2.42 KB · Sep 30, 2026 · 22:54 UTC

↓ Download file

/* Dump rendered page text from (optionally authenticated) pages — use it to
 * sync live numbers into the narration BEFORE generating audio, and to find
 * the on-screen strings the recorder blocks will anchor on.
 *
 * Usage:
 *   node scrape-text.cjs <url> [<url>...] [--profile <dir> | --state <auth-state.json>] [--chars 3500]
 *
 * Gotcha: 'networkidle' never fires on pages holding an SSE/websocket stream —
 * this script uses domcontentloaded + a fixed settle wait instead.
 */
const path = require('path');
const os = require('os');
const fs = require('fs');

function resolvePlaywright() {
  const candidates = [
    path.join(process.cwd(), 'node_modules', 'playwright'),
    'playwright',
  ];
  for (const c of candidates) {
    try { return require(c); } catch { /* next */ }
  }
  throw new Error('playwright not found — run `npm i -D playwright && npx playwright install chromium` in the host repo');
}
const { chromium } = resolvePlaywright();

function arg(name, def) {
  const i = process.argv.indexOf('--' + name);
  return i >= 0 && i + 1 < process.argv.length ? process.argv[i + 1] : def;
}
const PROFILE = arg('profile', null);
const STATE = arg('state', null);
const CHARS = Number(arg('chars', '3500'));
const flagsWithValue = new Set(['--profile', '--state', '--chars']);
const urls = process.argv.slice(2).filter((a, i, all) => !a.startsWith('--') && !flagsWithValue.has(all[i - 1]));

if (!urls.length) {
  console.error('Usage: node scrape-text.cjs <url> [<url>...] [--profile dir | --state file]');
  process.exit(64);
}

(async () => {
  let browser = null, ctx;
  const opts = { viewport: { width: 1536, height: 2000 } };
  if (PROFILE) {
    ctx = await chromium.launchPersistentContext(PROFILE, { headless: true, ...opts });
  } else {
    browser = await chromium.launch({ headless: true });
    ctx = await browser.newContext(STATE && fs.existsSync(STATE) ? { storageState: STATE, ...opts } : opts);
  }
  const page = ctx.pages?.()[0] || (await ctx.newPage());
  for (const url of urls) {
    await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });
    await page.waitForTimeout(2500);
    const text = await page.evaluate(() => document.body.innerText);
    console.log(`\n===== ${url} =====`);
    console.log(text.replace(/\n{2,}/g, '\n').slice(0, CHARS));
  }
  await ctx.close();
  if (browser) await browser.close();
})().catch((e) => {
  console.error('[scrape] ERROR:', e.message);
  process.exit(1);
});

SHA-256: 33d53430f68d8c790cf1f40ea81f05adc44d70665cc56b8b34dd7349002488f3