← back to Dear Bubbe Nextjs

lib/bluesky-poster.js

153 lines

const { BskyAgent, RichText } = require('@atproto/api');
const fs = require('fs').promises;
const path = require('path');

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

// Create Bluesky agent
const agent = new BskyAgent({
  service: 'https://bsky.social'
});

// Login to Bluesky
async function login() {
  try {
    await agent.login({
      identifier: config.identifier,
      password: config.password
    });
    return true;
  } catch (error) {
    console.error('❌ Failed to login to Bluesky:', error.message);
    return false;
  }
}

// Post to Bluesky
async function postToBluesky(text) {
  try {
    console.log('🦋 Posting to Bluesky...');
    console.log('Post:', text.substring(0, 100) + '...');
    
    // Login first
    const loggedIn = await login();
    if (!loggedIn) {
      return { success: false, error: 'Login failed' };
    }
    
    // Create rich text with proper encoding
    const rt = new RichText({ text });
    await rt.detectFacets(agent); // Detect mentions, links, etc.
    
    // Post
    const response = await agent.post({
      text: rt.text,
      facets: rt.facets,
      createdAt: new Date().toISOString()
    });
    
    if (response.uri) {
      // Extract post ID from URI
      const postId = response.uri.split('/').pop();
      const url = `https://bsky.app/profile/${config.identifier}/post/${postId}`;
      
      console.log('✅ Posted to Bluesky!');
      console.log('URL:', url);
      
      return { 
        success: true, 
        uri: response.uri,
        cid: response.cid,
        url: url,
        postId: postId
      };
    } else {
      return { success: false, error: 'No URI returned' };
    }
  } catch (error) {
    console.error('❌ Error posting to Bluesky:', error.message);
    return { success: false, error: error.message };
  }
}

// Post from queue
async function postFromQueue() {
  try {
    const queuePath = '/root/Projects/dear-bubbe-admin/logs/bluesky-queue.json';
    let queue = [];
    
    try {
      queue = JSON.parse(await fs.readFile(queuePath, 'utf8'));
    } catch (e) {
      console.log('No queue file found');
      return false;
    }
    
    const toPost = queue.find(item => item.status === 'queued');
    if (!toPost) {
      console.log('No queued posts');
      return false;
    }
    
    console.log('📤 Posting from queue...');
    const result = await postToBluesky(toPost.text);
    
    if (result.success) {
      toPost.status = 'posted';
      toPost.postedAt = new Date().toISOString();
      toPost.uri = result.uri;
      toPost.url = result.url;
      
      await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
      console.log('✅ Queue updated');
      
      // Log to posted file
      const postedPath = '/root/Projects/dear-bubbe-admin/logs/bluesky-posted.json';
      let posted = [];
      try {
        posted = JSON.parse(await fs.readFile(postedPath, 'utf8'));
      } catch (e) {
        // File doesn't exist
      }
      
      posted.push({
        ...toPost,
        postedAt: new Date().toISOString(),
        url: result.url
      });
      
      await fs.writeFile(postedPath, JSON.stringify(posted, null, 2));
    }
    
    return result.success;
  } catch (error) {
    console.error('Error:', error);
    return false;
  }
}

module.exports = {
  postToBluesky,
  postFromQueue,
  login
};

// Test if called directly
if (require.main === module) {
  const command = process.argv[2];
  
  if (command === 'test') {
    const testText = 'Bubbe is now on Bluesky! The schmucks finally figured it out! #BubbeAI #Bubbe #JewishGrandma Visit Bubbe.AI';
    postToBluesky(testText).then(result => {
      console.log('Result:', result);
      process.exit(result.success ? 0 : 1);
    });
  } else if (command === 'queue') {
    postFromQueue().then(success => {
      process.exit(success ? 0 : 1);
    });
  } else {
    console.log('Usage: node bluesky-poster.js [test|queue]');
  }
}