Skip to content

latest_leaflets.rb

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.

rb
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} <handle | did>"
  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