# TODO
# delete all/thread
# anchors (return after delete)
# boost
# archive
# federation
# client post media
# test with pleroma etc
require 'uri'
require 'base64'
require 'digest/sha2'
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)
enable :sessions
set :session_secret, File.read(".secret").chomp
set :default_content_type, 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
set :port, 9292
before "/" do
if request.request_method == "POST"
halt 400 unless verify_signature(request.env)
end
end
post "/inbox" do # server-server
request.body.rewind # in case someone already read it
body = request.body.read
action = JSON.parse body
case action["type"]
when "Create"
create action["object"]
when "Delete"
delete action["object"]
when "Update"
delete action["object"]
create action["object"]
when "Follow"
File.open(File.join("public", "followers", mention(action["actor"]) + ".json"), "w+") { |f| f.puts body }
accept = { "@context" => "https://www.w3.org/ns/activitystreams",
"id" => File.join(SOCIAL_URL + "#accepts", SecureRandom.uuid),
"type" => "Accept",
"actor" => ACTOR,
"object" => action }
send_signed accept, action["actor"]
when "Undo"
o = action["object"]
case o["type"]
when "Follow"
Dir["public/followers/*.json"].each do |follower|
FileUtils.rm follower if JSON.parse(File.read(follower))["actor"] == o["actor"]
end
end
#else
#p body
end
end
post "/outbox" do # client-server
protected!
request.body.rewind # in case someone already read it
body = request.body.read
date = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
# TODO hashtags, replys
outbox_path = File.join("public/outbox", date + ".json")
object_path = File.join("public/objects", date + ".json")
create = {
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => File.join(SOCIAL_URL, outbox_path),
"type" => "Create",
"actor" => ACTOR,
"object" => {
"id" => File.join(SOCIAL_URL, object_path),
"type" => "Note",
"attributedTo" => ACTOR,
"published" => date,
"content" => "",
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
},
"published" => date,
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
}
recipients = []
if /^@/.match body
mentions, body = body.split("\n", 2)
mentions.split(/, */).each do |m|
recipients << actor(m.chomp)
end
end
create["object"]["content"] = body.lines.select { |l| !l.empty? }.join("
")
recipients += Dir[File.join("public/followers", "*.json")].collect { |f| JSON.parse(File.read(f))["actor"] }
recipients.delete ACTOR
recipients.uniq!
create["object"]["to"] += recipients
create["to"] += recipients
File.open(outbox_path, "w+") { |f| f.puts create.to_json }
File.open(object_path, "w+") { |f| f.puts create["object"].to_json }
recipients.each { |r| send_signed create, r }
end
post "/delete/*" do
protected!
FileUtils.rm params['splat'][0]
redirect to("/")
end
post "/follow/*" do
protected!
mention = params['splat'][0]
actor = actor(mention)
following_path = File.join("public", "following", mention + ".json")
follow = { "@context" => "https://www.w3.org/ns/activitystreams",
"id" => File.join(SOCIAL_URL, following_path),
"type" => "Follow",
"actor" => ACTOR,
"object" => actor }
send_signed follow, actor
File.open(following_path, "w+") { |f| f.puts follow.to_json }
redirect to("/")
end
post "/unfollow/*" do
protected!
mention = params['splat'][0]
actor = actor(mention)
following_path = File.join("public", "following", mention + ".json")
if File.exists?(following_path)
undo = { "@context" => "https://www.w3.org/ns/activitystreams",
"id" => File.join(SOCIAL_URL + "#undo", SecureRandom.uuid),
"type" => "Undo",
"actor" => ACTOR,
"object" => JSON.parse(File.read(following_path)) }
send_signed undo, actor
FileUtils.rm following_path
redirect to("/")
end
end
post "/login" do
session["client"] = true if params["secret"] == File.read(".pwd").chomp
redirect to("/")
end
get "/.well-known/webfinger" do
if request["resource"] == "acct:#{ACCOUNT}"
send_file "./public/webfinger", :type => "application/jrd+json"
else
halt 404
end
end
get "/", :provides => 'html' do
protected!
items = Dir['./inbox/*.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'
{ :id => item['id'],
:parent => item['inReplyTo'],
:file => file,
:actor_url => item['attributedTo'],
:mention => mention,
:follow => follow,
:content => item['content'],
:attachment => item['attachment'],
:replies => []
}
end.compact
@inbox = []
items.each do |i|
if i[:parent].nil? or items.select{|it| it[:id] == i[:parent] }.empty?
@inbox << i
else
#p i
items.select{|it| it[:id] == i[:parent] }.each{|it| it[:replies] << i}
end
end
html="