Toot toooooooot (Bluesky-Mastodon cross-poster)
1require 'io/console' 2 3require_relative 'bluesky_account' 4require_relative 'mastodon_account' 5 6class Tootify 7 attr_accessor :check_interval 8 9 def initialize 10 @bluesky = BlueskyAccount.new 11 @mastodon = MastodonAccount.new 12 @check_interval = 60 13 end 14 15 def login_bluesky(handle) 16 handle = handle.gsub(/^@/, '') 17 18 print "App password: " 19 password = STDIN.noecho(&:gets).chomp 20 puts 21 22 @bluesky.login_with_password(handle, password) 23 end 24 25 def login_mastodon(handle) 26 print "Email: " 27 email = STDIN.gets.chomp 28 29 print "Password: " 30 password = STDIN.noecho(&:gets).chomp 31 puts 32 33 @mastodon.oauth_login(handle, email, password) 34 end 35 36 def sync 37 likes = @bluesky.fetch_likes 38 39 likes.each do |r| 40 like_uri = r['uri'] 41 post_uri = r['value']['subject']['uri'] 42 repo, collection, rkey = post_uri.split('/')[2..4] 43 44 next unless repo == @bluesky.did && collection == 'app.bsky.feed.post' 45 46 begin 47 record = @bluesky.fetch_record(repo, collection, rkey) 48 rescue Minisky::ClientErrorResponse => e 49 puts "Record not found: #{post_uri}" 50 @bluesky.delete_record_at(like_uri) 51 next 52 end 53 54 post_to_mastodon(record['value']) 55 56 @bluesky.delete_record_at(like_uri) 57 end 58 end 59 60 def watch 61 loop do 62 sync 63 sleep @check_interval 64 end 65 end 66 67 def post_to_mastodon(record) 68 p record 69 70 text = expand_facets(record) 71 72 if link = link_embed(record) 73 if !text.include?(link) 74 text += ' ' unless text.end_with?(' ') 75 text += link 76 end 77 end 78 79 p @mastodon.post_status(text) 80 end 81 82 def expand_facets(record) 83 bytes = record['text'].bytes 84 offset = 0 85 86 if facets = record['facets'] 87 facets.sort_by { |f| f['index']['byteStart'] }.each do |f| 88 if link = f['features'].detect { |ft| ft['$type'] == 'app.bsky.richtext.facet#link' } 89 left = f['index']['byteStart'] 90 right = f['index']['byteEnd'] 91 content = link['uri'].bytes 92 93 bytes[(left + offset) ... (right + offset)] = content 94 offset += content.length - (right - left) 95 end 96 end 97 end 98 99 bytes.pack('C*').force_encoding('UTF-8') 100 end 101 102 def link_embed(record) 103 record['embed'] && record['embed']['external'] && record['embed']['external']['uri'] 104 end 105end