Skip to content

fetch_bookmarks.rb

Example: sync your private bookmarks to a local JSON file. When run again, it will only fetch new ones since the last time and add them to the file.

rb
# Since bookmarks are private (currently stored on the AppView), this requires
# authentication. You will need a .yml config file with contents like this:
#
# id: your.handle
# pass: secretpass

require 'didkit'
require 'minisky'
require 'time'
require 'yaml'

$config_file = ARGV[0]
$bookmarks_file = ARGV[1]

if $config_file.nil? || $bookmarks_file.nil?
  puts "Usage: #{$PROGRAM_NAME} <config.yml> <bookmarks.json>"
  puts "Config.yml needs to include: { id: 'handle', pass: 'password' }"
  exit 1
elsif !File.exist?(File.expand_path($config_file))
  puts "Config file not found (#{$config_file})."
  puts "Create a YAML file with { id: 'handle', pass: 'password' }"
  exit 1
end

# this is a bit suboptimal, but currently you need to pass the PDS hostname to
# Minisky, even though it could look it up itself based on the handle in the config.
# this might be fixed in the future
id = YAML.load(File.read($config_file))['id']
pds = DID.resolve_handle(id).document.pds_host

# create a client instance using the auth config file
bsky = Minisky.new(pds, $config_file)

# print progress dots when loading multiple pages
bsky.default_progress = '.'

# load previously saved bookmarks; we will only fetch bookmarks newer than the
# last saved before
bookmarks = File.exist?($bookmarks_file) ? JSON.parse(File.read($bookmarks_file)) : []
latest_date = bookmarks[0] && bookmarks[0]['createdAt']

if !bookmarks.empty?
  last_date_fmt = Time.parse(latest_date).getlocal
  puts "Loaded #{bookmarks.length} existing bookmarks (last date: #{last_date_fmt})"
end

# fetch all bookmarks until the target timestamp
new_items = bsky.fetch_all('app.bsky.bookmark.getBookmarks',
  { limit: 100 },
  field: 'bookmarks',
  break_when: latest_date && proc { |x| x['createdAt'] <= latest_date }
)

# trim some data to save space
new_items.each do |b|
  if b['item']['author']
    b['item']['author'] = b['item']['author'].slice('did', 'handle')
  end
end

bookmarks = new_items + bookmarks

puts
puts "Fetched #{new_items.length} new bookmarks (total = #{bookmarks.length})"

# save all new and old bookmarks back to the file
File.write($bookmarks_file, JSON.pretty_generate(bookmarks))