# client-server
post "/outbox" do
protected!
date = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
outbox_path = File.join("public/outbox", date + ".json")
notes_path = File.join("public/notes", date + ".json")
recipients = ["https://www.w3.org/ns/activitystreams#Public", params[:to]]
recipients += Dir[File.join("public/followers", "*.json")].collect { |f| JSON.parse(File.read(f))["actor"] }
recipients.delete ACTOR
recipients.uniq!
content = []
attachment = []
tag = []
extensions = {
:image => ["jpeg","png"],
:audio => ["flac","wav","mp3","ogg"],
:video => ["mp4","webm"]
}
params[:content].lines.each do |line|
line.chomp!
if line.match(/^http/)
ext = File.extname(line).sub('.','')
media_type = extensions.select{|k,v| v.include? ext}.keys[0].to_s + '/' + ext
attachment << {
'type' => 'Document',
'mediaType' => media_type,
'url' => line
}
else
tags = line.split(/\s+/).grep(/^#\w+$/)
tags.each do |name|
href = File.join(SOCIAL_URL,'tags',name.sub('#',''))
tag << {
'type' => 'Hashtag',
'href' => href,
'name' => name
}
line.gsub!(name, "#{name}")
end
content << line
end
end
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, notes_path),
"type" => "Note",
"attributedTo" => ACTOR,
"inReplyTo" => params[:inReplyTo],
"published" => date,
"content" => "
\n"+content.join("\n
")+"\n
",
"attachment" => attachment,
"tag" => tag,
"to" => recipients
},
"published" => date,
"to" => recipients
}
File.open(outbox_path, "w+") { |f| f.puts create.to_json }
File.open(notes_path, "w+") { |f| f.puts create["object"].to_json }
create["to"].each { |r| send_signed create, r }
redirect to(params['redirect'])
end
post "/archive" do
protected!
FileUtils.mv params['file'], "archive/"
redirect to(params['redirect'])
end
post "/delete" do
protected!
FileUtils.rm params['file']
redirect to(params['redirect'])
end
post "/delete_all" do
protected!
FileUtils.rm Dir["inbox/*.json"]
redirect to("/")
end
post "/follow/*" do
protected!
mention = params['splat'][0]
actor = actor(mention)
follow = { "@context" => "https://www.w3.org/ns/activitystreams",
"id" => File.join(SOCIAL_URL, "following", mention + ".json"),
"type" => "Follow",
"actor" => ACTOR,
"object" => actor }
send_signed follow, actor
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
# private
["/", "/archive"].each do |path|
get path, :provides => 'html' do
protected!
if path == '/'
@dir = 'inbox'
@alt_dir = '/archive'
@alt_name = 'archive'
else
@dir = path.sub('/','')
@alt_dir = '/'
@alt_name = 'inbox'
end
threads
erb :index
end
end
helpers do
def protected!
redirect("/login.html") unless session['client']
end
def items
nr = 0
files = Dir[File.join(@dir, '*.json')] + Dir['public/objects/*.json']
@items = files.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'],
:indent => 2,
:replies => []
}
end.compact
@items.last[:nr] = @items.last[:nr] - 2 unless @items.empty?
end
def threads
items
@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 do |it|
i[:indent] = it[:indent] + 2
it[:replies] << i
end
end
end
end
def html item
@item = item
erb :item
end
def people
File.read('cache/people.tsv').split("\n").collect {|l| l.chomp.split("\t")}
end
def mention actor
person = people.select{|p| p[1] == actor}
if person.empty?
mention = "#{fetch(actor)["preferredUsername"]}@#{URI(actor).host}"
File.open('cache/people.tsv','a'){|f| f.puts "#{mention}\t#{actor}"}
mention
else
person[0][0]
end
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('cache/people.tsv','a'){|f| f.puts "#{mention}\t#{actor}"}
actor
else
actors[0][1]
end
end
end