← back to Dear Bubbe Nextjs
lib/social-autoposter.js
149 lines
#!/usr/bin/env node
/**
* Unified Social Media Auto-Poster for Dear Bubbe
* Posts to Twitter/X and Bluesky using official APIs
*
* Usage: node social-autoposter.js [twitter|bluesky|all] [post|schedule|stats]
*/
const { postTweet } = require('./twitter-v2-official');
const { optimizeTweet } = require('./twitter-optimizer');
const { postToBluesky } = require('./bluesky-poster');
const { anonymizeMessage } = require('./anonymous-user-poster');
const fs = require('fs').promises;
const axios = require('axios');
const STATE_FILE = '/root/Projects/dear-bubbe-admin/logs/social-poster-state.json';
const LOG_FILE = '/root/Projects/dear-bubbe-nextjs/logs/social-posts.log';
const CONFIG = {
twitter: { maxDaily: 100, minIntervalMin: 3, peakHours: [7, 9, 12, 15, 17, 19, 21] },
bluesky: { maxDaily: 10, minIntervalMin: 60 }
};
async function loadState() {
try {
const state = JSON.parse(await fs.readFile(STATE_FILE, 'utf8'));
const today = new Date().toDateString();
if (state.currentDay !== today) {
state.twitter = { postsToday: 0, lastPostTime: null };
state.bluesky = { postsToday: 0, lastPostTime: null };
state.currentDay = today;
}
return state;
} catch {
return {
currentDay: new Date().toDateString(),
twitter: { postsToday: 0, lastPostTime: null },
bluesky: { postsToday: 0, lastPostTime: null },
totalPosts: 0, weeklyPosts: []
};
}
}
async function saveState(state) {
await fs.writeFile(STATE_FILE, JSON.stringify(state, null, 2));
}
function canPost(state, platform) {
const cfg = CONFIG[platform];
const plat = state[platform];
if (plat.postsToday >= cfg.maxDaily) return false;
if (plat.lastPostTime) {
const elapsed = Date.now() - new Date(plat.lastPostTime).getTime();
if (elapsed < cfg.minIntervalMin * 60000) return false;
}
return true;
}
async function generateContent() {
const topics = [
"My mother-in-law criticized my cooking again",
"Should I quit my job without another lined up?",
"My boyfriend only texts me once a day",
"I spent $500 on shoes, was that too much?",
"My kids won't call me back",
"Is 35 too old to start dating again?",
"My boss is a total micromanager",
"I haven't been to temple in years",
"My friend is jealous of my success",
"I'm 28 and still single, is something wrong with me?"
];
const topic = topics[Math.floor(Math.random() * topics.length)];
try {
const resp = await axios.post('http://45.61.58.125:3011/api/chat', {
message: topic, mode: 'bubbe', userId: 'poster-' + Date.now(), location: 'New York, NY'
});
if (resp.data?.response) {
return { userMessage: anonymizeMessage(topic), bubbeResponse: resp.data.response };
}
} catch (e) { console.error('Content gen failed:', e.message); }
return null;
}
async function postTo(platform, content) {
const state = await loadState();
if (!canPost(state, platform)) {
console.log(`Rate limited on ${platform} (${state[platform].postsToday}/${CONFIG[platform].maxDaily})`);
return false;
}
const text = platform === 'twitter'
? optimizeTweet(content.userMessage, content.bubbeResponse)
: `Bubbe says:\n"${content.bubbeResponse.substring(0, 250)}"\n\nVisit Bubbe.AI #BubbeAI #JewishGrandma`;
let result;
if (platform === 'twitter') {
result = await postTweet(text);
} else {
result = await postToBluesky(text);
}
if (result.success) {
state[platform].postsToday++;
state[platform].lastPostTime = new Date().toISOString();
state.totalPosts++;
state.weeklyPosts = (state.weeklyPosts || []).concat({
date: new Date().toISOString(), platform, text: text.substring(0, 100)
}).slice(-500);
await saveState(state);
console.log(`Posted to ${platform} (#${state[platform].postsToday}/${CONFIG[platform].maxDaily})`);
}
return result.success;
}
async function postToAll() {
const content = await generateContent();
if (!content) { console.log('No content generated'); return; }
const tw = await postTo('twitter', content).catch(() => false);
await new Promise(r => setTimeout(r, 3000));
const bs = await postTo('bluesky', content).catch(() => false);
console.log(`Results: Twitter=${tw}, Bluesky=${bs}`);
}
async function runScheduler(platform) {
console.log(`Scheduler started for ${platform || 'all'}`);
const loop = async () => {
try {
if (!platform || platform === 'all') await postToAll();
else {
const content = await generateContent();
if (content) await postTo(platform, content);
}
} catch (e) { console.error('Scheduler error:', e.message); }
const delay = (platform === 'bluesky') ? 3600000 : 180000;
setTimeout(loop, delay);
};
loop();
}
if (require.main === module) {
const [platform = 'all', cmd = 'post'] = process.argv.slice(2);
if (cmd === 'schedule') runScheduler(platform);
else if (cmd === 'stats') loadState().then(s => { console.log(JSON.stringify(s, null, 2)); process.exit(0); });
else postToAll().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1); });
}
module.exports = { postTo, postToAll, runScheduler, generateContent, loadState };