<%= 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 %>
<% end %>
"
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 create object
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
def mention actor
"#{get(actor)["preferredUsername"]}@#{URI(actor).host}"
end
def get url, accept = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
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)
JSON.parse(response.body)
end
def ordered_collection dir
posts = Dir[File.join(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)
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
http.request(request)
end
def inbox uri
URI(get(uri)["inbox"]).request_uri
end
def verify_signature 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)
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
=begin
class Application
def call(env)
code = 404
type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
response = "Not found."
case env['REQUEST_METHOD']
when 'POST'
input = env["rack.input"].read
case env["REQUEST_PATH"]
when %r{/delete} # receive from client
if auth(env)
FileUtils.rm env["REQUEST_URI"].sub("/delete/", "")
return [302, { "Location" => "/inbox" }, []]
end
when "/outbox" # receive from client
p "outbox"
if auth(env)
p "OK"
else
code = 403
response = "You are not allowed to POST to #{env["REQUEST_URI"]}."
end
when "/follow" # receive from client
if auth(env)
input.split.each do |mention|
actor = actor(mention)
follow = { "@context" => "https://www.w3.org/ns/activitystreams",
"id" => File.join(SOCIAL_URL, "following", SecureRandom.uuid + ".json"),
"type" => "Follow",
"actor" => ACTOR,
"object" => actor }
save follow
puts(send follow, [actor])
code = 200
response = "OK"
end
else
code = 403
response = "You are not allowed to POST to #{env["REQUEST_URI"]}."
end
when "/unfollow" # receive from client
if auth(env)
input.split.each do |mention|
actor = actor(mention)
Dir["following/*.json"].each do |f|
follow = JSON.parse(File.read(f))
puts follow
if follow["object"] == actor
undo = { "@context" => "https://www.w3.org/ns/activitystreams",
"id" => File.join(SOCIAL_URL + "#undo", SecureRandom.uuid),
"type" => "Undo",
"actor" => ACTOR,
"object" => follow }
send undo, [actor]
FileUtils.rm f
end
end
end
end
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 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