# 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