← back to Norma

agents/instagram-agent/redo-last5.js

89 lines

#!/usr/bin/env node
/**
 * redo-last5.js — re-publish the last-5-days fleet posts with the CORRECTED caption
 * (Name + Color + Model/SKU, no http link). TK-10574.
 *
 * Reads the FROZEN delete-list (data/redo-originals-<date>.jsonl) — the 85 originals
 * captured before any repost — and, for each, reposts the SAME product via post-to.js,
 * which now composes the fixed caption from content.js.
 *
 *   node redo-last5.js                 # DRY: preview every corrected caption, publish nothing
 *   node redo-last5.js --go            # LIVE: repost each (customer-facing — run this yourself)
 *   node redo-last5.js --go --only designerwallcoverings   # just one account
 *
 * On --go it writes data/redo-results-<date>.jsonl mapping each ORIGINAL
 * (handle, old media_id, old permalink) -> the NEW permalink, so the openclaw
 * deletion step deletes exactly the right originals.
 */
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const content = require('./content');

const DATE = '20260814';
const SRC = path.join(__dirname, 'data', `redo-originals-${DATE}.jsonl`);
const OUT = path.join(__dirname, 'data', `redo-results-${DATE}.jsonl`);
const PACE_MS = 4000; // gentle spacing so a fleet-wide sweep never hammers the Graph API

const args = process.argv.slice(2);
const GO = args.includes('--go');
const only = (() => { const i = args.indexOf('--only'); return i >= 0 ? args[i + 1] : null; })();

// Already-reposted keys (idempotent + crash-resumable) — skip anything already in redo-results.
const done = new Set();
if (fs.existsSync(OUT)) {
  for (const l of fs.readFileSync(OUT, 'utf8').trim().split('\n').filter(Boolean)) {
    try { const r = JSON.parse(l); done.add(`${r.handle}|${r.product_handle}`); } catch { /* skip */ }
  }
}

const rows = fs.readFileSync(SRC, 'utf8').trim().split('\n')
  .map((l) => JSON.parse(l))
  .filter((r) => (only ? r.handle === only : true))
  .filter((r) => !done.has(`${r.handle}|${r.product_handle}`)); // never double-post

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

(async () => {
  console.log(`${GO ? 'LIVE REPOST' : 'DRY PREVIEW'} — ${rows.length} post(s)${only ? ` (@${only})` : ' across the fleet'}\n`);
  let ok = 0, fail = 0, skip = 0;
  for (let i = 0; i < rows.length; i++) {
    const r = rows[i];
    const tag = `[${i + 1}/${rows.length}] @${r.handle} ${r.product_handle}`;
    // Preview the corrected caption (also surfaces products that now fail to resolve)
    let preview;
    try { preview = await content.resolveProduct(r.product_handle); }
    catch (e) { console.log(`  ⚠ SKIP ${tag} — ${e.message}`); skip++; continue; }

    if (!GO) {
      console.log(`── ${tag}`);
      console.log(preview.caption.split('\n').slice(0, 3).join('\n')); // name/color/sku lines
      console.log('');
      await sleep(700); // pace storefront reads so a full sweep doesn't hit the rate limiter
      continue;
    }

    // LIVE: hand off to the sanctioned publisher (its own --confirm gate + ledger append)
    try {
      const out = execFileSync(process.execPath,
        ['post-to.js', r.handle, '--product', r.product_handle, '--confirm'],
        { cwd: __dirname, encoding: 'utf8' });
      const m = out.match(/posted IMAGE (\S+)/);
      const newPermalink = m ? m[1] : null;
      fs.appendFileSync(OUT, JSON.stringify({
        handle: r.handle, product_handle: r.product_handle,
        old_media_id: r.media_id, old_permalink: r.permalink,
        new_permalink: newPermalink, ts: new Date().toISOString(),
      }) + '\n');
      console.log(`  ✓ ${tag} — new ${newPermalink || '(permalink pending)'}`);
      ok++;
    } catch (e) {
      console.log(`  ✗ ${tag} — ${String(e.stderr || e.message).trim().split('\n').pop()}`);
      fail++;
    }
    if (i < rows.length - 1) await sleep(PACE_MS);
  }
  console.log(`\n${GO ? 'Reposted' : 'Previewed'}: ${ok || rows.length - skip} ok, ${fail} failed, ${skip} skipped.`);
  if (GO) console.log(`Mapping written -> ${OUT}\nNext: delete the ${ok} originals (openclaw), keyed by old_permalink.`);
})();