← back to Dear Bubbe Nextjs
archived/social-posters/playwright-bluesky-poster.js
172 lines
const { chromium } = require('playwright');
const fs = require('fs').promises;
const path = require('path');
// Configuration
const BLUESKY_HANDLE = 'dearbubbe.bsky.social';
const BLUESKY_PASSWORD = '*Blueaccess911*';
const COOKIES_PATH = path.join(__dirname, 'bluesky-cookies.json');
const POSTED_IDS_PATH = path.join(__dirname, 'posted-bluesky-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 postToBluesky(text, messageId = null) {
console.log('\n🦋 POSTING TO BLUESKY');
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 Bluesky
await page.goto('https://bsky.app', { waitUntil: 'networkidle' });
// Check if we need to login
const needsLogin = !await page.url().includes('/home') && !cookiesLoaded;
if (needsLogin) {
console.log('Logging in to Bluesky...');
// Click Sign in
await page.waitForSelector('button:has-text("Sign in")', { timeout: 10000 });
await page.click('button:has-text("Sign in")');
// Enter handle
await page.waitForSelector('input[placeholder*="Username or email"]', { timeout: 10000 });
await page.fill('input[placeholder*="Username or email"]', BLUESKY_HANDLE);
// Enter password
await page.fill('input[type="password"]', BLUESKY_PASSWORD);
// Click login
await page.click('button[type="submit"]');
// Wait for home page
await page.waitForURL('**/home', { timeout: 15000 });
// Save cookies
await saveCookies(context);
console.log('Login successful, cookies saved');
}
// Click compose button
await page.waitForSelector('button[aria-label*="Compose"]', { timeout: 10000 });
await page.click('button[aria-label*="Compose"]');
// Wait for compose modal
await page.waitForTimeout(1000);
// Type the post
await page.waitForSelector('textarea[placeholder*="What"]', { timeout: 10000 });
await page.fill('textarea[placeholder*="What"]', text);
// Wait a moment
await page.waitForTimeout(1000);
// Click Post button
await page.click('button:has-text("Post")');
// Wait for post to complete
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('✅ Bluesky post successful!');
await context.close();
await browser.close();
return true;
} catch (error) {
console.error('❌ Error posting to Bluesky:', error.message);
await browser.close();
return false;
}
}
// Post from queue
async function postFromQueue() {
try {
const queuePath = '/root/Projects/dear-bubbe-admin/logs/bluesky-queue.json';
const queue = JSON.parse(await fs.readFile(queuePath, 'utf8'));
if (queue.length === 0) {
console.log('No posts in queue');
return;
}
// Get first unposted
const toPost = queue.find(p => p.status === 'queued');
if (!toPost) {
console.log('All posts already sent');
return;
}
// Post it
const success = await postToBluesky(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 = {
postToBluesky,
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);
});
}