summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpdp8 <pdp8@pdp8.info>2023-07-29 20:59:48 +0200
committerpdp8 <pdp8@pdp8.info>2023-07-29 20:59:48 +0200
commitc50f749a2685a3e7608cec8730f5fe79de4676ac (patch)
tree75192aac75c86a6593737b1bdeb211f0799a5a8e
parent3daf30b0a3837d3d8becb0baceed580e92403ce6 (diff)
POST /create
-rw-r--r--activitypub.rb3
-rw-r--r--client.rb3
-rw-r--r--create.rb97
-rw-r--r--helpers.rb19
-rw-r--r--send.rb76
-rw-r--r--server.rb36
6 files changed, 142 insertions, 92 deletions
diff --git a/activitypub.rb b/activitypub.rb
index 90fe0c4..8a62fdd 100644
--- a/activitypub.rb
+++ b/activitypub.rb
@@ -22,6 +22,7 @@ ACTOR = File.join(SOCIAL_URL, USER)
INBOX = { dir: File.join(SOCIAL_DIR, 'inbox') }
OUTBOX = { dir: File.join(SOCIAL_DIR, 'outbox'), url: File.join(SOCIAL_URL, 'outbox') }
TAGS = { dir: File.join(PUBLIC_DIR, 'tags'), url: File.join(SOCIAL_URL, 'tags') }
+FOLLOWERS_URL = 'https://social.pdp8.info/followers'
CONTENT_TYPE = 'application/activity+json'
# CONTENT_TYPE = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
@@ -34,4 +35,4 @@ set :port, 9292
require_relative 'helpers'
require_relative 'server'
require_relative 'client'
-require_relative 'send'
+require_relative 'create'
diff --git a/client.rb b/client.rb
index 5ef721b..c2995f5 100644
--- a/client.rb
+++ b/client.rb
@@ -45,6 +45,7 @@ post '/follow' do
protected!
params['id'] = actor params['mention'] if params['mention']
outbox 'Follow', params['id'], [params['id']]
+ 200
end
post '/unfollow' do
@@ -55,6 +56,7 @@ post '/unfollow' do
activity ||= save_activity({ 'type' => 'Follow', 'actor' => ACTOR, 'object' => params['id'] }, OUTBOX) # recreate activity for old/deleted follows
outbox 'Undo', activity, [params['id']]
update_collection FOLLOWING, params['id'], true
+ 200
end
post '/share' do # TODO
@@ -72,6 +74,7 @@ end
post '/login' do
session['client'] = (OpenSSL::Digest::SHA256.base64digest(params['secret']) == File.read('.digest').chomp)
+ 200
end
helpers do
diff --git a/create.rb b/create.rb
new file mode 100644
index 0000000..d986812
--- /dev/null
+++ b/create.rb
@@ -0,0 +1,97 @@
+post '/create' do # TODO
+ protected!
+ request.body.rewind # in case someone already read it
+
+ to = []
+ inReplyTo = ''
+ content = []
+ tag = []
+ attachment = []
+
+ url_regexp = %r{\Ahttps?://\S+\Z}
+ mention_regexp = /\A@?\w+@\S+\Z/
+ hashtag_regexp = /\A#\w+\Z/
+
+ lines = request.body.read.each_line.to_a
+ lines.each.with_index do |line, i|
+ line.chomp!
+ if i == 0
+ to = line.split(/\s+/).collect do |word|
+ case word
+ when 'public'
+ ['https://www.w3.org/ns/activitystreams#Public', FOLLOWERS_URL]
+ when mention_regexp
+ actor word
+ when url_regexp
+ word
+ end
+ end.flatten
+ elsif i == 1 and line.match url_regexp
+ inReplyTo = line
+ elsif line == ''
+ content << '<p>'
+ elsif line.match(/\A==\Z/)
+ attachment = lines[i + 1..-1].collect do |url|
+ url.chomp!
+ {
+ 'type' => 'Document',
+ 'mediaType' => media_type(url),
+ 'url' => url
+ }
+ end
+ break
+ else
+ tags = line.split(/\s+/).grep(hashtag_regexp)
+ tags.each do |name|
+ tag_url = File.join(TAGS[:url], name.sub('#', ''))
+ tag << {
+ 'type' => 'Hashtag',
+ 'href' => tag_url,
+ 'name' => name
+ }
+ end
+ mentions = line.split(/\s+/).grep(mention_regexp)
+ mentions.each do |mention|
+ actor = actor(mention)
+ tag << {
+ 'type' => 'Mention',
+ 'href' => actor,
+ 'name' => mention
+ }
+ end
+ content << line
+ end
+ end
+
+ object = {
+ 'to' => to,
+ 'type' => 'Note',
+ 'attributedTo' => ACTOR,
+ 'content' => "#{content.join("\n")}"
+ }
+ object['inReplyTo'] = inReplyTo unless inReplyTo.empty?
+ object['attachment'] = attachment unless attachment.empty?
+ object['tag'] = tag unless tag.empty?
+
+ activity = outbox 'Create', object, to
+ if activity['object']['tag']
+ activity['object']['tag'].each do |tag|
+ next unless tag['type'] == 'Hashtag'
+
+ tag_path = File.join(TAGS[:dir], tag['name'].sub('#', '')) + '.json'
+ next if File.exist? tag_path
+
+ File.open(tag_path, 'w+') do |f|
+ tag_collection = {
+ '@context' => 'https://www.w3.org/ns/activitystreams',
+ 'id' => tag['href'],
+ 'type' => 'OrderedCollection',
+ 'totalItems' => 0,
+ 'orderedItems' => []
+ }
+ f.puts tag_collection.to_json
+ end
+ end
+ end
+ 200
+end
diff --git a/helpers.rb b/helpers.rb
index 5344606..7a56ec1 100644
--- a/helpers.rb
+++ b/helpers.rb
@@ -11,11 +11,13 @@ helpers do
activity_rel_path = File.join(activity['type'].downcase, basename)
activity_path = File.join(box[:dir], activity_rel_path)
if box == OUTBOX
+ return unless activity['to'].include? 'https://www.w3.org/ns/activitystreams#Public' # save only public messages
+
activity['id'] = File.join(box[:url], activity_rel_path)
activity['object']['published'] = date unless activity['object'].is_a? String
end
# save object
- save_object activity['object'], box if activity['object'] && activity['object']['type'] && !activity['object']['id']
+ save_object activity['object'], box if %w[Create Announce Update].include? activity['type']
# save activity
FileUtils.mkdir_p File.dirname(activity_path)
File.open(activity_path, 'w+') { |f| f.puts activity.to_json }
@@ -24,12 +26,12 @@ helpers do
def save_object(object, box)
object = fetch(object) if object.is_a? String and object.match(/^http/)
- return unless object
+ return unless object # and object['type'] != 'Person'
object['@context'] = 'https://www.w3.org/ns/activitystreams'
basename = "#{object['published']}_#{mention(object['attributedTo'])}.json"
object_rel_path = File.join 'object', object['type'].downcase, basename
- object['id'] = File.join box[:url], object_rel_path if box == OUTBOX
+ object['id'] ||= File.join box[:url], object_rel_path # if box == OUTBOX
object_path = File.join box[:dir], object_rel_path
FileUtils.mkdir_p File.dirname(object_path)
File.open(object_path, 'w+') { |f| f.puts object.to_json }
@@ -122,4 +124,15 @@ helpers do
sharedInbox = a['endpoints']['sharedInbox'] if a['endpoints'] && a['endpoints']['sharedInbox']
File.open('public/people.tsv', 'a') { |f| f.puts "#{mention}\t#{actor}\t#{sharedInbox}" }
end
+
+ def media_type(url) # TODO: extend extensions
+ extensions = {
+ image: %w[jpeg jpg png tiff],
+ audio: %w[flac wav mp3 ogg aiff],
+ video: %w[mp4 webm]
+ }
+ ext = File.extname(url).sub('.', '').downcase
+ type = extensions.find { |_k, v| v.include? ext }
+ "#{type[0]}/#{ext}"
+ end
end
diff --git a/send.rb b/send.rb
deleted file mode 100644
index 6dd4f3b..0000000
--- a/send.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-post '/outbox' do # TODO
- protected!
-
- recipients = params[:public] == 'true' ? public : []
- recipients << params[:to]
-
- tag = []
- params[:content].lines.each do |line|
- tags = line.split(/\s+/).grep(/^#\w+$/)
- tags.each do |name|
- tag_url = File.join(TAGS[:url], name.sub('#', ''))
- tag << {
- 'type' => 'Hashtag',
- 'href' => tag_url,
- 'name' => name
- }
- end
-
- mentions = line.split(/\s+/).grep(/^@\w+@\S+$/)
- mentions.each do |mention|
- actor = actor(mention)
- tag << {
- 'type' => 'Mention',
- 'href' => actor,
- 'name' => mention
- }
- end
- end
-
- attachment = []
- extensions = {
- image: %w[jpeg png],
- audio: %w[flac wav mp3 ogg],
- video: %w[mp4 webm]
- }
- if params[:media]
- params[:media].each do |_media|
- 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
- }
- end
- end
-
- object = {
- 'type' => 'Note',
- 'attributedTo' => ACTOR,
- 'inReplyTo' => params[:inReplyTo],
- 'content' => "<p>\n#{params[:content]}\n</p>",
- 'attachment' => attachment,
- 'tag' => tag,
- 'to' => recipients
- }
-
- activity = outbox 'Create', object, recipients
- activity['object']['tag'].each do |tag|
- next unless tag['type'] == 'Hashtag'
-
- tag_path = File.join(TAGS[:dir], tag['name'].sub('#', '')) + '.json'
- next if File.exist? tag_path
-
- File.open(tag_path, 'w+') do |f|
- tag_collection = {
- '@context' => 'https://www.w3.org/ns/activitystreams',
- 'id' => tag['href'],
- 'type' => 'OrderedCollection',
- 'totalItems' => 0,
- 'orderedItems' => []
- }
- f.puts tag_collection.to_json
- end
- end
-end
diff --git a/server.rb b/server.rb
index e9f4f2e..5dfc53e 100644
--- a/server.rb
+++ b/server.rb
@@ -13,8 +13,8 @@ post '/inbox' do
end
halt 501 if @activity['actor'] and @activity['type'] == 'Delete' # deleted actors return 403 => verification error
# verify! # pixelfed sends unsigned activities???
- save_activity(@activity, INBOX)
type = @activity['type'].downcase.to_sym
+ save_activity(@activity, INBOX) unless %i[create announce].include? type
send(type) if %i[create announce follow accept undo].include? type
halt 200
end
@@ -124,21 +124,32 @@ helpers do
halt 403 unless key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison)
end
- def outbox(type, object, recipients)
+ def actor_inbox(url)
+ actor = fetch url
+ return unless actor
+
+ if actor['endpoints'] and actor['endpoints']['sharedInbox']
+ actor['endpoints']['sharedInbox']
+ elsif actor['inbox']
+ actor['inbox']
+ end
+ end
+
+ def outbox(type, object, to)
# send
## https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb
+ to = [to] if to.is_a?(String)
inboxes = []
- recipients.uniq.each do |url|
+ to.uniq.each do |url|
next if [ACTOR, 'https://www.w3.org/ns/activitystreams#Public'].include? url
- actor = fetch url
- next unless actor
-
- if actor['endpoints'] and actor['endpoints']['sharedInbox']
- inboxes << actor['endpoints']['sharedInbox']
- elsif actor['inbox']
- inboxes << actor['inbox']
+ if url == FOLLOWERS_URL
+ JSON.parse(File.read(FOLLOWERS))['orderedItems'].each do |follower|
+ inboxes << actor_inbox(follower)
+ end
+ next
end
+ inboxes << actor_inbox(url)
end
# add date and id, save
@@ -147,8 +158,9 @@ helpers do
'type' => type,
'actor' => ACTOR,
'object' => object,
- 'to' => recipients
+ 'to' => to
}, OUTBOX)
+
body = activity.to_json
sha256 = OpenSSL::Digest.new('SHA256')
digest = "SHA-256=#{sha256.base64digest(body)}"
@@ -162,7 +174,7 @@ helpers do
signed_header = "keyId=\"#{ACTOR}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"#{signature}\""
curl(
- "-X POST -H 'Host: #{uri.host}' -H 'Date: #{httpdate}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' --data-binary '#{body}'", inbox
+ "-X POST -H 'Host: #{uri.host}' -H 'Date: #{httpdate}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' --data-raw '#{body}'", inbox
)
end
activity