← back to Norma
IG fabric-friday reshare: daily auto-mode — dedup, exclude own posts, hard cap 6/day, business-hours gate; + engagement monitor
0ba22ac804dd14c5121b99632c38f02d9bc5a74e · 2026-08-14 13:58:47 -0700 · Steve
Files touched
M agents/instagram-agent/fabric-friday-reshare.jsA agents/instagram-agent/monitor-reshares.js
Diff
commit 0ba22ac804dd14c5121b99632c38f02d9bc5a74e
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Aug 14 13:58:47 2026 -0700
IG fabric-friday reshare: daily auto-mode — dedup, exclude own posts, hard cap 6/day, business-hours gate; + engagement monitor
---
agents/instagram-agent/fabric-friday-reshare.js | 23 +++++++++++++++-
agents/instagram-agent/monitor-reshares.js | 35 +++++++++++++++++++++++++
2 files changed, 57 insertions(+), 1 deletion(-)
diff --git a/agents/instagram-agent/fabric-friday-reshare.js b/agents/instagram-agent/fabric-friday-reshare.js
index dd1b931..b116520 100644
--- a/agents/instagram-agent/fabric-friday-reshare.js
+++ b/agents/instagram-agent/fabric-friday-reshare.js
@@ -69,11 +69,31 @@ async function publish(imageUrl, caption) {
}
(async () => {
+ // Dedup + daily cap from the reshares log (so a scheduled job never double-posts or exceeds 6/day).
+ const LOG = path.join(__dirname, 'data', 'fabric-friday-reshares.jsonl');
+ const logRows = fs.existsSync(LOG) ? fs.readFileSync(LOG, 'utf8').trim().split('\n').filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean) : [];
+ const doneSrc = new Set(logRows.map((r) => r.permalink));
+ const today = new Date().toISOString().slice(0, 10);
+ const todayCount = logRows.filter((r) => (r.ts || '').slice(0, 10) === today).length;
+ const DAILY_CAP = 6;
+ const FORCE = args.includes('--force');
+ const hour = new Date().getHours();
+ if (GO && !FORCE && (hour < 8 || hour >= 20)) { console.log(`Outside posting hours (${hour}:00; window 08:00–20:00). Skipping.`); return; }
+ const allowed = GO ? Math.max(0, Math.min(N, DAILY_CAP - todayCount)) : N;
+ if (GO && allowed <= 0) { console.log(`Daily cap reached — ${todayCount}/${DAILY_CAP} reshared today. Nothing to do.`); return; }
+
+ // Exclude OUR OWN posts — our reshares carry #fabricfriday so they appear in the hashtag feed;
+ // without this the tool would reshare its own reshares in a loop.
+ const mine = await g(`${H}/${V}/${ID}/media?fields=permalink&limit=50&access_token=${encodeURIComponent(T)}`);
+ const ownSet = new Set((mine.data || []).map((x) => x.permalink));
+
const seen = new Set(); const cands = [];
for (const [tag, hid] of Object.entries(HASHTAG_IDS)) {
const j = await g(`${H}/${V}/${hid}/recent_media?user_id=${ID}&fields=caption,media_type,media_url,permalink,like_count,comments_count&limit=25&access_token=${encodeURIComponent(T)}`);
for (const m of (j.data || [])) {
if (seen.has(m.permalink)) continue; seen.add(m.permalink);
+ if (ownSet.has(m.permalink)) continue; // our own reshare — never re-share ourselves
+ if (doneSrc.has(m.permalink)) continue; // already reshared — never repeat
if (!m.media_url || !/^https?:/.test(m.media_url)) continue; // need a fetchable image
if (!['IMAGE', 'CAROUSEL_ALBUM'].includes(m.media_type)) continue;
if (/follow to win|giveaway|dm to buy|onlyfans|crypto/i.test(m.caption || '')) continue;
@@ -83,7 +103,8 @@ async function publish(imageUrl, caption) {
}
}
cands.sort((a, b) => b.score - a.score);
- const picks = cands.slice(0, N);
+ const picks = cands.slice(0, allowed);
+ if (GO) console.log(`(${todayCount}/${DAILY_CAP} reshared today; posting up to ${allowed} more)`);
console.log(`${GO ? 'LIVE RESHARE' : 'DRAFT'} — ${picks.length} attributable pick(s), DW-vendor matches ranked first:\n`);
const results = [];
diff --git a/agents/instagram-agent/monitor-reshares.js b/agents/instagram-agent/monitor-reshares.js
new file mode 100644
index 0000000..20ff2bc
--- /dev/null
+++ b/agents/instagram-agent/monitor-reshares.js
@@ -0,0 +1,35 @@
+// Poll the 2 fabric-friday reshares for engagement; notify on first likes/comments.
+const accounts=require('./accounts');
+const { execFileSync }=require('child_process');
+const a=accounts.resolve('fabric_fridays');
+const {access_token:t,graph_host:h,graph_version:v}=a;
+const POSTS=[{id:'18054497498547838',who:'Designtex'},{id:'17881914915683823',who:'Brunschwig & Fils'}];
+const notify=(msg)=>{ try{ execFileSync('osascript',['-e',`display notification ${JSON.stringify(msg)} with title "@fabric_fridays 🧵" sound name "Glass"`]); }catch(_){} };
+const base={};
+const g=async(u)=>(await fetch(u)).json();
+const CHECKS=16, EVERY=900000; // ~4h at 15-min intervals
+(async()=>{
+ console.log('monitoring 2 reshares for engagement (15-min polls, ~4h)...');
+ for(let i=0;i<CHECKS;i++){
+ for(const p of POSTS){
+ const m=await g(`${h}/${v}/${p.id}?fields=like_count,comments_count&access_token=${encodeURIComponent(t)}`);
+ const key=p.id; const prev=base[key]||{like_count:0,comments_count:0};
+ const nl=m.like_count||0, nc=m.comments_count||0;
+ if(nl>prev.like_count || nc>prev.comments_count){
+ let newComments='';
+ if(nc>prev.comments_count){
+ const cj=await g(`${h}/${v}/${p.id}/comments?fields=text,username&access_token=${encodeURIComponent(t)}`);
+ newComments=(cj.data||[]).slice(-(nc-prev.comments_count)).map(c=>`@${c.username}: ${c.text}`).join(' | ');
+ }
+ const msg=`${p.who} reshare: ❤${nl} 💬${nc}${newComments?' — '+newComments:''}`;
+ console.log(new Date().toISOString(), msg);
+ notify(msg);
+ }
+ base[key]={like_count:nl,comments_count:nc};
+ }
+ if(i<CHECKS-1) await new Promise(r=>setTimeout(r,EVERY));
+ }
+ const summary=POSTS.map(p=>`${p.who}: ❤${base[p.id].like_count} 💬${base[p.id].comments_count}`).join(' · ');
+ console.log('MONITOR DONE —', summary);
+ notify('Reshare watch ended — '+summary);
+})();
← 05e1786 IG: delete-viewer cockpit generator — visual selectable boar
·
back to Norma
·
IG: fabric-friday reshare launchd plist (4x/day spaced, cap 8ad73fc →