Toot toooooooot (Bluesky-Mastodon cross-poster)

implemented image upload

+5
app/bluesky_account.rb
··· 40 40 @sky.get_request('com.atproto.repo.getRecord', { repo: repo, collection: collection, rkey: rkey }) 41 41 end 42 42 43 + def fetch_blob(cid) 44 + # todo 45 + @sky.get_blob('com.atproto.sync.getBlob', { did: @sky.user.did, cid: cid }) 46 + end 47 + 43 48 def delete_record_at(uri) 44 49 repo, collection, rkey = uri.split('/')[2..4] 45 50
+21
app/bluesky_client.rb
··· 24 24 def save_config 25 25 File.write(CONFIG_FILE, YAML.dump(@config)) 26 26 end 27 + 28 + def get_blob(method, params = nil) 29 + check_access 30 + 31 + headers = authentication_header(true) 32 + url = URI("#{base_url}/#{method}") 33 + 34 + if params && !params.empty? 35 + url.query = URI.encode_www_form(params) 36 + end 37 + 38 + request = Net::HTTP::Get.new(url, headers) 39 + response = make_request(request) 40 + 41 + case response 42 + when Net::HTTPSuccess 43 + response.body 44 + else 45 + handle_response(response) 46 + end 47 + end 27 48 end
+8 -2
app/mastodon_account.rb
··· 42 42 client.create_app(APP_NAME, 'urn:ietf:wg:oauth:2.0:oob', scopes) 43 43 end 44 44 45 - def post_status(text) 45 + def post_status(text, media_ids = nil) 46 46 instance = @config['handle'].split('@').last 47 47 api = MastodonAPI.new(instance, @config['access_token']) 48 - api.post_status(text) 48 + api.post_status(text, media_ids) 49 + end 50 + 51 + def upload_media(data, filename, content_type, alt = nil) 52 + instance = @config['handle'].split('@').last 53 + api = MastodonAPI.new(instance, @config['access_token']) 54 + api.upload_media(data, filename, content_type, alt) 49 55 end 50 56 end
+28 -4
app/mastodon_api.rb
··· 58 58 get_json("/accounts/#{user_id}/statuses", params) 59 59 end 60 60 61 - def post_status(text) 62 - post_json("/statuses", { 63 - status: text 64 - }) 61 + def post_status(text, media_ids = nil) 62 + params = { status: text } 63 + params['media_ids[]'] = media_ids if media_ids 64 + 65 + post_json("/statuses", params) 66 + end 67 + 68 + def upload_media(data, filename, content_type, alt = nil) 69 + url = URI("https://#{@host}/api/v2/media") 70 + headers = { 'Authorization' => "Bearer #{@access_token}" } 71 + 72 + form_data = [ 73 + ['file', data, { :filename => filename, :content_type => content_type }], 74 + ['description', alt.to_s] 75 + ] 76 + 77 + request = Net::HTTP::Post.new(url, headers) 78 + request.set_form(form_data, 'multipart/form-data') 79 + 80 + response = Net::HTTP.start(url.hostname, url.port, :use_ssl => true) do |http| 81 + http.request(request) 82 + end 83 + 84 + if response.code.to_i / 100 == 2 85 + JSON.parse(response.body) 86 + else 87 + raise APIError.new(response) 88 + end 65 89 end 66 90 67 91 def get_json(path, params = {})
+29 -1
app/tootify.rb
··· 82 82 end 83 83 end 84 84 85 - p @mastodon.post_status(text) 85 + if images = attached_images(record) 86 + media_ids = [] 87 + 88 + images.each do |image| 89 + alt = image['alt'] 90 + cid = image['image']['ref']['$link'] 91 + mime = image['image']['mimeType'] 92 + 93 + data = @bluesky.fetch_blob(cid) 94 + 95 + uploaded_media = @mastodon.upload_media(data, cid, mime, alt) 96 + media_ids << uploaded_media['id'] 97 + end 98 + end 99 + 100 + p @mastodon.post_status(text, media_ids) 86 101 end 87 102 88 103 def expand_facets(record) ··· 125 140 embed['record']['uri'] 126 141 when 'app.bsky.embed.recordWithMedia' 127 142 embed['record']['record']['uri'] 143 + else 144 + nil 145 + end 146 + end 147 + end 148 + 149 + def attached_images(record) 150 + if embed = record['embed'] 151 + case embed['$type'] 152 + when 'app.bsky.embed.images' 153 + embed['images'] 154 + when 'app.bsky.embed.recordWithMedia' 155 + embed['media']['images'] 128 156 else 129 157 nil 130 158 end