[object Object]

← back to Dw Marketing Reels

publish-ig.mjs: resolve per-account ig_user_id (--account / reel.igAccount) on the shared token; DRY-gate unchanged

b778dff32db1500c4e33b52540115b54700403b7 · 2026-08-25 10:44:42 -0700 · Steve Abrams

Files touched

Diff

commit b778dff32db1500c4e33b52540115b54700403b7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Aug 25 10:44:42 2026 -0700

    publish-ig.mjs: resolve per-account ig_user_id (--account / reel.igAccount) on the shared token; DRY-gate unchanged
---
 scripts/publish-ig.mjs | 36 +++++++++++++++++++++++++++++++++---
 1 file changed, 33 insertions(+), 3 deletions(-)

diff --git a/scripts/publish-ig.mjs b/scripts/publish-ig.mjs
index a9d6d54..aa8fffa 100644
--- a/scripts/publish-ig.mjs
+++ b/scripts/publish-ig.mjs
@@ -26,6 +26,27 @@ const PUBLIC_BASE = process.env.PUBLIC_BASE || 'https://marketing.designerwallco
 const V = 'https://graph.facebook.com/v21.0';
 const argFile = (process.argv.find(a => a.startsWith('--file=')) || '').split('=')[1]
   || (process.argv.includes('--file') ? process.argv[process.argv.indexOf('--file') + 1] : '');
+const argAccount = (process.argv.find(a => a.startsWith('--account=')) || '').split('=')[1]
+  || (process.argv.includes('--account') ? process.argv[process.argv.indexOf('--account') + 1] : '');
+
+// Resolve a selected account (id OR handle) → its ig_user_id, on the ONE shared
+// durable META token. The 35 postable DW accounts all publish on that single token
+// (accounts.json token_source = META_ACCESS_TOKEN, shared, never-expiring); only the
+// target ig_user_id changes per account. Reads the managed layer first
+// (ig-accounts.json carries ig_user_id), then the canonical mirror by handle.
+function resolveAccountUid(sel) {
+  if (!sel) return null;
+  const key = String(sel).toLowerCase();
+  const load = f => { try { return JSON.parse(readFileSync(join(ROOT, 'data', f), 'utf8')); } catch { return null; } };
+  const managed = load('ig-accounts.json') || [];
+  const m = managed.find(a => String(a.id).toLowerCase() === key || String(a.handle || '').toLowerCase() === key);
+  if (m && m.ig_user_id) return { uid: m.ig_user_id, label: m.label || m.handle };
+  const canon = (load('dw-canonical-accounts.json') || {}).accounts || [];
+  const c = canon.find(a => String(a.handle || '').toLowerCase() === key
+    || 'ig-' + String(a.handle || '').toLowerCase().replace(/[^a-z0-9-]+/g, '-') === key);
+  if (c && c.ig_user_id) return { uid: c.ig_user_id, label: c.name || c.handle };
+  return null;
+}
 
 // resolve creds: env → co-located Norma agent .env (same machine)
 function creds() {
@@ -49,13 +70,22 @@ const list = Array.isArray(reels) ? reels : (reels.reels || []);
 const reel = argFile ? list.find(r => r.file === argFile) : list[0];
 if (!reel) die(argFile ? `reel not found: ${argFile}` : 'no reels in manifest');
 
-const { uid, tok } = creds();
-if (!uid || !tok) die('IG_USER_ID / IG_ACCESS_TOKEN not found (env or Norma agent .env)');
+let { uid, tok } = creds();
+if (!tok) die('IG_ACCESS_TOKEN not found (env or Norma agent .env)');
+
+// Pick the target account: explicit --account, else the reel's saved igAccount
+// (set in the console via /api/reel-account), else the env default (main DW).
+const selected = argAccount || reel.igAccount || '';
+const acct = resolveAccountUid(selected);
+let acctLabel = 'env default (main DW)';
+if (acct) { uid = acct.uid; acctLabel = acct.label; }
+else if (selected) die(`account not resolvable to an ig_user_id: ${selected}`);
+if (!uid) die('IG_USER_ID not found (no account selected and no env IG_USER_ID)');
 const mediaUrl = `${PUBLIC_BASE}/reels/${encodeURIComponent(reel.file)}`;
 
 console.log(`reel:    ${reel.file}`);
 console.log(`media:   ${mediaUrl}`);
-console.log(`account: ig_user_id ${uid}  token …${tok.slice(-4)}`);
+console.log(`account: ${acctLabel}  ig_user_id ${uid}  token …${tok.slice(-4)}`);
 
 // verify the public media is fetchable (Meta must be able to pull it)
 { const r = await fetch(mediaUrl, { method: 'HEAD' }); console.log(`media reachable: ${r.status}`); if (r.status !== 200) die('public media not 200 — deploy the reel first'); }

← 5363df3 Wire all 35 postable IG accounts as ready in reels console (  ·  back to Dw Marketing Reels  ·  chore: harden /api/youtube (escapeAttr href, strict video-id 69537a3 →