Template of a custom feed generator service for the Bluesky network in Ruby
1require 'bundler/setup'
2
3require 'blue_factory/rake'
4require 'sinatra/activerecord'
5require 'sinatra/activerecord/rake'
6
7require_relative 'app/config'
8
9def get_feed
10 if ENV['KEY'].to_s == ''
11 puts "Please specify feed key as KEY=feedname (the part of the feed's at:// URI after the last slash)"
12 exit 1
13 end
14
15 feed_key = ENV['KEY']
16 feed = BlueFactory.get_feed(feed_key)
17
18 if feed.nil?
19 puts "No feed configured for key '#{feed_key}' - use `BlueFactory.add_feed '#{feed_key}', MyFeed.new`"
20 exit 1
21 end
22
23 feed
24end
25
26desc "Print posts in the feed, starting from the newest ones (limit = N)"
27task :print_feed do
28 feed = get_feed
29 limit = ENV['N'] ? ENV['N'].to_i : 100
30
31 posts = FeedPost.where(feed_id: feed.feed_id).joins(:post).order('feed_posts.time DESC').limit(limit).map(&:post)
32
33 Rainbow.enabled = true
34
35 # this fixes an error when piping a long output to less and then closing without reading it all
36 Signal.trap("SIGPIPE", "SYSTEM_DEFAULT")
37
38 posts.each do |s|
39 puts Rainbow(s.time).bold + ' * ' + Rainbow("https://bsky.app/profile/#{s.repo}/post/#{s.rkey}").darkgray
40 puts
41 puts feed.colored_text(s.text)
42 if s.record['embed']
43 json = JSON.generate(s.record['embed'])
44 colored = feed.colored_text(json)
45 puts colored unless colored == json
46 end
47 puts
48 puts "---"
49 puts
50 end
51end
52
53desc "Rescan all posts and rebuild the feed from scratch (DAYS = number of days)"
54task :rebuild_feed do
55 feed = get_feed
56
57 ActiveRecord::Base.transaction do
58 if ENV['ONLY_EXISTING']
59 posts = FeedPost.where(feed_id: feed.feed_id).joins(:post).map(&:post)
60 else
61 days = ENV['DAYS'] ? ENV['DAYS'].to_i : 7
62 posts = Post.order('time, id').where("time > DATETIME('now', '-#{days} day')")
63 end
64
65 if ENV['APPEND_ONLY']
66 current_post_ids = FeedPost.where(feed_id: feed.feed_id).pluck('post_id')
67 else
68 puts "Cleaning up feed..."
69 FeedPost.where(feed_id: feed.feed_id).delete_all
70 current_post_ids = []
71 end
72
73 total = posts.count
74
75 offset = 0
76 page = 100000
77
78 while offset < total
79 batch = posts.is_a?(Array) ? posts[offset...(offset+page)] : posts.limit(page).offset(offset).to_a
80
81 batch.each_with_index do |post, i|
82 print "Processing posts... [#{offset + i + 1}/#{total}]\r"
83 $stdout.flush
84
85 if !current_post_ids.include?(post.id) && feed.post_matches?(post)
86 FeedPost.create!(feed_id: feed.feed_id, post: post, time: post.time)
87 end
88 end
89
90 offset += page
91 end
92 end
93
94 puts "Processing posts... Done." + " " * 30
95end