Monorepo for Aesthetic.Computer
aesthetic.computer
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)