Toot toooooooot (Bluesky-Mastodon cross-poster)

posting from Bluesky to Mastodon works! :]

+37 -5
app/bluesky_account.rb
··· 2 2 require_relative 'bluesky_client' 3 3 4 4 class BlueskyAccount 5 + def initialize 6 + @sky = BlueskyClient.new 7 + end 8 + 9 + def did 10 + @sky.user.did 11 + end 12 + 5 13 def login_with_password(handle, password) 6 14 did = DID.resolve_handle(handle) 7 15 if did.nil? ··· 11 19 12 20 pds = did.get_document.pds_endpoint.gsub('https://', '') 13 21 14 - sky = BlueskyClient.new 15 - sky.host = pds 16 - sky.user.id = handle 17 - sky.user.pass = password 18 - sky.log_in 22 + @sky.host = pds 23 + @sky.user.id = handle 24 + @sky.user.pass = password 25 + 26 + @sky.log_in 27 + end 28 + 29 + def fetch_likes 30 + json = @sky.get_request('com.atproto.repo.listRecords', { 31 + repo: @sky.user.did, 32 + collection: 'app.bsky.feed.like', 33 + limit: 100 34 + }) 35 + 36 + json['records'] 37 + end 38 + 39 + def fetch_record(repo, collection, rkey) 40 + @sky.get_request('com.atproto.repo.getRecord', { repo: repo, collection: collection, rkey: rkey }) 41 + end 42 + 43 + def delete_record_at(uri) 44 + repo, collection, rkey = uri.split('/')[2..4] 45 + 46 + begin 47 + @sky.post_request('com.atproto.repo.deleteRecord', { repo: repo, collection: collection, rkey: rkey }) 48 + rescue JSON::ParserError 49 + # todo 50 + end 19 51 end 20 52 end
+6
app/mastodon_account.rb
··· 41 41 client = Mastodon::REST::Client.new(base_url: "https://#{instance}") 42 42 client.create_app(APP_NAME, 'urn:ietf:wg:oauth:2.0:oob', scopes) 43 43 end 44 + 45 + def post_status(text) 46 + instance = @config['handle'].split('@').last 47 + api = MastodonAPI.new(instance, @config['access_token']) 48 + api.post_status(text) 49 + end 44 50 end
+28
app/mastodon_api.rb
··· 67 67 get_json("/accounts/#{user_id}/statuses", params) 68 68 end 69 69 70 + def post_status(text) 71 + post_json("/statuses", { 72 + status: text 73 + }) 74 + end 75 + 70 76 def get_json(path, params = {}) 71 77 url = URI(path.start_with?('https://') ? path : @root + path) 72 78 url.query = URI.encode_www_form(params) if params ··· 81 87 JSON.parse(response.body) 82 88 elsif status / 100 == 3 83 89 get_json(response['Location']) 90 + else 91 + raise APIError.new(response) 92 + end 93 + end 94 + 95 + def post_json(path, params = {}) 96 + url = URI(path.start_with?('https://') ? path : @root + path) 97 + 98 + headers = {} 99 + headers['Authorization'] = "Bearer #{@access_token}" if @access_token 100 + 101 + request = Net::HTTP::Post.new(url, headers) 102 + request.form_data = params 103 + 104 + response = Net::HTTP.start(url.hostname, url.port, :use_ssl => true) do |http| 105 + http.request(request) 106 + end 107 + 108 + status = response.code.to_i 109 + 110 + if status / 100 == 2 111 + JSON.parse(response.body) 84 112 else 85 113 raise APIError.new(response) 86 114 end
+29
app/tootify.rb
··· 29 29 30 30 @mastodon.oauth_login(handle, email, password) 31 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 + p @mastodon.post_status(record['text']) 60 + end 32 61 end
+1
tootify
··· 12 12 when 'login' 13 13 login(args[1]) 14 14 when 'check' 15 + $app.sync 15 16 else 16 17 print_help 17 18 end