Monorepo for Aesthetic.Computer aesthetic.computer
at main 124 lines 4.2 kB view raw
1#!/usr/bin/env node 2 3/** 4 * Query Posts 5 * 6 * Introspective tool to fetch and display posts from an ATProto account. 7 * 8 * Usage: 9 * node query-posts.mjs aesthetic.computer 10 * node query-posts.mjs aesthetic.computer --limit 20 11 */ 12 13import { AtpAgent } from '@atproto/api' 14import { config } from 'dotenv' 15 16config() 17 18const BSKY_SERVICE = process.env.BSKY_SERVICE || 'https://bsky.social' 19 20async function queryPosts(actor, limit = 10) { 21 console.log(`\n📝 Querying posts from: ${actor}`) 22 console.log(`📡 Using service: ${BSKY_SERVICE}`) 23 console.log(`📊 Limit: ${limit}\n`) 24 25 const agent = new AtpAgent({ service: BSKY_SERVICE }) 26 27 try { 28 const feed = await agent.getAuthorFeed({ actor, limit }) 29 30 console.log('═══════════════════════════════════════') 31 console.log(`Found ${feed.data.feed.length} posts`) 32 console.log('═══════════════════════════════════════\n') 33 34 if (feed.data.feed.length === 0) { 35 console.log('(no posts found)') 36 return 37 } 38 39 feed.data.feed.forEach((item, i) => { 40 const post = item.post 41 const record = post.record 42 const author = post.author 43 44 console.log(`\n━━━ Post ${i + 1} ━━━━━━━━━━━━━━━━━━━━━━`) 45 console.log(`Author: @${author.handle}`) 46 console.log(`Posted: ${new Date(post.indexedAt).toLocaleString()}`) 47 console.log(`URI: ${post.uri}`) 48 console.log() 49 console.log('Text:') 50 console.log(record.text || '(no text)') 51 console.log() 52 53 // Engagement 54 console.log(`❤️ ${post.likeCount || 0} likes`) 55 console.log(`💬 ${post.replyCount || 0} replies`) 56 console.log(`🔁 ${post.repostCount || 0} reposts`) 57 58 // Embeds 59 if (post.embed) { 60 console.log() 61 console.log('Embed:') 62 if (post.embed.images) { 63 console.log(` 📷 ${post.embed.images.length} image(s)`) 64 post.embed.images.forEach((img, j) => { 65 console.log(` ${j + 1}. ${img.alt || '(no alt text)'}`) 66 console.log(` ${img.thumb}`) 67 }) 68 } else if (post.embed.external) { 69 console.log(` 🔗 Link: ${post.embed.external.title}`) 70 console.log(` ${post.embed.external.uri}`) 71 } else if (post.embed.record) { 72 console.log(` 📄 Quoted post: ${post.embed.record.uri}`) 73 } else { 74 console.log(` ${post.embed.$type}`) 75 } 76 } 77 78 // Reply info 79 if (record.reply) { 80 console.log() 81 console.log('↩️ Reply to:', record.reply.parent.uri) 82 } 83 84 // Facets (mentions, links, hashtags) 85 if (record.facets && record.facets.length > 0) { 86 console.log() 87 console.log('Features:') 88 record.facets.forEach(facet => { 89 facet.features.forEach(feature => { 90 if (feature.$type === 'app.bsky.richtext.facet#mention') { 91 console.log(` @${feature.did}`) 92 } else if (feature.$type === 'app.bsky.richtext.facet#link') { 93 console.log(` 🔗 ${feature.uri}`) 94 } else if (feature.$type === 'app.bsky.richtext.facet#tag') { 95 console.log(` #${feature.tag}`) 96 } 97 }) 98 }) 99 } 100 }) 101 102 console.log('\n═══════════════════════════════════════\n') 103 104 } catch (error) { 105 console.error('❌ Error querying posts:', error.message) 106 process.exit(1) 107 } 108} 109 110// CLI 111const args = process.argv.slice(2) 112const actor = args.find(arg => !arg.startsWith('--')) 113const limitArg = args.find(arg => arg.startsWith('--limit')) 114const limit = limitArg ? parseInt(limitArg.split('=')[1]) : 10 115 116if (!actor) { 117 console.error('Usage: node query-posts.mjs <handle-or-did> [--limit=N]') 118 console.error('\nExamples:') 119 console.error(' node query-posts.mjs aesthetic.computer') 120 console.error(' node query-posts.mjs aesthetic.computer --limit=20') 121 process.exit(1) 122} 123 124queryPosts(actor, limit)