summaryrefslogtreecommitdiff
path: root/application.rb
diff options
context:
space:
mode:
Diffstat (limited to 'application.rb')
-rw-r--r--application.rb150
1 files changed, 111 insertions, 39 deletions
diff --git a/application.rb b/application.rb
index 029b04e..9e4e3a8 100644
--- a/application.rb
+++ b/application.rb
@@ -1,3 +1,11 @@
+# TODO
+# run as service
+# federation
+# client post media
+# client follow
+# client get media
+# server follow
+# test with pleroma etc
require 'json'
require 'net/http'
require 'uri'
@@ -5,6 +13,7 @@ require 'base64'
require 'securerandom'
require 'fileutils'
require 'digest/sha2'
+require 'nokogiri'
USER = "pdp8"
WWW_DOMAIN = "pdp8.info"
@@ -15,13 +24,11 @@ ACCOUNT = "#{USER}@#{SOCIAL_DOMAIN}"
SOCIAL_URL = "https://#{SOCIAL_DOMAIN}"
ACTOR = File.join(SOCIAL_URL, USER)
-MATRIX = "@#{USER}:matrix.#{WWW_DOMAIN}"
-
class Application
def call(env)
code = 404
type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
- response = "not allowed"
+ response = "Not found."
case env['REQUEST_METHOD']
@@ -40,6 +47,7 @@ class Application
puts input
when "Follow"
File.open(File.join("followers", SecureRandom.uuid + ".json"), "w+") { |f| f.puts input }
+ # TODO return accept activity
when "Undo"
puts input
else
@@ -52,33 +60,23 @@ class Application
response = "Request body contains invalid json."
end
else
- code = 401
- response = "Verification failed for POST to #{env["REQUEST_URI"]}."
+ code = 403
+ response = "Key verification failed for POST to #{env["REQUEST_URI"]}."
end
when "/outbox" # receive from client
- # TODO auth
if auth(env)
- input = JSON.parse(input)
- input["type"] == "Create" ? activity = input : activity = activity(input) # expand object to create activity
- add_id activity
- save activity
- FileUtils.ln_s File.join('..', path(activity)), "outbox"
- code, response = send activity, ["to", "bto", "cc", "bcc", "audience"].collect { |d|
- activity[d]
- }.flatten.uniq.compact
- code = 200
- response = "OK"
+ code, response = process input
else
code = 403
- response = "forbidden"
+ response = "You are not allowed to POST to #{env["REQUEST_URI"]}."
end
end
when 'GET'
- case env["REQUEST_PATH"]
+ case env["REQUEST_URI"] # REQUEST_PATH does not contain queries
when "/.well-known/webfinger?resource=acct:#{ACCOUNT}"
type = "application/jrd+json"
@@ -90,8 +88,17 @@ class Application
response = File.read(USER)
code = 200
- when %r{/[inbox|outbox|following|followers|likes|shares]}
- response = ordered_collection env["REQUEST_PATH"]
+ when "/inbox"
+ if auth(env)
+ type, response = format ordered_collection(env["REQUEST_PATH"]), env["HTTP_ACCEPT"]
+ code = 200
+ else
+ code = 403
+ response = "You are not allowed to GET #{env["REQUEST_URI"]}."
+ end
+
+ when %r{/[outbox|following|followers|likes|shares]}
+ response = ordered_collection(env["REQUEST_PATH"]).to_json
code = 200
end
@@ -99,25 +106,83 @@ class Application
[code, { "Content-Type" => type }, [response]]
end
- def add_id object
- object["id"] = File.join(SOCIAL_URL, object["type"].downcase, SecureRandom.uuid + ".json")
- end
-
- def activity object
+ def process input
date = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
- object["attributedTo"] = ACTOR
- object["published"] = date
- add_id object
- save object
- {
+ # 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" => ""
+ }
+ create = {
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "id" => File.join(SOCIAL_URL, "create", SecureRandom.uuid + ".json"),
"type" => "Create",
"actor" => ACTOR,
- "object" => object,
+ "object" => note,
"published" => date,
- "to" => object["to"],
- "cc" => object["cc"]
}
+ 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("<br>")
+ 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 format response, accept
+ if accept == "text/plain"
+ response = response["orderedItems"].collect.with_index do |r, i|
+ object = r["object"]
+ doc = Nokogiri::HTML(object["content"])
+ str = "#{i}\t#{object["published"]}\t#{}\n#{doc.text}"
+ str << "\n" + object["attachment"].collect { |att|
+ `kitty +kitten icat #{att["url"]}`
+ }.join("\n") if object["attachment"]
+ str
+ end.join("\n\n")
+ type = "text/plain"
+ else
+ response = response.to_json
+ type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
+ end
+ [type, response]
+ end
+
+ def actor account
+ account = account.sub(/^@/, '').chomp
+ user, server = account.split("@")
+ header = { 'Accept' => "application/jrd+json" }
+ uri = URI("https://" + server + "/.well-known/webfinger?resource=acct:#{account}")
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = true
+ request = Net::HTTP::Get.new(uri.request_uri, header)
+ response = http.request(request)
+ JSON.parse(response.body)["links"].select { |l| l["rel"] == "self" }[0]["href"]
end
def path object
@@ -131,8 +196,10 @@ class Application
end
def send object, urls
+ puts 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)
@@ -158,22 +225,24 @@ class Application
request = Net::HTTP::Post.new(uri.request_uri, header)
request.body = body
- response = http.request(request)
- # TODO return error if response.code > 400
- puts(response.body, response.code)
+ responses << http.request(request)
end
+ puts responses
+ responses
end
def ordered_collection dir
collection = dir.sub(/^\//, "")
- posts = Dir[File.join(collection, "*.json")].sort.reverse.collect { |f| p f; JSON.parse(File.read f) }
+ posts = Dir[File.join(collection, "*.json")].collect { |f|
+ p f; JSON.parse(File.read f)
+ }.sort_by { |o| o["published"] }
{
"@context" => "https://www.w3.org/ns/activitystreams",
"summary" => "#{USER} #{collection}",
"type" => "OrderedCollection",
"totalItems" => posts.size,
"orderedItems" => posts,
- }.to_json
+ }
end
def verify env
@@ -220,6 +289,9 @@ class Application
end
def auth env
- true
+ 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]
end
end