#!/usr/bin/env ruby # Example: latest Leaflet articles from the people you follow. # # Leaflet is a blogging platform based on the AT Protocol. This script downloads the list of # accounts you follow on Bluesky, checks which of them have a Leaflet blog, and then looks up # the most recent Leaflet post for each of those accounts. require 'bundler/setup' require 'didkit' require 'minisky' # TODO: currently only these "staging" relays support listReposByCollection RELAY = 'relay1.us-west.bsky.network' $handle = ARGV[0] if $handle.nil? puts "Usage: #{$PROGRAM_NAME} " exit 1 end $did = DID.resolve_handle($handle) if $did.nil? puts "Couldn't resolve handle: #{$handle}" exit 1 end pds_host = $did.document.pds_host pds = Minisky.new(pds_host, nil, progress: '.') print "Fetching all follows of #{$handle} from #{pds_host}: " follows = pds.fetch_all('com.atproto.repo.listRecords', { repo: $did, collection: 'app.bsky.graph.follow', limit: 100 }, field: 'records') puts puts "Found #{follows.length} follows." print "Fetching list of repos with Leaflet documents: " # listReposByCollection returns a list of all DIDs on the whole network (known to # the relay) which have at least one record of the given type relay = Minisky.new(RELAY, nil, progress: '.') leaflet_repos = relay.fetch_all('com.atproto.sync.listReposByCollection', { collection: 'pub.leaflet.document' }, field: 'repos') # follows with Leaflets are the intersection of the two lists: followed_dids = follows.map { |x| x['value']['subject'] } leaflet_dids = leaflet_repos.map { |x| x['did'] } leaflet_follows = followed_dids & leaflet_dids puts print "Found #{leaflet_repos.length} repos - " puts "#{leaflet_follows.length} followed profiles have a Leaflet blog." puts puts '-' * 40 # For each user, load Bluesky profile info from the AppView (to get their name) # and one Leaflet document record from their PDS. Records in listRecords are # sorted by rkey (which is based on time), so the first one will be the newest. appview = Minisky.new('api.bsky.app', nil) leaflet_follows.each do |did| begin fpds = DID.new(did).document.pds_host response = Minisky.new(fpds, nil).get_request('com.atproto.repo.listRecords', { repo: did, collection: 'pub.leaflet.document', limit: 1 }) if record = response['records'][0] profile = appview.get_request('app.bsky.actor.getProfile', { actor: did }) puts puts "#{profile['displayName']} (@#{profile['handle']}):" published_at = Time.parse(record['value']['publishedAt']) puts "#{record['value']['title'].inspect} • #{published_at}" if !record['value']['description'].to_s.empty? puts puts record['value']['description'] end puts puts '-' * 40 end rescue StandardError => e if fpds puts "\n#{did}: error loading repo from #{fpds}" else puts "\n#{did}: error loading account info" end puts puts '-' * 40 end end