A simple tool which lets you scrape twitter accounts and crosspost them to bluesky accounts! Comes with a CLI and a webapp for managing profiles! Works with images/videos/link embeds/threads.
at master 81 lines 2.9 kB view raw
1import { BskyAgent } from '@atproto/api'; 2import { getConfig } from './config-manager.js'; 3 4const activeAgents = new Map<string, BskyAgent>(); 5 6export async function getAgent(mapping: { 7 bskyIdentifier: string; 8 bskyPassword: string; 9 bskyServiceUrl?: string; 10}): Promise<BskyAgent | null> { 11 const serviceUrl = mapping.bskyServiceUrl || 'https://bsky.social'; 12 const cacheKey = `${mapping.bskyIdentifier}-${serviceUrl}`; 13 const existing = activeAgents.get(cacheKey); 14 if (existing) return existing; 15 16 const agent = new BskyAgent({ service: serviceUrl }); 17 try { 18 await agent.login({ identifier: mapping.bskyIdentifier, password: mapping.bskyPassword }); 19 activeAgents.set(cacheKey, agent); 20 return agent; 21 } catch (err) { 22 console.error(`Failed to login to Bluesky for ${mapping.bskyIdentifier} on ${serviceUrl}:`, err); 23 return null; 24 } 25} 26 27export async function deleteAllPosts(mappingId: string): Promise<number> { 28 const config = getConfig(); 29 const mapping = config.mappings.find(m => m.id === mappingId); 30 if (!mapping) throw new Error('Mapping not found'); 31 32 const agent = await getAgent(mapping); 33 if (!agent) throw new Error('Failed to authenticate with Bluesky'); 34 35 let cursor: string | undefined; 36 let deletedCount = 0; 37 38 console.log(`[${mapping.bskyIdentifier}] 🗑️ Starting deletion of all posts...`); 39 40 // Safety loop limit to prevent infinite loops 41 let loops = 0; 42 while (loops < 1000) { 43 loops++; 44 try { 45 const { data } = await agent.com.atproto.repo.listRecords({ 46 repo: agent.session!.did, 47 collection: 'app.bsky.feed.post', 48 limit: 50, // Keep batch size reasonable 49 cursor, 50 }); 51 52 if (data.records.length === 0) break; 53 54 console.log(`[${mapping.bskyIdentifier}] 🗑️ Deleting batch of ${data.records.length} posts...`); 55 56 // Use p-limit like approach or just Promise.all since 50 is manageable 57 await Promise.all(data.records.map(r => 58 agent.com.atproto.repo.deleteRecord({ 59 repo: agent.session!.did, 60 collection: 'app.bsky.feed.post', 61 rkey: r.uri.split('/').pop()!, 62 }).catch(e => console.warn(`Failed to delete record ${r.uri}:`, e)) 63 )); 64 65 deletedCount += data.records.length; 66 cursor = data.cursor; 67 68 if (!cursor) break; 69 70 // Small delay to be nice to the server 71 await new Promise(r => setTimeout(r, 500)); 72 73 } catch (err) { 74 console.error(`[${mapping.bskyIdentifier}] ❌ Error during deletion loop:`, err); 75 throw err; 76 } 77 } 78 79 console.log(`[${mapping.bskyIdentifier}] ✅ Deleted ${deletedCount} posts.`); 80 return deletedCount; 81}