diff options
author | pdp8 <pdp8@pdp8.info> | 2023-06-19 14:18:16 +0200 |
---|---|---|
committer | pdp8 <pdp8@pdp8.info> | 2023-06-19 14:18:16 +0200 |
commit | c719909e458d9896ec777b25fe8620df3eb9ff7a (patch) | |
tree | 56cf597e3e2525c6213dbedda3b4a4285fc45205 /activitypub.rb | |
parent | a9aa0fcd661a359fd17061704c5cd55770518521 (diff) |
redirect html to pdp8.info, Net:HTTP substituted with curl
Diffstat (limited to 'activitypub.rb')
-rw-r--r-- | activitypub.rb | 394 |
1 files changed, 207 insertions, 187 deletions
diff --git a/activitypub.rb b/activitypub.rb index e6ab6da..f51ea3b 100644 --- a/activitypub.rb +++ b/activitypub.rb @@ -1,15 +1,20 @@ # TODO -# read actor from people.csv -# thread expansion -# follow request confirmation -# boost +# server +# fix failed follows # federation -# client post media +# boost +# thread expansion +# include own posts in threads +# remaining activities # test with pleroma etc + +# client +# post form +# parse hashtags in post +# client post media require 'uri' require 'base64' require 'digest/sha2' -require 'net/http' require 'sinatra' USER = "pdp8" @@ -26,7 +31,8 @@ set :session_secret, File.read(".secret").chomp set :default_content_type, 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' set :port, 9292 -post "/inbox" do # server-server +# server-server +post "/inbox" do verify! request.body.rewind # in case someone already read it body = request.body.read @@ -68,7 +74,13 @@ post "/inbox" do # server-server when "Follow" File.open(File.join("public","following",mention(o['object'])+".json"),"w+"){|f| f.puts o.to_json} end + #when "Announce" + #when "Move" + #when "Add" + #when "Remove" + #when "Like" + #when "Block" else p "Unknown action: #{action['type']}" @@ -76,7 +88,8 @@ post "/inbox" do # server-server end end -post "/outbox" do # client-server +# client-server +post "/outbox" do protected! request.body.rewind # in case someone already read it body = request.body.read @@ -173,6 +186,7 @@ post "/login" do redirect to("/") end +# public get "/.well-known/webfinger" do if request["resource"] == "acct:#{ACCOUNT}" send_file "./public/webfinger", :type => "application/jrd+json" @@ -181,14 +195,12 @@ get "/.well-known/webfinger" do end end -get "/archive", :provides => 'html' do - protected! - dir_html "archive" +get "/pdp8", :provides => 'html' do + redirect 'https://pdp8.info' end -get "/", :provides => 'html' do - protected! - dir_html "inbox" +get "/pdp8" do + send_file "pdp8.json" end ["/outbox","/following","/followers"].each do |path| @@ -197,6 +209,17 @@ end end end +# private +get "/archive", :provides => 'html' do + protected! + dir_html "archive" +end + +get "/", :provides => 'html' do + protected! + dir_html "inbox" +end + helpers do def protected! @@ -218,7 +241,6 @@ helpers do signature = Base64.decode64(signature_params['signature']) actor = fetch key_id - halt 400 unless actor key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem']) comparison = headers.split(' ').map do |signed_params_name| @@ -238,202 +260,200 @@ helpers do halt 400 end end -end -def dir_html dir - nr = 0 - items = Dir[File.join(dir, '*.json')].sort.collect do |file| - item = JSON.parse(File.read(file)) - mention = mention(item['attributedTo']) - following_path = File.join('public', 'following', mention + '.json') - File.exists?(following_path) ? follow = 'unfollow' : follow = 'follow' - nr += 1 - { :id => item['id'], - :nr => nr, - :parent => item['inReplyTo'], - :file => file, - :actor_url => item['attributedTo'], - :mention => mention, - :follow => follow, - :content => item['content'], - :attachment => item['attachment'], - :replies => [] - } - end.compact - items.last[:nr] = items.last[:nr] - 2 - threads = [] - items.each do |i| - if i[:parent].nil? or items.select{|it| it[:id] == i[:parent] }.empty? - threads << i - else - items.select{|it| it[:id] == i[:parent] }.each{|it| it[:replies] << i} + def dir_html dir + nr = 0 + items = Dir[File.join(dir, '*.json')].sort.collect do |file| + item = JSON.parse(File.read(file)) + mention = mention(item['attributedTo']) + following_path = File.join('public', 'following', mention + '.json') + File.exists?(following_path) ? follow = 'unfollow' : follow = 'follow' + nr += 1 + { :id => item['id'], + :nr => nr, + :parent => item['inReplyTo'], + :file => file, + :actor_url => item['attributedTo'], + :mention => mention, + :follow => follow, + :content => item['content'], + :attachment => item['attachment'], + :replies => [] + } + end.compact + items.last[:nr] = items.last[:nr] - 2 + threads = [] + items.each do |i| + if i[:parent].nil? or items.select{|it| it[:id] == i[:parent] }.empty? + threads << i + else + items.select{|it| it[:id] == i[:parent] }.each{|it| it[:replies] << i} + end end - end - html="<!DOCTYPE html> - <html lang='en'> - <head> - <link rel='stylesheet' type='text/css' href='/style.css'> - </head> - <body> - <h1>#{dir}" + html="<!DOCTYPE html> + <html lang='en'> + <head> + <link rel='stylesheet' type='text/css' href='/style.css'> + </head> + <body> + <h1>#{dir}" - if dir == "inbox" - html << "<form action='archive' method='get'> - <button>archive</button> - </form>" - elsif dir == "archive" - html << "<form action='/' method='get'> - <button>inbox</button> - </form>" - end - html << " </h1> " - threads.each do |item| - html << item_html(item,dir) + if dir == "inbox" + html << "<form action='archive' method='get'> + <button>archive</button> + </form>" + elsif dir == "archive" + html << "<form action='/' method='get'> + <button>inbox</button> + </form>" + end + html << " </h1> " + threads.each do |item| + html << item_html(item,dir) + end + html << " + <form action='delete_all' method='post'> + <button>Delete all</button> + </form> + </body> + </html>" if dir == "inbox" + html end - html << " - <form action='delete_all' method='post'> - <button>Delete all</button> - </form> - </body> - </html>" if dir == "inbox" - html -end -def item_html item, dir, indent=2 - html = " - <div style='margin-left:#{indent}em' id='#{item[:nr]}'> - <b><a href='#{ item[:actor_url] }', target='_blank'>#{ item[:mention] }</a></b> - <form action='#{ File.join item[:follow], item[:mention] }' method='post'> - <button>#{ item[:follow].capitalize }</button> - </form> - - " - case dir - when "inbox" - html << " - <form action='/archive' method='post'> - <input type='hidden' name='file' id='file' value='#{item[:file]}' /> - <input type='hidden' name='anchor' id='anchor' value='#{item[:nr]}' /> - <button>Archive</button> + def item_html item, dir, indent=2 + html = " + <div style='margin-left:#{indent}em' id='#{item[:nr]}'> + <b><a href='#{ item[:actor_url] }', target='_blank'>#{ item[:mention] }</a></b> + <form action='#{ File.join item[:follow], item[:mention] }' method='post'> + <button>#{ item[:follow].capitalize }</button> </form> + " - when "archive" - html << " - <form action='/delete' method='post'> - <input type='hidden' name='file' id='file' value='#{item[:file]}' /> - <input type='hidden' name='anchor' id='anchor' value='#{item[:nr]}' /> - <button>Delete</button> - </form> - " - end + case dir + when "inbox" + html << " + <form action='/archive' method='post'> + <input type='hidden' name='file' id='file' value='#{item[:file]}' /> + <input type='hidden' name='anchor' id='anchor' value='#{item[:nr]}' /> + <button>Archive</button> + </form> + " + when "archive" + html << " + <form action='/delete' method='post'> + <input type='hidden' name='file' id='file' value='#{item[:file]}' /> + <input type='hidden' name='anchor' id='anchor' value='#{item[:nr]}' /> + <button>Delete</button> + </form> + " + end - html << " - #{ item[:content].gsub('<br />','') }" - if item[:attachment] - item[:attachment].each do |att| - html << "<br>" - case att['mediaType'] - when /audio/ - html << "<audio controls=''><source src='#{ att['url'] }' type='#{ att['mediaType'] }'></audio>" - when /image/ - html << "<a href='#{ att['url'] }'><img src='#{ att['url'] }'></a>" - when /video/ - html << "<video controls=''><source src='#{ att['url'] }' type='#{ att['mediaType'] }'></video>" - else - html << "#{ att }<br> - <a href='#{ att['url'] }'>#{ att['url'] }</a>" - end + html << " + #{ item[:content].gsub('<br />','') }" + if item[:attachment] + item[:attachment].each do |att| + html << "<br>" + case att['mediaType'] + when /audio/ + html << "<audio controls=''><source src='#{ att['url'] }' type='#{ att['mediaType'] }'></audio>" + when /image/ + html << "<a href='#{ att['url'] }'><img src='#{ att['url'] }'></a>" + when /video/ + html << "<video controls=''><source src='#{ att['url'] }' type='#{ att['mediaType'] }'></video>" + else + html << "#{ att }<br> + <a href='#{ att['url'] }'>#{ att['url'] }</a>" + end + end end - end - html << " - </div>" - item[:replies].each do |r| - html << item_html(r,dir,indent+4) + html << " + </div>" + item[:replies].each do |r| + html << item_html(r,dir,indent+4) + end + + html end - html -end + def delete object + Dir["inbox/*.json"].each do |doc| + FileUtils.rm doc if JSON.parse(File.read(doc))["id"] == object["id"] + end + end -def delete object - Dir["inbox/*.json"].each do |doc| - FileUtils.rm doc if JSON.parse(File.read(doc))["id"] == object["id"] + def create object + unless object['type'] == 'Person' + doc = File.join("inbox", "#{Time.now.strftime('%Y-%m-%dT%H:%M:%S.%N')}.json") + File.open(doc, "w+") { |f| f.puts object.to_json } + end end -end -def create object - unless object['type'] == 'Person' - doc = File.join("inbox", "#{Time.now.strftime('%Y-%m-%dT%H:%M:%S.%N')}.json") - File.open(doc, "w+") { |f| f.puts object.to_json } + def people + File.read('inbox/people.tsv').split("\n").collect {|l| l.chomp.split("\t")} end -end -def mention actor - people = File.read('inbox/people.tsv').split("\n").collect {|l| l.chomp.split("\t")} - person = people.select{|p| p[1] == actor} - if person.empty? - mention = "#{fetch(actor)["preferredUsername"]}@#{URI(actor).host}" - File.open('inbox/people.tsv','a'){|f| f.puts "#{mention}\t#{actor}"} - mention - else - person[0][0] + def mention actor + person = people.select{|p| p[1] == actor} + if person.empty? + mention = "#{fetch(actor)["preferredUsername"]}@#{URI(actor).host}" + File.open('inbox/people.tsv','a'){|f| f.puts "#{mention}\t#{actor}"} + mention + else + person[0][0] + end end -end -def actor mention - mention = mention.sub(/^@/, '').chomp - user, server = mention.split("@") - fetch("https://#{server}/.well-known/webfinger?resource=acct:#{mention}", - "application/jrd+json")["links"].select { |l| - l["rel"] == "self" - }[0]["href"] -end + def actor mention + mention = mention.sub(/^@/, '').chomp + actors = people.select{|p| p[0] == mention} + if actors.empty? + user, server = mention.split("@") + actor = fetch("https://#{server}/.well-known/webfinger?resource=acct:#{mention}", + "application/jrd+json")["links"].select { |l| + l["rel"] == "self" + }[0]["href"] + File.open('inbox/people.tsv','a'){|f| f.puts "#{mention}\t#{actor}"} + actor + else + actors[0][0] + end + end -def fetch url, accept = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', limit = 10 - uri = URI(url) - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true - header = { 'Accept' => accept } - request = Net::HTTP::Get.new(uri.request_uri, header) - response = http.request(request) - case response - when Net::HTTPSuccess - return JSON.parse(response.body) - when Net::HTTPRedirection - fetch response['location'], accept, limit-1 - else - puts "#{url}: #{response.code}, #{response.message}" - #halt 400 + def fetch url, accept = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' + response = `/run/current-system/sw/bin/curl --fail-with-body -sSL -H 'Accept: #{accept}' #{url}` + unless $?.success? + p url + halt 400 + end + JSON.parse(response) end -end -def ordered_collection dir - posts = Dir[File.join("public",dir, "*.json")].collect { |f| JSON.parse(File.read f) }.sort_by { |o| o["published"] } - { - "@context" => "https://www.w3.org/ns/activitystreams", - "summary" => "#{USER} #{dir}", - "type" => "OrderedCollection", - "totalItems" => posts.size, - "orderedItems" => posts, - } -end + def ordered_collection dir + posts = Dir[File.join("public",dir, "*.json")].collect { |f| JSON.parse(File.read f) }.sort_by { |o| o["published"] } + { + "@context" => "https://www.w3.org/ns/activitystreams", + "summary" => "#{USER} #{dir}", + "type" => "OrderedCollection", + "totalItems" => posts.size, + "orderedItems" => posts, + } + end -def send_signed object, url - # https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb - keypair = OpenSSL::PKey::RSA.new(File.read('private.pem')) - date = Time.now.utc.httpdate - uri = URI.parse(url) + def send_signed object, url + # https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb + keypair = OpenSSL::PKey::RSA.new(File.read('private.pem')) + date = Time.now.utc.httpdate + uri = URI.parse(url) - sha256 = OpenSSL::Digest::SHA256.new - body = object.to_json - digest = "SHA-256=" + sha256.base64digest(body) + sha256 = OpenSSL::Digest::SHA256.new + body = object.to_json + digest = "SHA-256=" + sha256.base64digest(body) - signed_string = "(request-target): post #{inbox uri}\nhost: #{uri.host}\ndate: #{date}\ndigest: #{digest}\ncontent-type: application/activity+json" - signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) - signed_header = 'keyId="' + ACTOR + '#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="' + signature + '"' + signed_string = "(request-target): post #{fetch(uri)["inbox"]}\nhost: #{uri.host}\ndate: #{date}\ndigest: #{digest}\ncontent-type: application/activity+json" + signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) + signed_header = 'keyId="' + ACTOR + '#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="' + signature + '"' - puts `/run/current-system/sw/bin/curl -i -X POST -H 'Content-Type: application/activity+json' -H 'Host: #{uri.host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}' #{fetch(url)['inbox']}` -end + puts `/run/current-system/sw/bin/curl -i -X POST -H 'Content-Type: application/activity+json' -H 'Host: #{uri.host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}' #{fetch(url)['inbox']}` + end -def inbox uri - URI(fetch(uri)["inbox"]).request_uri end |