# TODO # boost # archive # threads # federation # client post media # test with pleroma etc =begin require 'json' require 'uri' require 'base64' require 'securerandom' require 'fileutils' require 'digest/sha2' require 'nokogiri' =end require 'net/http' require 'sinatra' USER = "pdp8" WWW_DOMAIN = "pdp8.info" WWW_URL = "https://#{WWW_DOMAIN}" SOCIAL_DOMAIN = "social.#{WWW_DOMAIN}" ACCOUNT = "#{USER}@#{SOCIAL_DOMAIN}" SOCIAL_URL = "https://#{SOCIAL_DOMAIN}" ACTOR = File.join(SOCIAL_URL, USER) use Rack::Reloader set :default_content_type, 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' set :port, 9292 post "/inbox" do request.body.rewind # in case someone already read it body = request.body.read object = JSON.parse body case object["type"] when "Create" File.open(File.join("inbox", "#{object["published"]}-#{mention object["actor"]}.json"), "w+") { |f| f.puts object["object"].to_json } end end get "/.well-known/webfinger" do if request["resource"] == "acct:#{ACCOUNT}" send_file "./webfinger", :type => "application/jrd+json" else 404 end end get "/inbox", :provides => 'html' do template = "
<% Dir['./inbox/*'].sort.each do |file| %> <% item = JSON.parse(File.read(file)) %> <%= mention item['actor'] %> <%= item['published'].sub('T', ' ') %><%= item['content'] %>
<% if item['attachment']
item['attachment'].each do |att|
case att['mediaType']
when /audio/ %>
<% when /image/ %>
'>'>
<% when /video/ %>
<% else %>
<%= att %>
'><%= att['url'] %>
<% end %>
<% end %>
<% end %>
#{o["object"]["content"]}
"
if o["object"]["attachment"]
o["object"]["attachment"].each do |att|
case att["mediaType"]
when /audio/
html<< "\n
"
when /image/
html << "\n
"
when /video/
html<< "\n
"
else
html<< att + "
"
html << "\n#{att["url"]}"
end
end
end
end
html << "\n\t\n"
html
end
=end
=begin
def parse input
date = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
# TODO media attachments, hashtags
note = {
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => File.join(SOCIAL_URL, "note", SecureRandom.uuid + ".json"),
"type" => "Note",
"attributedTo" => ACTOR,
"published" => date,
"content" => "",
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
}
create = {
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => File.join(SOCIAL_URL, "create", SecureRandom.uuid + ".json"),
"type" => "Create",
"actor" => ACTOR,
"object" => note,
"published" => date,
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
}
recipients = []
if /^@/.match input
mentions, input = input.split("\n", 2)
mentions.split(/, */).each do |m|
recipients << actor(m.chomp)
end
end
note["content"] = input.lines.select { |l| !l.empty? }.join("
")
recipients += Dir[File.join("followers", "*.json")].collect { |f| JSON.parse(File.read(f))["actor"] }
recipients.delete ACTOR
recipients.uniq!
note["to"] += recipients
create["to"] += recipients
save create
save note
FileUtils.ln_s File.join('..', path(create)), "outbox"
responses = send create, recipients
if responses.collect { |r| r.code.to_i }.uniq.max < 400
code = 200
response = "OK"
else
code = 502
response = responses.select { |r| r.code.to_i >= 400 }.collect { |r| r.body }.uniq
end
[code, response]
end
def actor mention
mention = mention.sub(/^@/, '').chomp
user, server = mention.split("@")
get("https://#{server}/.well-known/webfinger?resource=acct:#{mention}",
"application/jrd+json")["links"].select { |l|
l["rel"] == "self"
}[0]["href"]
end
def send object, urls
# https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb
keypair = OpenSSL::PKey::RSA.new(File.read('private.pem'))
responses = []
urls.each do |url|
date = Time.now.utc.httpdate
uri = URI.parse(url)
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 + '"'
uri = URI.parse(get(url)["inbox"])
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
header = {
'Content-Type' => 'application/activity+json',
'Host' => uri.host,
'Date' => date,
'Digest' => digest,
'Signature' => signed_header,
}
request = Net::HTTP::Post.new(uri.request_uri, header)
request.body = body
responses << http.request(request)
end
# puts responses
responses
end
def inbox uri
URI(get(uri)["inbox"]).request_uri
end
def path object
object["id"].sub(SOCIAL_URL, '').sub('/', '')
end
def save object
path = path object
FileUtils.mkdir_p File.dirname(path)
File.open(path, "w+") { |f| f.puts object.to_json }
end
def verify env
# https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb
# TODO verify digest
signature_params = {}
env["HTTP_SIGNATURE"].split(',').each do |pair|
k, v = pair.split('=')
signature_params[k] = v.gsub('"', '')
end
key_id = signature_params['keyId']
headers = signature_params['headers']
signature = Base64.decode64(signature_params['signature'])
actor = get key_id
key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem'])
comparison = headers.split(' ').map do |signed_params_name|
if signed_params_name == '(request-target)'
'(request-target): post /inbox'
elsif signed_params_name == 'content-type'
"#{signed_params_name}: #{env["CONTENT_TYPE"]}"
else
"#{signed_params_name}: #{env["HTTP_" + signed_params_name.upcase]}"
end
end.join("\n")
key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison)
# true
end
def auth env
auth = Rack::Auth::Basic::Request.new(env)
usr = File.read(".usr").chomp
pwd = File.read(".pwd").chomp
auth.provided? && auth.basic? && auth.credentials && auth.credentials == [usr, pwd]
true
end
end
=end