← back to Majilite Jewelry Cases

scripts/tk11086-ig-delete-heldset2.mjs

105 lines

#!/usr/bin/env node
// TK-11086 HELD SET #2 — IRREVERSIBLE Instagram deletion of the 7 Rodeo Drive
// jewelry-store reels on @beverlyhillsvideos that matched ONLY on the word
// "jewelry" (why_flagged == ["caption:jewelry-terms"]).
// Steve answered the final clarify gate: NO to keeping them -> DELETE.
//
// Deletes ONLY the exact 7 media IDs in data/tk11086-ig-heldset2-targets.jsonl.
// DELETE graph.facebook.com/v21.0/{ig-media-id}; verifies each with a follow-up
// GET (expects "Unsupported get request"). Every deletion logged to:
//   - data/tk11086-ig-deleted-audit.jsonl (per-delete audit record)
//   - ~/.claude/yolo-queue/executed-reversible/ledger.jsonl
//       (undo = "IRREVERSIBLE — Steve-authorized IG deletion TK-11086 held-set-2")
// SAFETY: DRY-RUN by default; real deletes require --apply. Hard-caps at 7 rows.
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-heldset2-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));

// RAIL: exact held set only — refuse to widen.
if (rows.length !== 7) { console.error(`REFUSING: expected 7 targets, got ${rows.length}`); process.exit(1); }
for (const r of rows) {
  if (r.account !== 'beverlyhillsvideos' || JSON.stringify(r.why_flagged) !== JSON.stringify(['caption:jewelry-terms'])) {
    console.error(`REFUSING: target ${r.ig_media_id} is not a held-set-2 jewelry reel`); process.exit(1);
  }
}

const sleep = ms => new Promise(r => setTimeout(r, ms));
console.log(`Held-set-2 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.caption_snippet.slice(0,40)}`); 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);

  // Follow-up GET verify (expects "Unsupported get request" once gone)
  let verifyResult = 'skipped';
  if (success || alreadyGone) {
    await sleep(300);
    try {
      const gr = await fetch(`${V}/${r.ig_media_id}?fields=id&access_token=${encodeURIComponent(TOK)}`);
      const gt = await gr.text();
      verifyResult = /Unsupported get request|does not exist|cannot be loaded/i.test(gt) ? 'confirmed-gone' : `still-present:${gt.slice(0,80)}`;
    } catch (e) { verifyResult = `verify-fetch-error:${e.message}`; }
  }

  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,
    held_set: 2,
    delete_result: success ? 'success' : (alreadyGone ? 'already-gone' : 'error'),
    get_verify: verifyResult,
    graph_response: d,
  };
  fs.appendFileSync(AUDIT, JSON.stringify(rec) + '\n');
  if (success) {
    deleted++;
    console.log(`  ✓ deleted ${r.account} ${r.ig_media_id} | GET-verify: ${verifyResult}`);
    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}) — Rodeo Drive jewelry reel (held-set-2)`,
      blast_radius: 1,
      undo_cmd: 'IRREVERSIBLE — Steve-authorized IG deletion TK-11086 held-set-2',
      verify: `GET ${V}/${r.ig_media_id} returns "Unsupported get request" (${verifyResult})`,
    }) + '\n');
  } else if (alreadyGone) { gone++; console.log(`  = ${r.account} ${r.ig_media_id}: already gone | GET-verify: ${verifyResult}`); }
  else { err++; console.log(`  ! ${r.account} ${r.ig_media_id}: ${JSON.stringify(d).slice(0,140)}`); }
  await sleep(400);
}
console.log(`\n=== HELD-SET-2 RESULT ===  deleted=${deleted}  already-gone=${gone}  errors=${err}  audit=${AUDIT}`);