[WIP] Post Roulette feed where it sends you random posts by users you follow, unbiased by post age.

got most of followgraphseeder almost done?

+84 -29
+10
.vscode/settings.json
··· 1 + { 2 + "editor.formatOnSave": true, 3 + "[ruby]": { 4 + "editor.defaultFormatter": "Shopify.ruby-lsp" 5 + }, 6 + "rubyLsp.formatter": "standard", 7 + "rubyLsp.linters": [ 8 + "standard" 9 + ] 10 + }
+1 -1
Rakefile
··· 30 30 BlueFactory::Server.run! 31 31 end 32 32 task :firehose do 33 - firehose = PostRouletteFeed::Firehose.new ENV.fetch("REDIS_URL") 33 + firehose = PostRouletteFeed::Firehose.new 34 34 firehose.run 35 35 end 36 36 end
+1 -1
bskypostroulettefeed.gemspec
··· 4 4 5 5 Gem::Specification.new do |spec| 6 6 spec.name = "bskypostroulettefeed" 7 - spec.version = Bskypostroulettefeed::VERSION 7 + spec.version = PostRouletteFeed::VERSION 8 8 spec.authors = ["ansxor"] 9 9 spec.email = ["me@ansxor.ca"] 10 10
+2 -2
db/migrate/001_create_sample_records.rb
··· 9 9 create_table(:users) do 10 10 primary_key :id 11 11 String :did, null: false 12 - backfill_status_enum :backfill_status, null: false, default: 'unwatched' 12 + backfill_status_enum :backfill_status, null: false, default: "unwatched" 13 13 Boolean :is_feed_subscriber, null: false, default: false 14 14 15 15 index :backfill_status 16 16 index :is_feed_subscriber 17 17 end 18 18 19 - create_join_table({follower_id: :users, followed_id: :users}, options: { name: 'user_relationships' }) do 19 + create_join_table({follower_id: :users, followed_id: :users}, options: {name: "user_relationships"}) do 20 20 index %i[follower_id followed_id], unique: true 21 21 index :followed_id 22 22 end
+1 -1
lib/bskypostroulettefeed.rb
··· 2 2 3 3 require_relative "bskypostroulettefeed/version" 4 4 5 - module Bskypostroulettefeed 5 + module PostRouletteFeed 6 6 class Error < StandardError; end 7 7 # Your code goes here... 8 8 end
+1 -1
lib/bskypostroulettefeed/version.rb
··· 1 1 # frozen_string_literal: true 2 2 3 - module Bskypostroulettefeed 3 + module PostRouletteFeed 4 4 VERSION = "0.1.0" 5 5 end
+2 -2
lib/feed/config.rb
··· 1 - require 'blue_factory' 1 + require "blue_factory" 2 2 require_relative "feed" 3 3 4 4 BlueFactory.set :publisher_did, ENV.fetch("FEED_PUBLISHER_DID") 5 5 BlueFactory.set :hostname, ENV.fetch("FEED_HOSTNAME") 6 6 7 - BlueFactory.add_feed "postroulette", PostRouletteFeed::Feed.new 7 + BlueFactory.add_feed "postroulette", PostRouletteFeed::Feed.new
+4 -8
lib/feed/feed.rb
··· 2 2 class Feed 3 3 def get_posts(params, context) 4 4 p context.user 5 - { posts: ["at://did:plc:brka7yc4gssxdquiwpii22pr/app.bsky.feed.post/3mckyk6xwhc2y"] } 5 + {posts: ["at://did:plc:brka7yc4gssxdquiwpii22pr/app.bsky.feed.post/3mckyk6xwhc2y"]} 6 6 end 7 7 8 - def display_name 9 - "Post Roulette" 10 - end 8 + def display_name = "Post Roulette" 11 9 12 - def description 13 - "This is my testing feed for now" 14 - end 10 + def description = "This is my testing feed for now" 15 11 end 16 - end 12 + end
+5 -5
lib/firehose/firehose.rb
··· 1 - require 'skyfall' 2 - require 'redis' 1 + require "skyfall" 2 + require "redis" 3 + require_relative "../shared/redis" 3 4 4 5 module PostRouletteFeed 6 + using PostRouletteFeedRedis 5 7 class Firehose 6 8 def initialize redis_url 7 9 @sky = Skyfall::Firehose.new("bsky.network", :subscribe_repos) ··· 10 12 11 13 def run 12 14 redis = Redis.new(url: @redis_url) 13 - 14 15 @sky.on_message do |msg| 15 - if redis.exists? "watching:#{msg.did}" 16 + if redis.watching? msg.did 16 17 p "watching", msg 17 18 end 18 19 end ··· 20 21 end 21 22 end 22 23 end 23 -
+19
lib/shared/redis.rb
··· 1 + require "redis" 2 + 3 + module PostRouletteFeedRedis 4 + refine Redis do 5 + def watching?(did) 6 + exists? watching_did_key(did) 7 + end 8 + 9 + def watch(did) 10 + set watching_did_key(did), true 11 + end 12 + 13 + private 14 + 15 + def watching_did_key(did) 16 + "watching:#{did}" 17 + end 18 + end 19 + end
+38 -8
lib/workers/followgraphseeder.rb
··· 1 - require 'sidekiq' 2 - require 'minisky' 1 + require "sidekiq" 2 + require "minisky" 3 + require "sequel" 4 + require_relative "../shared/redis" 3 5 4 6 module PostRouletteFeed 5 7 class FollowGraphSeeder 6 8 include Sidekiq::Worker 7 9 8 - def perform(did) 9 - atproto = Minisky.new('public.api.bsky.app', nil) 10 + using PostRouletteFeedRedis 11 + 12 + def initialize 13 + @redis_url = ENV.fetch("REDIS_URL") 14 + @database_url = ENV.fetch("DATABASE_URL") 15 + end 16 + 17 + def perform(follower_did) 18 + atproto = Minisky.new("public.api.bsky.app", nil) 10 19 11 20 dids = [] 12 21 cursor = nil 13 22 14 - while true do 15 - json = atproto.get_request('app.bsky.graph.getFollows', { 16 - actor: did, 23 + loop do 24 + json = atproto.get_request("app.bsky.graph.getFollows", { 25 + actor: follower_did, 17 26 limit: 100, 18 27 cursor: cursor 19 28 }) 29 + dids.concat(Array(json["follows"]).map { |follow| follow["did"] }) 20 30 cursor = json["cursor"] 21 31 break if cursor.nil? 22 32 end 33 + # signal to redis that we need to start watching these users 34 + redis = Redis.new(url: @redis_url) 35 + dids.each do |did| 36 + redis.watch did 37 + end 38 + ## create the users / connections in postgres 39 + db = Sequel.connect(@database_url) 40 + users_dataset = db[:users] 41 + user_relationships_dataset = db[:users_relationships] 42 + # create the user that is viewing the feed 43 + follower_id_rows = users_dataset.insert_conflict(target: :did, update: {}).returning(:id).insert(did: follower_did, is_feed_subscriber: true) 44 + # we add the chain in case the user was already created before, as may be the case 45 + # if someone followed them and used this feed in the past 46 + follower_id_rows.first&.fetch(:id) || users_dataset.where(did: follower_did).get(:id) 47 + # create all of the users that the user is following 48 + followed_users = dids.each { |did| {did: did} } 49 + followed_users_rows = users_dataset.insert_conflict(target: :did, update: {}).returning(:id).multi_insert(followed_users) 50 + # create the relationships that signify the follower is following all these users 51 + user_relationships_dataset.insert_conflict(target: :follower_id, update: {}).multi_insert(followed_users_rows.each { |row| }) 52 + p dids.length 23 53 p "done" 24 54 end 25 55 end 26 - end 56 + end