summaryrefslogtreecommitdiff
path: root/helpers.rb
blob: 0db6566eed06367625a6a9e43f3c01f7ecc724b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# frozen_string_literal: true

require 'English'

helpers do
  # add date and id, save
  def save_activity(activity, box)
    date = Time.now.utc.iso8601
    activity['published'] = date if box == OUTBOX
    basename = "#{activity['published']}_#{mention(activity['actor'])}.json"
    activity_rel_path = File.join(activity['type'].downcase, basename)
    activity_path = File.join(box[:dir], activity_rel_path)
    if box == OUTBOX
      activity['id'] = File.join(box[:url], activity_rel_path)
      activity['object']['published'] = date
    end
    # save object
    save_object activity['object'], box if activity['object'] && activity['object']['type'] && !activity['object']['id']
    # save activity
    FileUtils.mkdir_p File.dirname(activity_path)
    File.open(activity_path, 'w+') { |f| f.puts activity.to_json }
    activity
  end

  def save_object(object, box)
    object = fetch(object) if object.is_a? String and object.match(/^http/)
    object['@context'] = 'https://www.w3.org/ns/activitystreams'
    basename = "#{object['published']}_#{mention(object['attributedTo'])}.json"
    object_rel_path = File.join 'object', object['type'].downcase, basename
    object['id'] = File.join box[:url], object_rel_path if box == OUTBOX
    object_path = File.join box[:dir], object_rel_path
    FileUtils.mkdir_p File.dirname(object_path)
    File.open(object_path, 'w+') { |f| f.puts object.to_json }
    object
  end

  def update_collection(path, objects, delete = false)
    objects = [objects] unless objects.is_a? Array
    File.open(path, 'r+') do |f|
      f.flock(File::LOCK_EX)
      json = f.read
      collection = JSON.parse(json)
      objects.each do |object|
        id = object['id'] || object
        if delete
          collection['orderedItems'].delete_if { |o| o['id'] == id or o == id }
        else
          ids = collection['orderedItems'].collect { |i| i['id'] }
          collection['orderedItems'] << object unless ids.include?(id) or collection['orderedItems'].include?(id)
        end
      end
      collection['totalItems'] = collection['orderedItems'].size
      f.rewind
      f.puts collection.to_json
      f.truncate(f.pos)
    end
  end

  def fetch(url, accept = 'application/activity+json')
    uri = URI(url)
    httpdate = Time.now.utc.httpdate
    keypair = OpenSSL::PKey::RSA.new(File.read('private.pem'))
    string = "(request-target): get #{uri.request_uri}\nhost: #{uri.host}\ndate: #{httpdate}"
    signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), string))
    signed_header = "keyId=\"#{ACTOR}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date\",signature=\"#{signature}\""
    response = curl(
      "-H 'Accept: #{accept}' -H 'Host: #{uri.host}' -H 'Date: #{httpdate}' -H 'Signature: #{signed_header}' ", url
    )
    response ? JSON.parse(response) : nil
  end

  def curl(ext, url)
    p url
    # p "/run/current-system/sw/bin/curl --fail-with-body -sSL #{ext} #{url}"
    response = `/run/current-system/sw/bin/curl --fail-with-body -sSL #{ext} #{url}`
    if $CHILD_STATUS.success?
      response
    else
      p response
      nil
    end
  end

  def mention(actor)
    person = people.select { |p| p[1] == actor }
    if person.empty?
      a = fetch(actor)
      return nil unless a

      mention = "#{a['preferredUsername']}@#{URI(actor).host}"
      cache mention, actor, a
      mention
    else
      person[0][0]
    end
  end

  def actor(mention)
    mention = mention.sub(/^@/, '').chomp
    actors = people.select { |p| p[0] == mention }
    if actors.empty?
      _, server = mention.split('@')
      a = fetch("https://#{server}/.well-known/webfinger?resource=acct:#{mention}", 'application/jrd+json')
      return nil unless a

      actor = a['links'].select do |l|
        l['rel'] == 'self'
      end[0]['href']
      cache mention, actor, a
      actor
    else
      actors[0][1]
    end
  end

  def people
    File.read('public/people.tsv').split("\n").collect { |l| l.chomp.split("\t") }
  end

  def cache(mention, actor, a)
    sharedInbox = a['endpoints']['sharedInbox'] if a['endpoints'] && a['endpoints']['sharedInbox']
    File.open('public/people.tsv', 'a') { |f| f.puts "#{mention}\t#{actor}\t#{sharedInbox}" }
  end
end