← back to Majilite Jewelry Cases

scripts/tk11086-ig-delete.mjs

81 lines

#!/usr/bin/env node
// TK-11086 STEP 3 — IRREVERSIBLE Instagram deletion of the CLEAN target set.
// Steve-authorized (clarify gate): remove Novasuede + jewelry-render posts from
// the last 20 days across DW brand IG accounts. Deletes ONLY the vetted clean
// set in data/tk11086-ig-delete-targets.jsonl (91 dedicated single-product
// Novasuede DWCC posts). DELETE graph.facebook.com/v21.0/{ig-media-id}.
//
// SAFETY: DRY-RUN by default; real deletes require --apply. Steve pre-authorized
// this specific clean set. Every deletion logged to:
//   - data/tk11086-ig-deleted-audit.jsonl (per-delete audit record)
//   - ~/.claude/yolo-queue/executed-reversible/ledger.jsonl (undo=IRREVERSIBLE)
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { homedir } from 'node:os';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
const V = 'https://graph.facebook.com/v21.0';
const args = process.argv.slice(2);
const APPLY = args.includes('--apply');

function token() {
  if (process.env.IG_ACCESS_TOKEN) return process.env.IG_ACCESS_TOKEN;
  const p = path.join(homedir(), 'Projects/Norma/agents/instagram-agent/.env');
  const m = fs.readFileSync(p, 'utf8').match(/^IG_ACCESS_TOKEN=(.+)$/m);
  return m ? m[1].trim() : null;
}
const TOK = token();
if (!TOK) { console.error('no IG_ACCESS_TOKEN'); process.exit(1); }

const TARGETS = path.join(ROOT, 'data/tk11086-ig-delete-targets.jsonl');
const AUDIT = path.join(ROOT, 'data/tk11086-ig-deleted-audit.jsonl');
const LEDGER = path.join(homedir(), '.claude/yolo-queue/executed-reversible/ledger.jsonl');
const rows = fs.readFileSync(TARGETS, 'utf8').trim().split('\n').map(l => JSON.parse(l));

const sleep = ms => new Promise(r => setTimeout(r, ms));
console.log(`Targets: ${rows.length}  | mode: ${APPLY ? 'APPLY (LIVE IRREVERSIBLE DELETE)' : 'DRY-RUN (no deletes)'}`);

let deleted = 0, gone = 0, err = 0;
for (const r of rows) {
  if (!APPLY) { console.log(`  + WOULD delete ${r.account} ${r.ig_media_id} (${r.posted_date})`); continue; }
  const url = `${V}/${r.ig_media_id}?access_token=${encodeURIComponent(TOK)}`;
  let res, body;
  try {
    res = await fetch(url, { method: 'DELETE' });
    body = await res.text();
  } catch (e) { console.log(`  ! ${r.account} ${r.ig_media_id}: fetch error ${e.message}`); err++; await sleep(400); continue; }
  let d; try { d = JSON.parse(body); } catch { d = { raw: body }; }
  const success = d && d.success === true;
  const alreadyGone = !success && /does not exist|Unsupported.*request|cannot be loaded/i.test(body);
  const rec = {
    ts: new Date().toISOString(),
    account: r.account,
    ig_user_id: r.ig_user_id,
    ig_media_id: r.ig_media_id,
    posted_date: r.posted_date,
    media_type: r.media_type,
    caption_snippet: r.caption_snippet,
    permalink: r.permalink,
    why_flagged: r.why_flagged,
    delete_result: success ? 'success' : (alreadyGone ? 'already-gone' : 'error'),
    graph_response: d,
  };
  fs.appendFileSync(AUDIT, JSON.stringify(rec) + '\n');
  if (success) {
    deleted++;
    console.log(`  ✓ deleted ${r.account} ${r.ig_media_id}`);
    fs.appendFileSync(LEDGER, JSON.stringify({
      ts: rec.ts, agent: 'vp-dw-marketing', ticket: 'TK-11086',
      action: `IG DELETE ${r.account} media ${r.ig_media_id} (${r.posted_date}) — Novasuede post`,
      blast_radius: 1,
      undo_cmd: 'IRREVERSIBLE — Steve-authorized IG deletion TK-11086',
      verify: `permalink ${r.permalink} returns 404`,
    }) + '\n');
  } else if (alreadyGone) { gone++; console.log(`  = ${r.account} ${r.ig_media_id}: already gone`); }
  else { err++; console.log(`  ! ${r.account} ${r.ig_media_id}: ${JSON.stringify(d).slice(0,140)}`); }
  await sleep(400);
}
console.log(`\n=== RESULT ===  deleted=${deleted}  already-gone=${gone}  errors=${err}  audit=${AUDIT}`);