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