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
125
|
# 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 unless activity['object'].is_a? String
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/)
return unless object
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
response = `/run/current-system/sw/bin/curl -H 'Content-Type: #{CONTENT_TYPE}' -H 'Accept: #{CONTENT_TYPE}' --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
|