+2
app/at_uri.rb
+2
app/at_uri.rb
···
7
attr_reader :repo, :collection, :rkey
8
9
def initialize(uri)
10
+
uri = uri.to_s
11
raise InvalidURIError, "Invalid AT URI: #{uri}" if uri.include?(' ') || !uri.start_with?('at://')
12
13
parts = uri.split('/')
···
39
end
40
41
def AT_URI(uri)
42
+
return uri if uri.is_a?(AT_URI)
43
AT_URI.new(uri)
44
end
-18
app/importers/base_importer.rb
-18
app/importers/base_importer.rb
···
43
def import_items
44
raise NotImplementedError
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
end
+4
-9
app/importers/likes_importer.rb
+4
-9
app/importers/likes_importer.rb
···
1
require 'time'
2
-
3
-
require_relative '../at_uri'
4
require_relative 'base_importer'
5
6
class LikesImporter < BaseImporter
···
20
21
records.each do |record|
22
begin
23
-
like_rkey = AT_URI(record['uri']).rkey
24
-
next if @user.likes.where(rkey: like_rkey).exists?
25
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))
31
end
32
rescue StandardError => e
33
puts "Error in LikesImporter: #{record['uri']}: #{e}"
···
1
require 'time'
2
require_relative 'base_importer'
3
4
class LikesImporter < BaseImporter
···
18
19
records.each do |record|
20
begin
21
+
like = @user.likes.import_from_record(record['uri'], record['value'])
22
23
+
if like && like.pending? && @item_queue
24
+
@item_queue.push(like)
25
+
@report&.update(queue: { length: @item_queue.length })
26
end
27
rescue StandardError => e
28
puts "Error in LikesImporter: #{record['uri']}: #{e}"
+12
-39
app/importers/posts_importer.rb
+12
-39
app/importers/posts_importer.rb
···
1
require 'time'
2
-
3
-
require_relative '../at_uri'
4
require_relative 'base_importer'
5
6
class PostsImporter < BaseImporter
···
20
21
records.each do |record|
22
begin
23
-
if record['value']['embed'] && record['value']['embed']['record']
24
-
save_quote(record)
25
-
end
26
27
-
if record['value']['reply'] && record['value']['text'].include?('📌')
28
-
save_pin(record)
29
end
30
rescue StandardError => e
31
puts "Error in LikesImporter: #{record['uri']}: #{e}"
···
37
38
break if !cursor
39
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
end
73
end
74
end
···
1
require 'time'
2
require_relative 'base_importer'
3
4
class PostsImporter < BaseImporter
···
18
19
records.each do |record|
20
begin
21
+
quote = @user.quotes.import_from_record(record['uri'], record['value'])
22
+
pin = @user.pins.import_from_record(record['uri'], record['value'])
23
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 })
34
end
35
rescue StandardError => e
36
puts "Error in LikesImporter: #{record['uri']}: #{e}"
···
42
43
break if !cursor
44
break if @time_limit && records.any? { |x| Time.parse(x['value']['createdAt']) < @time_limit }
45
end
46
end
47
end
+4
-9
app/importers/reposts_importer.rb
+4
-9
app/importers/reposts_importer.rb
···
1
require 'time'
2
-
3
-
require_relative '../at_uri'
4
require_relative 'base_importer'
5
6
class RepostsImporter < BaseImporter
···
20
21
records.each do |record|
22
begin
23
-
repost_rkey = AT_URI(record['uri']).rkey
24
-
next if @user.reposts.where(rkey: repost_rkey).exists?
25
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))
31
end
32
rescue StandardError => e
33
puts "Error in RepostsImporter: #{record['uri']}: #{e}"
···
1
require 'time'
2
require_relative 'base_importer'
3
4
class RepostsImporter < BaseImporter
···
18
19
records.each do |record|
20
begin
21
+
repost = @user.reposts.import_from_record(record['uri'], record['value'])
22
23
+
if repost && repost.pending? && @item_queue
24
+
@item_queue.push(repost)
25
+
@report&.update(queue: { length: @item_queue.length })
26
end
27
rescue StandardError => e
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
require 'active_record'
2
3
require_relative 'post'
4
require_relative 'searchable'
5
require_relative 'user'
6
7
class Like < ActiveRecord::Base
8
include Searchable
9
10
validates_presence_of :time, :rkey
11
validates_length_of :rkey, is: 13
···
14
15
belongs_to :user, foreign_key: 'actor_id'
16
belongs_to :post, optional: true
17
end
···
1
require 'active_record'
2
+
require 'time'
3
4
+
require_relative '../at_uri'
5
require_relative 'post'
6
+
require_relative 'importable'
7
require_relative 'searchable'
8
require_relative 'user'
9
10
class Like < ActiveRecord::Base
11
include Searchable
12
+
include Importable
13
14
validates_presence_of :time, :rkey
15
validates_length_of :rkey, is: 13
···
18
19
belongs_to :user, foreign_key: 'actor_id'
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
29
end
+16
app/models/pin.rb
+16
app/models/pin.rb
···
1
require 'active_record'
2
3
require_relative 'post'
4
require_relative 'searchable'
5
require_relative 'user'
6
7
class Pin < ActiveRecord::Base
8
include Searchable
9
10
validates_presence_of :time, :rkey
11
validates_length_of :rkey, is: 13
···
15
16
belongs_to :user, foreign_key: 'actor_id'
17
belongs_to :post, optional: true
18
end
···
1
require 'active_record'
2
+
require 'time'
3
4
+
require_relative '../at_uri'
5
require_relative 'post'
6
require_relative 'searchable'
7
require_relative 'user'
8
9
class Pin < ActiveRecord::Base
10
include Searchable
11
+
include Importable
12
+
13
+
PIN_SIGN = '📌'
14
15
validates_presence_of :time, :rkey
16
validates_length_of :rkey, is: 13
···
20
21
belongs_to :user, foreign_key: 'actor_id'
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
34
end
+23
app/models/quote.rb
+23
app/models/quote.rb
···
1
require 'active_record'
2
3
require_relative 'post'
4
require_relative 'searchable'
5
require_relative 'user'
6
7
class Quote < ActiveRecord::Base
8
include Searchable
9
10
validates_presence_of :time, :rkey
11
validates_length_of :rkey, is: 13
···
15
16
belongs_to :user, foreign_key: 'actor_id'
17
belongs_to :post, optional: true
18
end
···
1
require 'active_record'
2
+
require 'time'
3
4
+
require_relative '../at_uri'
5
require_relative 'post'
6
require_relative 'searchable'
7
require_relative 'user'
8
9
class Quote < ActiveRecord::Base
10
include Searchable
11
+
include Importable
12
13
validates_presence_of :time, :rkey
14
validates_length_of :rkey, is: 13
···
18
19
belongs_to :user, foreign_key: 'actor_id'
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
41
end
+11
app/models/repost.rb
+11
app/models/repost.rb
···
1
require 'active_record'
2
3
require_relative 'post'
4
require_relative 'searchable'
5
require_relative 'user'
6
7
class Repost < ActiveRecord::Base
8
include Searchable
9
10
validates_presence_of :time, :rkey
11
validates_length_of :rkey, is: 13
···
14
15
belongs_to :user, foreign_key: 'actor_id'
16
belongs_to :post, optional: true
17
end
···
1
require 'active_record'
2
+
require 'time'
3
4
+
require_relative '../at_uri'
5
require_relative 'post'
6
require_relative 'searchable'
7
require_relative 'user'
8
9
class Repost < ActiveRecord::Base
10
include Searchable
11
+
include Importable
12
13
validates_presence_of :time, :rkey
14
validates_length_of :rkey, is: 13
···
17
18
belongs_to :user, foreign_key: 'actor_id'
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
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
validates_length_of :did, maximum: 260
13
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
has_many :imports
20
21
def all_pending_items
22
[:likes, :reposts, :quotes, :pins].map { |x| self.send(x).pending.to_a }.reduce(&:+)
···
12
validates_length_of :did, maximum: 260
13
14
has_many :posts
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
52
53
def all_pending_items
54
[:likes, :reposts, :quotes, :pins].map { |x| self.send(x).pending.to_a }.reduce(&:+)