← back to Dear Bubbe Nextjs
archived/social-posters/twitter-anonymous-automated.js
288 lines
#!/usr/bin/env node
const { chromium } = require('playwright');
const fs = require('fs').promises;
const path = require('path');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
// Import anonymization
const { anonymizeMessage } = require('./anonymous-user-poster');
// Configuration
const CONFIG = {
TWITTER_USERNAME: 'DearBubbe',
TWITTER_PASSWORD: '*Twitteraccess911*',
COOKIES_PATH: path.join(__dirname, 'twitter-cookies.json'),
STATE_FILE: '/root/Projects/dear-bubbe-admin/logs/twitter-anon-state.json',
POSTS_PER_DAY: 5, // Reduced to 5 as requested
MIN_INTERVAL_HOURS: 4, // Post every 4-5 hours
};
// Get user messages
async function getUserMessages() {
try {
const { stdout } = await execPromise(
`pm2 logs bubbe-ai --lines 1000 --nostream | grep -E "message:" | grep -v "__init__" | grep -v "__returning" | head -50`
);
const messages = [];
const lines = stdout.split('\n').filter(line => line.trim());
for (const line of lines) {
const match = line.match(/message:\s*["'](.+?)["']/);
if (match && match[1]) {
const msg = match[1].trim();
if (msg.length > 20 && !msg.includes('__') && !msg.startsWith('test') && !msg.includes('favicon')) {
messages.push(msg);
}
}
}
return [...new Set(messages)];
} catch (error) {
console.error('Error getting messages:', error);
return [];
}
}
// Format for Twitter
function formatTweet(userMessage) {
const intros = [
"Real question from a user:",
"Someone asked Bubbe:",
"Anonymous dilemma:",
"Today someone asked:",
"User needs advice:"
];
const intro = intros[Math.floor(Math.random() * intros.length)];
const anonymized = anonymizeMessage(userMessage);
// Twitter limit with room for hashtags
const maxLength = 200;
const truncated = anonymized.length > maxLength ?
anonymized.substring(0, maxLength) + '...' :
anonymized;
return `${intro}\n"${truncated}"\n\n#BubbeAI #JewishWisdom\nbubbe.ai`;
}
// Load state
async function loadState() {
try {
const data = await fs.readFile(CONFIG.STATE_FILE, 'utf8');
return JSON.parse(data);
} catch {
return {
postedMessages: [],
lastPostTime: null,
totalPosts: 0,
dailyPosts: 0,
lastResetDate: new Date().toDateString()
};
}
}
// Save state
async function saveState(state) {
await fs.writeFile(CONFIG.STATE_FILE, JSON.stringify(state, null, 2));
}
// Load cookies
async function loadCookies(context) {
try {
const cookies = JSON.parse(await fs.readFile(CONFIG.COOKIES_PATH, 'utf8'));
await context.addCookies(cookies);
return true;
} catch (e) {
console.log('No saved cookies');
return false;
}
}
// Save cookies
async function saveCookies(context) {
const cookies = await context.cookies();
await fs.writeFile(CONFIG.COOKIES_PATH, JSON.stringify(cookies, null, 2));
}
// Post to Twitter
async function postToTwitter(text) {
const browser = await chromium.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
try {
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
});
const page = await context.newPage();
// Load cookies
const cookiesLoaded = await loadCookies(context);
// Go to Twitter
await page.goto('https://twitter.com/home', { waitUntil: 'networkidle', timeout: 30000 });
// Check if login needed
const needsLogin = await page.url().includes('login') || await page.url().includes('i/flow');
if (needsLogin && !cookiesLoaded) {
console.log('Logging in...');
// Login flow
await page.waitForSelector('input[autocomplete="username"]', { timeout: 10000 });
await page.fill('input[autocomplete="username"]', CONFIG.TWITTER_USERNAME);
await page.press('input[autocomplete="username"]', 'Enter');
await page.waitForTimeout(2000);
await page.waitForSelector('input[type="password"]', { timeout: 10000 });
await page.fill('input[type="password"]', CONFIG.TWITTER_PASSWORD);
await page.press('input[type="password"]', 'Enter');
await page.waitForURL('**/home', { timeout: 15000 });
await saveCookies(context);
console.log('✅ Login successful');
}
// Click compose button
await page.waitForSelector('[data-testid="SideNav_NewTweet_Button"]', { timeout: 10000 });
await page.click('[data-testid="SideNav_NewTweet_Button"]');
// Type tweet
await page.waitForSelector('[data-testid="tweetTextarea_0"]', { timeout: 10000 });
await page.fill('[data-testid="tweetTextarea_0"]', text);
await page.waitForTimeout(1000);
// Post it
await page.click('[data-testid="tweetButton"]');
await page.waitForTimeout(3000);
// Save cookies
await saveCookies(context);
console.log('✅ Posted to Twitter successfully!');
return { success: true };
} catch (error) {
console.error('❌ Error posting:', error.message);
return { success: false, error: error.message };
} finally {
await browser.close();
}
}
// Main posting function
async function postAnonymousToTwitter() {
console.log('🐦 Twitter Anonymous Poster (5x daily)');
console.log('=====================================');
// Load state
const state = await loadState();
// Reset daily counter
const today = new Date().toDateString();
if (today !== state.lastResetDate) {
state.dailyPosts = 0;
state.lastResetDate = today;
console.log('📅 New day - counter reset');
}
// Check daily limit
if (state.dailyPosts >= CONFIG.POSTS_PER_DAY) {
console.log(`📊 Daily limit reached (${state.dailyPosts}/${CONFIG.POSTS_PER_DAY})`);
return;
}
// Check interval (4 hours minimum)
if (state.lastPostTime) {
const hoursSince = (Date.now() - new Date(state.lastPostTime).getTime()) / (1000 * 60 * 60);
if (hoursSince < CONFIG.MIN_INTERVAL_HOURS) {
const waitHours = (CONFIG.MIN_INTERVAL_HOURS - hoursSince).toFixed(1);
console.log(`⏱️ Waiting ${waitHours} more hours before next post`);
return;
}
}
// Get messages
const messages = await getUserMessages();
console.log(`📝 Found ${messages.length} messages`);
// Filter unposted
const unposted = messages.filter(msg => !state.postedMessages.includes(msg));
console.log(`📌 ${unposted.length} new messages`);
if (unposted.length === 0) {
console.log('❌ No new messages');
return;
}
// Pick random message
const selected = unposted[Math.floor(Math.random() * unposted.length)];
console.log(`\n🎯 Selected: "${selected.substring(0, 50)}..."`);
// Format tweet
const tweet = formatTweet(selected);
console.log(`\n🐦 Tweet:\n${tweet}\n`);
// Post to Twitter
const result = await postToTwitter(tweet);
if (result.success) {
// Update state
state.postedMessages.push(selected);
state.lastPostTime = new Date().toISOString();
state.totalPosts++;
state.dailyPosts++;
// Keep only last 100
if (state.postedMessages.length > 100) {
state.postedMessages = state.postedMessages.slice(-100);
}
await saveState(state);
console.log(`📊 Posted ${state.dailyPosts}/${CONFIG.POSTS_PER_DAY} today`);
console.log(`📈 Total: ${state.totalPosts} posts`);
}
}
// Continuous posting
async function continuousPost() {
console.log('🚀 Starting Twitter Anonymous Poster');
console.log(`📊 ${CONFIG.POSTS_PER_DAY} posts per day`);
console.log(`⏱️ Every ${CONFIG.MIN_INTERVAL_HOURS} hours`);
console.log('=' .repeat(40));
// Post immediately
await postAnonymousToTwitter();
// Schedule every hour to check if it's time to post
setInterval(async () => {
await postAnonymousToTwitter();
}, 60 * 60 * 1000); // Check every hour
}
// Handle shutdown
process.on('SIGINT', () => {
console.log('\n👋 Shutting down...');
process.exit(0);
});
// Run
if (require.main === module) {
const args = process.argv.slice(2);
if (args[0] === 'continuous') {
continuousPost().catch(console.error);
} else {
postAnonymousToTwitter().catch(console.error);
}
}
module.exports = { postAnonymousToTwitter };