Toot toooooooot (Bluesky-Mastodon cross-poster)

added support for cross-posting threads/replies

(somewhat experimental and I'll probably need to heavily refactor this)

+1
.gitignore
··· 1 .DS_Store 2 config/*.yml
··· 1 .DS_Store 2 + config/*.csv 3 config/*.yml
+2 -2
app/mastodon_account.rb
··· 46 client.create_app(APP_NAME, 'urn:ietf:wg:oauth:2.0:oob', scopes) 47 end 48 49 - def post_status(text, media_ids = nil) 50 instance = @config['handle'].split('@').last 51 api = MastodonAPI.new(instance, @config['access_token']) 52 - api.post_status(text, media_ids) 53 end 54 55 def upload_media(data, filename, content_type, alt = nil)
··· 46 client.create_app(APP_NAME, 'urn:ietf:wg:oauth:2.0:oob', scopes) 47 end 48 49 + def post_status(text, media_ids = nil, parent_id = nil) 50 instance = @config['handle'].split('@').last 51 api = MastodonAPI.new(instance, @config['access_token']) 52 + api.post_status(text, media_ids, parent_id) 53 end 54 55 def upload_media(data, filename, content_type, alt = nil)
+2 -1
app/mastodon_api.rb
··· 58 get_json("/accounts/#{user_id}/statuses", params) 59 end 60 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
··· 58 get_json("/accounts/#{user_id}/statuses", params) 59 end 60 61 + def post_status(text, media_ids = nil, parent_id = nil) 62 params = { status: text } 63 params['media_ids[]'] = media_ids if media_ids 64 + params['in_reply_to_id'] = parent_id if parent_id 65 66 post_json("/statuses", params) 67 end
+23
app/post_history.rb
···
··· 1 + class PostHistory 2 + HISTORY_FILE = File.expand_path(File.join(__dir__, '..', 'config', 'history.csv')) 3 + 4 + def initialize 5 + if File.exist?(HISTORY_FILE) 6 + @id_map = File.read(HISTORY_FILE).lines.map { |l| l.strip.split(',') }.then { |pairs| Hash[pairs] } 7 + else 8 + @id_map = {} 9 + end 10 + end 11 + 12 + def [](bluesky_rkey) 13 + @id_map[bluesky_rkey] 14 + end 15 + 16 + def add(bluesky_rkey, mastodon_id) 17 + @id_map[bluesky_rkey] = mastodon_id 18 + 19 + File.open(HISTORY_FILE, 'a') do |f| 20 + f.puts("#{bluesky_rkey},#{mastodon_id}") 21 + end 22 + end 23 + end
+36 -9
app/tootify.rb
··· 2 3 require_relative 'bluesky_account' 4 require_relative 'mastodon_account' 5 6 class Tootify 7 attr_accessor :check_interval ··· 9 def initialize 10 @bluesky = BlueskyAccount.new 11 @mastodon = MastodonAccount.new 12 @check_interval = 60 13 end 14 ··· 58 next 59 end 60 61 - if record['value']['reply'] 62 - puts "Skipping reply" 63 - @bluesky.delete_record_at(like_uri) 64 - next 65 end 66 67 - records << [record['value'], like_uri] 68 end 69 70 - records.sort_by { |x| x[0]['createdAt'] }.each do |record, like_uri| 71 - post_to_mastodon(record) 72 @bluesky.delete_record_at(like_uri) 73 end 74 end ··· 80 end 81 end 82 83 - def post_to_mastodon(record) 84 p record 85 86 text = expand_facets(record) ··· 121 text += "\n\n" + tags.map { |t| '#' + t.gsub(' ', '') }.join(' ') 122 end 123 124 - p @mastodon.post_status(text, media_ids) 125 end 126 127 def expand_facets(record)
··· 2 3 require_relative 'bluesky_account' 4 require_relative 'mastodon_account' 5 + require_relative 'post_history' 6 7 class Tootify 8 attr_accessor :check_interval ··· 10 def initialize 11 @bluesky = BlueskyAccount.new 12 @mastodon = MastodonAccount.new 13 + @history = PostHistory.new 14 @check_interval = 60 15 end 16 ··· 60 next 61 end 62 63 + if reply = record['value']['reply'] 64 + parent_uri = reply['parent']['uri'] 65 + prepo = parent_uri.split('/')[2] 66 + 67 + if prepo != @bluesky.did 68 + puts "Skipping reply to someone else" 69 + @bluesky.delete_record_at(like_uri) 70 + next 71 + else 72 + # self-reply, we'll try to cross-post it 73 + end 74 end 75 76 + records << [record['value'], rkey, like_uri] 77 end 78 79 + records.sort_by { |x| x[0]['createdAt'] }.each do |record, rkey, like_uri| 80 + mastodon_parent_id = nil 81 + 82 + if reply = record['reply'] 83 + parent_uri = reply['parent']['uri'] 84 + prkey = parent_uri.split('/')[4] 85 + 86 + if parent_id = @history[prkey] 87 + mastodon_parent_id = parent_id 88 + else 89 + puts "Skipping reply to a post that wasn't cross-posted" 90 + @bluesky.delete_record_at(like_uri) 91 + next 92 + end 93 + end 94 + 95 + response = post_to_mastodon(record, mastodon_parent_id) 96 + p response 97 + 98 + @history.add(rkey, response['id']) 99 @bluesky.delete_record_at(like_uri) 100 end 101 end ··· 107 end 108 end 109 110 + def post_to_mastodon(record, mastodon_parent_id = nil) 111 p record 112 113 text = expand_facets(record) ··· 148 text += "\n\n" + tags.map { |t| '#' + t.gsub(' ', '') }.join(' ') 149 end 150 151 + @mastodon.post_status(text, media_ids, mastodon_parent_id) 152 end 153 154 def expand_facets(record)