Skip to content

Instantly share code, notes, and snippets.

@bbarker
Created October 13, 2025 23:10
Show Gist options
  • Select an option

  • Save bbarker/9f9c2ce7bac9a9d890984e5b351fc30a to your computer and use it in GitHub Desktop.

Select an option

Save bbarker/9f9c2ce7bac9a9d890984e5b351fc30a to your computer and use it in GitHub Desktop.
Retrieve posts for a bluesky account - useful for checking if a bot, etc. Written in Nu (nushell)
#!/usr/bin/env nu
# Bluesky Reply Post Fetcher for Nushell
# No authentication required for public posts
# Run with: nu script.nu
let handle = 'amy1017-99.bsky.social'
let api_base = 'https://public.api.bsky.app/xrpc'
print $"\n🔍 Analyzing account: ($handle)\n"
print "Fetching posts...\n"
# Fetch the author feed
let data = http get $"($api_base)/app.bsky.feed.getAuthorFeed?actor=($handle)&limit=100"
let posts = $data.feed
if ($posts | length) == 0 {
print "No posts found for this account."
exit
}
# Filter for replies
let replies = $posts | where { |item|
($item.post.record.reply? | is-not-empty)
}
# Statistics
print "=== ACCOUNT STATISTICS ==="
print $"Total posts retrieved: ($posts | length)"
print $"Reply posts: ($replies | length)"
print $"Original posts: (($posts | length) - ($replies | length))"
let reply_ratio = (($replies | length) / ($posts | length) * 100)
print $"Reply ratio: ($reply_ratio | into string | str substring 0..4)%"
print "\n=== RECENT REPLIES ===\n"
# Show details of recent replies
$replies | first 20 | enumerate | each { |item|
let post = $item.item.post
let text = $post.record.text? | default "[no text]"
let created_at = $post.record.createdAt? | default ""
let reply_uri = $post.record.reply?.parent?.uri? | default "unknown"
let replying_to = $reply_uri | split row "/" | last
print $"($item.index + 1). Posted: ($created_at)"
let short_text = if ($text | str length) > 100 {
$"($text | str substring 0..100)..."
} else {
$text
}
print $" Text: ($short_text)"
print $" Replying to: ($replying_to)"
print $" Likes: ($post.likeCount? | default 0) | Replies: ($post.replyCount? | default 0) | Reposts: ($post.repostCount? | default 0)"
print ""
}
# Bot indicators analysis
print "\n=== BOT INDICATOR ANALYSIS ==="
# Check posting time patterns
print "\nPosting distribution by hour (UTC):"
let hour_counts = $posts | each { |item|
let created = $item.post.record.createdAt? | default ""
if ($created | is-not-empty) {
$created | into datetime | format date "%H" | into int
}
} | compact | group-by { |hour| $hour } | transpose hour count | update count { |row| $row.count | length }
let max_count = $hour_counts | get count | math max
$hour_counts | each { |row|
let bar_length = (($row.count / $max_count) * 20 | math floor)
let bar = ("█" | fill -w $bar_length -c "█" -a left | str substring 0..$bar_length)
print $" ($row.hour | into string | fill -a right -c '0' -w 2):00 - ($bar) (($row.count))"
}
# Check for repetitive content
let texts = $posts | each { |item| $item.post.record.text? | default "" }
let unique_texts = $texts | uniq | length
let total_posts = $posts | length
let duplicate_rate = (($total_posts - $unique_texts) / $total_posts * 100)
print $"\nContent diversity: ($unique_texts) unique posts out of ($total_posts) total"
print $"Duplicate rate: ($duplicate_rate | into string | str substring 0..4)%"
# Check average engagement
let avg_likes = $posts | each { |item| $item.post.likeCount? | default 0 } | math avg
let avg_replies = $posts | each { |item| $item.post.replyCount? | default 0 } | math avg
print $"\nAverage likes per post: ($avg_likes | into string | str substring 0..4)"
print $"Average replies per post: ($avg_replies | into string | str substring 0..4)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment