Toot toooooooot (Bluesky-Mastodon cross-poster)

migrated post history from csv to sqlite

+1 -1
.gitignore
··· 1 1 .DS_Store 2 - config/*.csv 3 2 config/*.yml 3 + db/history.sqlite3
+3
Gemfile
··· 8 8 gem 'net-http', '~> 0.2' 9 9 gem 'uri', '~> 0.13' 10 10 gem 'yaml', '~> 0.1' 11 + 12 + gem "activerecord", "~> 7.2" 13 + gem "sqlite3", "~> 2.7"
+39
Gemfile.lock
··· 1 1 GEM 2 2 remote: https://rubygems.org/ 3 3 specs: 4 + activemodel (7.2.2.1) 5 + activesupport (= 7.2.2.1) 6 + activerecord (7.2.2.1) 7 + activemodel (= 7.2.2.1) 8 + activesupport (= 7.2.2.1) 9 + timeout (>= 0.4.0) 10 + activesupport (7.2.2.1) 11 + base64 12 + benchmark (>= 0.3) 13 + bigdecimal 14 + concurrent-ruby (~> 1.0, >= 1.3.1) 15 + connection_pool (>= 2.2.5) 16 + drb 17 + i18n (>= 1.6, < 2) 18 + logger (>= 1.4.2) 19 + minitest (>= 5.1) 20 + securerandom (>= 0.3) 21 + tzinfo (~> 2.0, >= 2.0.5) 4 22 base64 (0.2.0) 23 + benchmark (0.4.1) 24 + bigdecimal (3.2.2) 25 + concurrent-ruby (1.3.5) 26 + connection_pool (2.5.3) 5 27 didkit (0.2.3) 28 + drb (2.2.3) 29 + i18n (1.14.7) 30 + concurrent-ruby (~> 1.0) 6 31 io-console (0.7.2) 7 32 json (2.10.2) 33 + logger (1.7.0) 34 + mini_portile2 (2.8.9) 8 35 minisky (0.5.0) 9 36 base64 (~> 0.1) 37 + minitest (5.25.5) 10 38 net-http (0.4.1) 11 39 uri 40 + securerandom (0.4.1) 41 + sqlite3 (2.7.1) 42 + mini_portile2 (~> 2.8.0) 43 + sqlite3 (2.7.1-aarch64-linux-gnu) 44 + sqlite3 (2.7.1-arm64-darwin) 45 + sqlite3 (2.7.1-x86_64-linux-gnu) 46 + timeout (0.4.3) 47 + tzinfo (2.0.6) 48 + concurrent-ruby (~> 1.0) 12 49 uri (0.13.2) 13 50 yaml (0.3.0) 14 51 ··· 19 56 x86_64-linux 20 57 21 58 DEPENDENCIES 59 + activerecord (~> 7.2) 22 60 didkit (~> 0.2) 23 61 io-console (~> 0.5) 24 62 json (~> 2.5) 25 63 minisky (~> 0.5) 26 64 net-http (~> 0.2) 65 + sqlite3 (~> 2.7) 27 66 uri (~> 0.13) 28 67 yaml (~> 0.1) 29 68
+2 -1
README.md
··· 55 55 56 56 * `bluesky.yml` – created when you log in, stores Bluesky user ID/password and access tokens 57 57 * `mastodon.yml` – created when you log in, stores Mastodon user ID/password and access tokens 58 - * `history.csv` – stores a mapping between Bluesky and Mastodon post IDs; used for reply references in threads 59 58 * `tootify.yml` - optional additional configuration 60 59 61 60 The config in `tootify.yml` currently supports one option: 62 61 63 62 - `extract_link_from_quotes: true` – if enabled, posts which are quotes of someone else's post which includes a link will be "collapsed" into a normal post that just includes that link directly without the quote (so the link card on Mastodon will show info about the link and not the quoted bsky.app post) 63 + 64 + There is also an SQLite database file that's automatically created in `db/history.sqlite3`. It stores a mapping between Bluesky and Mastodon post IDs, and is used to maintain reply references in threads. 64 65 65 66 66 67 ## Credits
+16
app/database.rb
··· 1 + require 'active_record' 2 + 3 + module Database 4 + DB_FILE = File.expand_path(File.join(__dir__, '..', 'db', 'history.sqlite3')) 5 + MIGRATIONS_PATH = File.expand_path(File.join(__dir__, '..', 'db', 'migrate')) 6 + 7 + def self.init 8 + ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: DB_FILE) 9 + run_migrations 10 + end 11 + 12 + def self.run_migrations 13 + migration_context = ActiveRecord::MigrationContext.new(MIGRATIONS_PATH) 14 + migration_context.migrate 15 + end 16 + end
+10
app/post.rb
··· 1 + require 'active_record' 2 + 3 + class Post < ActiveRecord::Base 4 + validates_presence_of :mastodon_id, :bluesky_rkey 5 + 6 + validates_length_of :mastodon_id, maximum: 50 7 + validates_length_of :bluesky_rkey, is: 13 8 + 9 + validates_uniqueness_of :bluesky_rkey 10 + 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
+9 -6
app/tootify.rb
··· 2 2 require 'yaml' 3 3 4 4 require_relative 'bluesky_account' 5 + require_relative 'database' 5 6 require_relative 'mastodon_account' 6 - require_relative 'post_history' 7 + require_relative 'post' 7 8 8 9 class Tootify 9 10 CONFIG_FILE = File.expand_path(File.join(__dir__, '..', 'config', 'tootify.yml')) ··· 13 14 def initialize 14 15 @bluesky = BlueskyAccount.new 15 16 @mastodon = MastodonAccount.new 16 - @history = PostHistory.new 17 17 @config = load_config 18 18 @check_interval = 60 19 + 20 + Database.init 19 21 end 20 22 21 23 def load_config ··· 86 88 87 89 if reply = record['reply'] 88 90 parent_uri = reply['parent']['uri'] 89 - prkey = parent_uri.split('/')[4] 91 + parent_rkey = parent_uri.split('/')[4] 90 92 91 - if parent_id = @history[prkey] 92 - mastodon_parent_id = parent_id 93 + if parent_post = Post.find_by(bluesky_rkey: parent_rkey) 94 + mastodon_parent_id = parent_post.mastodon_id 93 95 else 94 96 puts "Skipping reply to a post that wasn't cross-posted" 95 97 @bluesky.delete_record_at(like_uri) ··· 100 102 response = post_to_mastodon(record, mastodon_parent_id) 101 103 p response 102 104 103 - @history.add(rkey, response['id']) 105 + Post.create!(bluesky_rkey: rkey, mastodon_id: response['id']) 106 + 104 107 @bluesky.delete_record_at(like_uri) 105 108 end 106 109 end
+10
db/migrate/001_create_posts.rb
··· 1 + class CreatePosts < ActiveRecord::Migration[7.2] 2 + def change 3 + create_table :posts do |t| 4 + t.string :bluesky_rkey, null: false 5 + t.string :mastodon_id, null: false 6 + end 7 + 8 + add_index :posts, :bluesky_rkey, unique: true 9 + end 10 + end