← back to Dear Bubbe Nextjs

archived/social-posters/playwright-twitter-poster.js

171 lines

const { chromium } = require('playwright');
const fs = require('fs').promises;
const path = require('path');

// Configuration
const TWITTER_USERNAME = 'DearBubbe';
const TWITTER_PASSWORD = '*Twitteraccess911*';
const COOKIES_PATH = path.join(__dirname, 'twitter-cookies.json');
const POSTED_IDS_PATH = path.join(__dirname, 'posted-twitter-ids.json');

// Load cookies
async function loadCookies(context) {
    try {
        const cookies = JSON.parse(await fs.readFile(COOKIES_PATH, 'utf8'));
        await context.addCookies(cookies);
        return true;
    } catch (e) {
        console.log('No saved cookies found');
        return false;
    }
}

// Save cookies
async function saveCookies(context) {
    const cookies = await context.cookies();
    await fs.writeFile(COOKIES_PATH, JSON.stringify(cookies, null, 2));
}

// Main posting function
async function postToTwitter(text, messageId = null) {
    console.log('\n🐦 POSTING TO TWITTER');
    console.log('Text:', text.substring(0, 100) + '...');
    
    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 if available
        const cookiesLoaded = await loadCookies(context);
        
        // Go to Twitter
        await page.goto('https://twitter.com/home', { waitUntil: 'networkidle' });
        
        // Check if we need to login
        const needsLogin = await page.url().includes('login') || await page.url().includes('i/flow');
        
        if (needsLogin && !cookiesLoaded) {
            console.log('Logging in to Twitter...');
            
            // Enter username
            await page.waitForSelector('input[autocomplete="username"]', { timeout: 10000 });
            await page.fill('input[autocomplete="username"]', TWITTER_USERNAME);
            await page.press('input[autocomplete="username"]', 'Enter');
            
            // Wait for password field
            await page.waitForTimeout(2000);
            
            // Enter password
            await page.waitForSelector('input[type="password"]', { timeout: 10000 });
            await page.fill('input[type="password"]', TWITTER_PASSWORD);
            await page.press('input[type="password"]', 'Enter');
            
            // Wait for login to complete
            await page.waitForURL('**/home', { timeout: 15000 });
            
            // Save cookies
            await saveCookies(context);
            console.log('Login successful, cookies saved');
        }
        
        // Click compose tweet button
        await page.waitForSelector('[data-testid="SideNav_NewTweet_Button"]', { timeout: 10000 });
        await page.click('[data-testid="SideNav_NewTweet_Button"]');
        
        // Wait for compose modal
        await page.waitForTimeout(1000);
        
        // Type the tweet
        await page.waitForSelector('[data-testid="tweetTextarea_0"]', { timeout: 10000 });
        await page.fill('[data-testid="tweetTextarea_0"]', text);
        
        // Wait a moment
        await page.waitForTimeout(1000);
        
        // Click Tweet button
        await page.click('[data-testid="tweetButton"]');
        
        // Wait for tweet to be posted
        await page.waitForTimeout(3000);
        
        // Mark as posted
        if (messageId) {
            let postedIds = [];
            try {
                postedIds = JSON.parse(await fs.readFile(POSTED_IDS_PATH, 'utf8'));
            } catch (e) {}
            postedIds.push(messageId);
            await fs.writeFile(POSTED_IDS_PATH, JSON.stringify(postedIds, null, 2));
        }
        
        console.log('✅ Tweet posted successfully!');
        await context.close();
        await browser.close();
        return true;
        
    } catch (error) {
        console.error('❌ Error posting tweet:', error.message);
        await browser.close();
        return false;
    }
}

// Post from queue
async function postFromQueue() {
    try {
        const queuePath = '/root/Projects/dear-bubbe-admin/logs/twitter-queue.json';
        const queue = JSON.parse(await fs.readFile(queuePath, 'utf8'));
        
        if (queue.length === 0) {
            console.log('No tweets in queue');
            return;
        }
        
        // Get first unposted tweet
        const toPost = queue.find(t => t.status === 'queued');
        if (!toPost) {
            console.log('All tweets already posted');
            return;
        }
        
        // Post it
        const success = await postToTwitter(toPost.text);
        
        if (success) {
            // Update queue
            toPost.status = 'posted';
            toPost.postedAt = new Date().toISOString();
            await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
            console.log('Queue updated');
        }
        
        return success;
        
    } catch (error) {
        console.error('Error posting from queue:', error);
        return false;
    }
}

module.exports = {
    postToTwitter,
    postFromQueue
};

// If run directly, post from queue
if (require.main === module) {
    postFromQueue()
        .then(result => {
            console.log('Result:', result ? 'SUCCESS' : 'FAILED');
            process.exit(result ? 0 : 1);
        });
}