← 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}`);