← back to Dear Bubbe Nextjs

lib/twitter-oauth.js

158 lines

const https = require('https');
const OAuth = require('oauth-1.0a');
const crypto = require('crypto');
const fs = require('fs').promises;
const querystring = require('querystring');

// Load config
const config = require('./twitter-api-config.json');

// OAuth configuration
const oauth = OAuth({
  consumer: {
    key: config.appKey,
    secret: config.appSecret
  },
  signature_method: 'HMAC-SHA1',
  hash_function(base_string, key) {
    return crypto.createHmac('sha1', key).update(base_string).digest('base64');
  }
});

const token = {
  key: config.accessToken,
  secret: config.accessSecret
};

async function postTweet(text) {
  console.log('🐦 Posting via OAuth 1.0a (Twitter API v1.1)...');
  console.log('Tweet:', text.substring(0, 80) + '...\n');
  
  return new Promise((resolve, reject) => {
    const request_data = {
      url: 'https://api.twitter.com/1.1/statuses/update.json',
      method: 'POST',
      data: { status: text }
    };
    
    const authHeader = oauth.toHeader(oauth.authorize(request_data, token));
    const postData = querystring.stringify(request_data.data);
    
    const options = {
      hostname: 'api.twitter.com',
      path: '/1.1/statuses/update.json',
      method: 'POST',
      headers: {
        ...authHeader,
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(postData)
      }
    };
    
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        if (res.statusCode === 200) {
          try {
            const tweet = JSON.parse(data);
            console.log('✅ Tweet posted successfully!');
            console.log('Tweet ID:', tweet.id_str);
            console.log('URL: https://x.com/DearBubbe/status/' + tweet.id_str);
            resolve({ success: true, tweetId: tweet.id_str });
          } catch (e) {
            console.error('❌ Failed to parse response');
            resolve({ success: false, error: 'Parse error' });
          }
        } else {
          console.error('❌ Error:', res.statusCode, data);
          resolve({ success: false, error: data });
        }
      });
    });
    
    req.on('error', (e) => {
      console.error('❌ Request error:', e.message);
      reject(e);
    });
    
    req.write(postData);
    req.end();
  });
}

async function postFromQueue() {
  try {
    const queuePath = '/root/Projects/dear-bubbe-admin/logs/twitter-queue.json';
    const queue = JSON.parse(await fs.readFile(queuePath, 'utf8'));
    
    const toPost = queue.find(t => t.status === 'queued');
    if (!toPost) {
      console.log('No posts in queue');
      return false;
    }
    
    console.log('📤 Posting from queue...');
    const result = await postTweet(toPost.text);
    
    if (result.success) {
      toPost.status = 'posted';
      toPost.postedAt = new Date().toISOString();
      toPost.tweetId = result.tweetId;
      await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
      console.log('✅ Queue updated');
      
      // Log to immediate posts
      const immediateLogsPath = '/root/Projects/dear-bubbe-admin/logs/immediate-posts.json';
      try {
        const logs = JSON.parse(await fs.readFile(immediateLogsPath, 'utf8'));
        logs.push({
          timestamp: new Date().toISOString(),
          platform: 'twitter',
          status: 'posted',
          tweetId: result.tweetId,
          text: toPost.text,
          url: `https://x.com/DearBubbe/status/${result.tweetId}`
        });
        await fs.writeFile(immediateLogsPath, JSON.stringify(logs, null, 2));
      } catch (e) {
        // Log file might not exist
      }
    }
    
    return result.success;
  } catch (error) {
    console.error('Error:', error);
    return false;
  }
}

async function postAll() {
  console.log('📬 Posting all queued tweets...\n');
  let posted = 0;
  
  while (true) {
    const success = await postFromQueue();
    if (!success) break;
    posted++;
    
    // Wait 5 seconds between posts to avoid rate limits
    console.log('Waiting 5 seconds before next post...\n');
    await new Promise(r => setTimeout(r, 5000));
  }
  
  console.log(`\n✅ Posted ${posted} tweets total`);
  return posted;
}

module.exports = { postTweet, postFromQueue, postAll };

if (require.main === module) {
  if (process.argv[2] === 'test') {
    postTweet('Bubbe is back! The API is working, bubbeleh! #BubbeAI Visit Bubbe.AI').then(console.log);
  } else if (process.argv[2] === 'all') {
    postAll();
  } else {
    postFromQueue();
  }
}