+2
app/at_uri.rb
+2
app/at_uri.rb
···
7
7
attr_reader :repo, :collection, :rkey
8
8
9
9
def initialize(uri)
10
+
uri = uri.to_s
10
11
raise InvalidURIError, "Invalid AT URI: #{uri}" if uri.include?(' ') || !uri.start_with?('at://')
11
12
12
13
parts = uri.split('/')
···
38
39
end
39
40
40
41
def AT_URI(uri)
42
+
return uri if uri.is_a?(AT_URI)
41
43
AT_URI.new(uri)
42
44
end
-18
app/importers/base_importer.rb
-18
app/importers/base_importer.rb
···
43
43
def import_items
44
44
raise NotImplementedError
45
45
end
46
-
47
-
def create_item_for_post(uri)
48
-
post_uri = AT_URI(uri)
49
-
return unless post_uri.is_post?
50
-
51
-
post = Post.find_by_at_uri(post_uri)
52
-
53
-
if post
54
-
yield({ post: post })
55
-
else
56
-
item_stub = yield({ post_uri: post_uri })
57
-
58
-
if @item_queue
59
-
@item_queue.push(item_stub)
60
-
@report&.update(queue: { length: @item_queue.length })
61
-
end
62
-
end
63
-
end
64
46
end
+4
-9
app/importers/likes_importer.rb
+4
-9
app/importers/likes_importer.rb
···
1
1
require 'time'
2
-
3
-
require_relative '../at_uri'
4
2
require_relative 'base_importer'
5
3
6
4
class LikesImporter < BaseImporter
···
20
18
21
19
records.each do |record|
22
20
begin
23
-
like_rkey = AT_URI(record['uri']).rkey
24
-
next if @user.likes.where(rkey: like_rkey).exists?
21
+
like = @user.likes.import_from_record(record['uri'], record['value'])
25
22
26
-
like_time = Time.parse(record['value']['createdAt'])
27
-
post_uri = record['value']['subject']['uri']
28
-
29
-
create_item_for_post(post_uri) do |args|
30
-
@user.likes.create!(args.merge(rkey: like_rkey, time: like_time))
23
+
if like && like.pending? && @item_queue
24
+
@item_queue.push(like)
25
+
@report&.update(queue: { length: @item_queue.length })
31
26
end
32
27
rescue StandardError => e
33
28
puts "Error in LikesImporter: #{record['uri']}: #{e}"
+12
-39
app/importers/posts_importer.rb
+12
-39
app/importers/posts_importer.rb
···
1
1
require 'time'
2
-
3
-
require_relative '../at_uri'
4
2
require_relative 'base_importer'
5
3
6
4
class PostsImporter < BaseImporter
···
20
18
21
19
records.each do |record|
22
20
begin
23
-
if record['value']['embed'] && record['value']['embed']['record']
24
-
save_quote(record)
25
-
end
21
+
quote = @user.quotes.import_from_record(record['uri'], record['value'])
22
+
pin = @user.pins.import_from_record(record['uri'], record['value'])
26
23
27
-
if record['value']['reply'] && record['value']['text'].include?('📌')
28
-
save_pin(record)
24
+
if @item_queue
25
+
if quote && quote.pending?
26
+
@item_queue.push(quote)
27
+
end
28
+
29
+
if pin && pin.pending?
30
+
@item_queue.push(pin)
31
+
end
32
+
33
+
@report&.update(queue: { length: @item_queue.length })
29
34
end
30
35
rescue StandardError => e
31
36
puts "Error in LikesImporter: #{record['uri']}: #{e}"
···
37
42
38
43
break if !cursor
39
44
break if @time_limit && records.any? { |x| Time.parse(x['value']['createdAt']) < @time_limit }
40
-
end
41
-
end
42
-
43
-
def save_quote(record)
44
-
post_rkey = AT_URI(record['uri']).rkey
45
-
return if @user.quotes.where(rkey: post_rkey).exists?
46
-
47
-
post_time = Time.parse(record['value']['createdAt'])
48
-
49
-
quoted_post_uri = case record['value']['embed']['$type']
50
-
when 'app.bsky.embed.record'
51
-
record['value']['embed']['record']['uri']
52
-
when 'app.bsky.embed.recordWithMedia'
53
-
record['value']['embed']['record']['record']['uri']
54
-
else
55
-
return
56
-
end
57
-
58
-
create_item_for_post(quoted_post_uri) do |args|
59
-
@user.quotes.create!(args.merge(rkey: post_rkey, time: post_time, quote_text: record['value']['text']))
60
-
end
61
-
end
62
-
63
-
def save_pin(record)
64
-
post_rkey = AT_URI(record['uri']).rkey
65
-
return if @user.pins.where(rkey: post_rkey).exists?
66
-
67
-
post_time = Time.parse(record['value']['createdAt'])
68
-
parent_post_uri = record['value']['reply']['parent']['uri']
69
-
70
-
create_item_for_post(parent_post_uri) do |args|
71
-
@user.pins.create!(args.merge(rkey: post_rkey, time: post_time, pin_text: record['value']['text']))
72
45
end
73
46
end
74
47
end
+4
-9
app/importers/reposts_importer.rb
+4
-9
app/importers/reposts_importer.rb
···
1
1
require 'time'
2
-
3
-
require_relative '../at_uri'
4
2
require_relative 'base_importer'
5
3
6
4
class RepostsImporter < BaseImporter
···
20
18
21
19
records.each do |record|
22
20
begin
23
-
repost_rkey = AT_URI(record['uri']).rkey
24
-
next if @user.reposts.where(rkey: repost_rkey).exists?
21
+
repost = @user.reposts.import_from_record(record['uri'], record['value'])
25
22
26
-
repost_time = Time.parse(record['value']['createdAt'])
27
-
post_uri = record['value']['subject']['uri']
28
-
29
-
create_item_for_post(post_uri) do |args|
30
-
@user.reposts.create!(args.merge(rkey: repost_rkey, time: repost_time))
23
+
if repost && repost.pending? && @item_queue
24
+
@item_queue.push(repost)
25
+
@report&.update(queue: { length: @item_queue.length })
31
26
end
32
27
rescue StandardError => e
33
28
puts "Error in RepostsImporter: #{record['uri']}: #{e}"
+29
app/models/importable.rb
+29
app/models/importable.rb
···
1
+
require 'active_support/concern'
2
+
3
+
require_relative '../at_uri'
4
+
require_relative 'post'
5
+
6
+
module Importable
7
+
extend ActiveSupport::Concern
8
+
9
+
included do
10
+
scope :pending, -> { where(post: nil) }
11
+
12
+
def pending?
13
+
post_uri != nil
14
+
end
15
+
16
+
def import_item!
17
+
post_uri = AT_URI(self.post_uri)
18
+
return nil if !post_uri.is_post?
19
+
20
+
if post = Post.find_by_at_uri(post_uri)
21
+
self.post = post
22
+
self.post_uri = nil
23
+
end
24
+
25
+
self.save!
26
+
self
27
+
end
28
+
end
29
+
end
+12
app/models/like.rb
+12
app/models/like.rb
···
1
1
require 'active_record'
2
+
require 'time'
2
3
4
+
require_relative '../at_uri'
3
5
require_relative 'post'
6
+
require_relative 'importable'
4
7
require_relative 'searchable'
5
8
require_relative 'user'
6
9
7
10
class Like < ActiveRecord::Base
8
11
include Searchable
12
+
include Importable
9
13
10
14
validates_presence_of :time, :rkey
11
15
validates_length_of :rkey, is: 13
···
14
18
15
19
belongs_to :user, foreign_key: 'actor_id'
16
20
belongs_to :post, optional: true
21
+
22
+
def self.new_from_record(uri, record)
23
+
self.new(
24
+
rkey: AT_URI(uri).rkey,
25
+
time: Time.parse(record['createdAt']),
26
+
post_uri: record['subject']['uri']
27
+
)
28
+
end
17
29
end
+16
app/models/pin.rb
+16
app/models/pin.rb
···
1
1
require 'active_record'
2
+
require 'time'
2
3
4
+
require_relative '../at_uri'
3
5
require_relative 'post'
4
6
require_relative 'searchable'
5
7
require_relative 'user'
6
8
7
9
class Pin < ActiveRecord::Base
8
10
include Searchable
11
+
include Importable
12
+
13
+
PIN_SIGN = '📌'
9
14
10
15
validates_presence_of :time, :rkey
11
16
validates_length_of :rkey, is: 13
···
15
20
16
21
belongs_to :user, foreign_key: 'actor_id'
17
22
belongs_to :post, optional: true
23
+
24
+
def self.new_from_record(uri, record)
25
+
return nil unless record['reply'] && record['text'].include?(PIN_SIGN)
26
+
27
+
self.new(
28
+
rkey: AT_URI(uri).rkey,
29
+
time: Time.parse(record['createdAt']),
30
+
post_uri: record['reply']['parent']['uri'],
31
+
pin_text: record['text']
32
+
)
33
+
end
18
34
end
+23
app/models/quote.rb
+23
app/models/quote.rb
···
1
1
require 'active_record'
2
+
require 'time'
2
3
4
+
require_relative '../at_uri'
3
5
require_relative 'post'
4
6
require_relative 'searchable'
5
7
require_relative 'user'
6
8
7
9
class Quote < ActiveRecord::Base
8
10
include Searchable
11
+
include Importable
9
12
10
13
validates_presence_of :time, :rkey
11
14
validates_length_of :rkey, is: 13
···
15
18
16
19
belongs_to :user, foreign_key: 'actor_id'
17
20
belongs_to :post, optional: true
21
+
22
+
def self.new_from_record(uri, record)
23
+
return nil unless record['embed']
24
+
25
+
quoted_post_uri = case record['embed']['$type']
26
+
when 'app.bsky.embed.record'
27
+
record['embed']['record']['uri']
28
+
when 'app.bsky.embed.recordWithMedia'
29
+
record['embed']['record']['record']['uri']
30
+
else
31
+
return nil
32
+
end
33
+
34
+
self.new(
35
+
rkey: AT_URI(uri).rkey,
36
+
time: Time.parse(record['createdAt']),
37
+
post_uri: quoted_post_uri,
38
+
quote_text: record['text']
39
+
)
40
+
end
18
41
end
+11
app/models/repost.rb
+11
app/models/repost.rb
···
1
1
require 'active_record'
2
+
require 'time'
2
3
4
+
require_relative '../at_uri'
3
5
require_relative 'post'
4
6
require_relative 'searchable'
5
7
require_relative 'user'
6
8
7
9
class Repost < ActiveRecord::Base
8
10
include Searchable
11
+
include Importable
9
12
10
13
validates_presence_of :time, :rkey
11
14
validates_length_of :rkey, is: 13
···
14
17
15
18
belongs_to :user, foreign_key: 'actor_id'
16
19
belongs_to :post, optional: true
20
+
21
+
def self.new_from_record(uri, record)
22
+
self.new(
23
+
rkey: AT_URI(uri).rkey,
24
+
time: Time.parse(record['createdAt']),
25
+
post_uri: record['subject']['uri']
26
+
)
27
+
end
17
28
end
-2
app/models/searchable.rb
-2
app/models/searchable.rb
+36
-4
app/models/user.rb
+36
-4
app/models/user.rb
···
12
12
validates_length_of :did, maximum: 260
13
13
14
14
has_many :posts
15
-
has_many :likes, foreign_key: 'actor_id'
16
-
has_many :reposts, foreign_key: 'actor_id'
17
-
has_many :quotes, foreign_key: 'actor_id'
18
-
has_many :pins, foreign_key: 'actor_id'
19
15
has_many :imports
16
+
17
+
has_many :likes, foreign_key: 'actor_id' do
18
+
def import_from_record(like_uri, record)
19
+
like = self.new_from_record(like_uri, record)
20
+
return nil if like.nil? || self.where(rkey: like.rkey).exists?
21
+
22
+
like.import_item!
23
+
end
24
+
end
25
+
26
+
has_many :reposts, foreign_key: 'actor_id' do
27
+
def import_from_record(repost_uri, record)
28
+
repost = self.new_from_record(repost_uri, record)
29
+
return nil if repost.nil? || self.where(rkey: repost.rkey).exists?
30
+
31
+
repost.import_item!
32
+
end
33
+
end
34
+
35
+
has_many :quotes, foreign_key: 'actor_id' do
36
+
def import_from_record(post_uri, record)
37
+
quote = self.new_from_record(post_uri, record)
38
+
return nil if quote.nil? || self.where(rkey: quote.rkey).exists?
39
+
40
+
quote.import_item!
41
+
end
42
+
end
43
+
44
+
has_many :pins, foreign_key: 'actor_id' do
45
+
def import_from_record(post_uri, record)
46
+
pin = self.new_from_record(post_uri, record)
47
+
return nil if pin.nil? || self.where(rkey: pin.rkey).exists?
48
+
49
+
pin.import_item!
50
+
end
51
+
end
20
52
21
53
def all_pending_items
22
54
[:likes, :reposts, :quotes, :pins].map { |x| self.send(x).pending.to_a }.reduce(&:+)