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