← 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]');
}
}