Skip to content

Commit b79b3fc

Browse files
committed
Add attachment publication support
1 parent 5948a18 commit b79b3fc

12 files changed

+117
-26
lines changed

app/models/discourse_activity_pub_object.rb

+4
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ def attributed_to
159159
end
160160
end
161161

162+
def attachment
163+
self.attachments
164+
end
165+
162166
def likes_collection
163167
@likes_collection ||=
164168
begin

app/serializers/discourse_activity_pub/ap/object/article_serializer.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
class DiscourseActivityPub::AP::Object::ArticleSerializer < DiscourseActivityPub::AP::ObjectSerializer
4-
attributes :content, :inReplyTo, :url, :updated
4+
attributes :content, :inReplyTo, :url, :updated, :attachment
55

66
def inReplyTo
77
object.in_reply_to
@@ -18,4 +18,12 @@ def include_content?
1818
def deleted?
1919
!object.stored.model || object.stored.model.trashed?
2020
end
21+
22+
def attachment
23+
object.attachment.map(&:json)
24+
end
25+
26+
def include_attachment?
27+
object.attachment.present?
28+
end
2129
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
class DiscourseActivityPub::AP::Object::DocumentSerializer < DiscourseActivityPub::AP::ObjectSerializer
4+
attributes :media_type
5+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
class DiscourseActivityPub::AP::Object::ImageSerializer < DiscourseActivityPub::AP::ObjectSerializer
4+
attributes :media_type
5+
end

app/serializers/discourse_activity_pub/ap/object/note_serializer.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
class DiscourseActivityPub::AP::Object::NoteSerializer < DiscourseActivityPub::AP::ObjectSerializer
4-
attributes :content, :inReplyTo, :context
4+
attributes :content, :inReplyTo, :context, :attachment
55

66
def content
77
content = object.content
@@ -30,4 +30,12 @@ def include_content?
3030
def deleted?
3131
!object.stored.model || object.stored.model.trashed?
3232
end
33+
34+
def attachment
35+
object.attachment.map(&:json)
36+
end
37+
38+
def include_attachment?
39+
object.attachment.present?
40+
end
3341
end

app/serializers/discourse_activity_pub/ap/object_serializer.rb

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def attributes(*args)
2121
end
2222

2323
def include_id?
24+
return false if object.attachment?
2425
object.id.present?
2526
end
2627

lib/discourse_activity_pub/ap/object.rb

+9-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class Object
77
include HasErrors
88
include Handlers
99

10+
ATTACHMENT_TYPES = %w[Image Document]
11+
1012
attr_writer :json
1113
attr_writer :attributed_to
1214
attr_writer :context
@@ -50,6 +52,10 @@ def actor?
5052
base_type == "Actor"
5153
end
5254

55+
def attachment?
56+
ATTACHMENT_TYPES.include?(type)
57+
end
58+
5359
def url
5460
if stored
5561
stored.respond_to?(:url) && stored&.url
@@ -118,12 +124,10 @@ def delivered_to
118124
@delivered_to ||= []
119125
end
120126

121-
def media_type
122-
json[:mediaType] if json.present?
123-
end
124-
125127
def attachment
126-
if json.present? && json[:attachment].present?
128+
if stored.respond_to?(:attachment)
129+
@attachment ||= (stored.attachment || []).map { |a| a.ap }
130+
elsif json.present? && json[:attachment].present?
127131
json[:attachment].each_with_object([]) do |attachment_json, result|
128132
obj = AP::Object.factory(attachment_json)
129133
result << obj if obj.present?

lib/discourse_activity_pub/ap/object/document.rb

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ def type
77
"Document"
88
end
99

10+
def media_type
11+
if stored && stored.respond_to?(:media_type)
12+
stored.media_type
13+
elsif json
14+
json[:mediaType]
15+
end
16+
end
17+
1018
def can_belong_to
1119
%i[remote]
1220
end

lib/discourse_activity_pub/ap/object/image.rb

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ def type
77
"Image"
88
end
99

10+
def media_type
11+
if stored && stored.respond_to?(:media_type)
12+
stored.media_type
13+
elsif json
14+
json[:mediaType]
15+
end
16+
end
17+
1018
def can_belong_to
1119
%i[remote]
1220
end

plugin.rb

+24-19
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@
140140
require_relative "app/serializers/discourse_activity_pub/ap/actor/person_serializer"
141141
require_relative "app/serializers/discourse_activity_pub/ap/object/note_serializer"
142142
require_relative "app/serializers/discourse_activity_pub/ap/object/article_serializer"
143+
require_relative "app/serializers/discourse_activity_pub/ap/object/image_serializer"
144+
require_relative "app/serializers/discourse_activity_pub/ap/object/document_serializer"
143145
require_relative "app/serializers/discourse_activity_pub/ap/collection_serializer"
144146
require_relative "app/serializers/discourse_activity_pub/ap/collection/ordered_collection_serializer"
145147
require_relative "app/serializers/discourse_activity_pub/about_serializer"
@@ -1185,25 +1187,28 @@
11851187
)
11861188
end
11871189

1188-
if object.attachment.present?
1189-
object.attachment.each do |attachment|
1190-
# Some platforms (e.g. Mastodon) put attachment url media types on the attachment itself,
1191-
# instead of on a Link object in the url attribute. Technically this violates the specification,
1192-
# but we need to support it nevertheless. See further https://www.w3.org/TR/activitystreams-vocabulary/#dfn-mediatype
1193-
media_type = attachment.url.media_type || attachment.media_type
1194-
name = attachment.url.name || attachment.name
1195-
1196-
begin
1197-
DiscourseActivityPubAttachment.create(
1198-
object_id: object.stored.id,
1199-
object_type: "DiscourseActivityPubObject",
1200-
ap_type: attachment.type,
1201-
url: attachment.url.href,
1202-
name: name,
1203-
media_type: media_type,
1204-
)
1205-
rescue ActiveRecord::RecordInvalid => error
1206-
# fail silently if an attachment does not validate
1190+
if object.json[:attachment].present?
1191+
object.json[:attachment].each do |json|
1192+
attachment = DiscourseActivityPub::AP::Object.factory(json)
1193+
if attachment
1194+
# Some platforms (e.g. Mastodon) put attachment url media types on the attachment itself,
1195+
# instead of on a Link object in the url attribute. Technically this violates the specification,
1196+
# but we need to support it nevertheless. See further https://www.w3.org/TR/activitystreams-vocabulary/#dfn-mediatype
1197+
media_type = attachment.url.media_type || attachment.media_type
1198+
name = attachment.url.name || attachment.name
1199+
1200+
begin
1201+
DiscourseActivityPubAttachment.create(
1202+
object_id: object.stored.id,
1203+
object_type: "DiscourseActivityPubObject",
1204+
ap_type: attachment.type,
1205+
url: attachment.url.href,
1206+
name: name,
1207+
media_type: media_type,
1208+
)
1209+
rescue ActiveRecord::RecordInvalid => error
1210+
# fail silently if an attachment does not validate
1211+
end
12071212
end
12081213
end
12091214
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
Fabricator(:discourse_activity_pub_attachment) do
4+
ap_type { "Image" }
5+
media_type { "image/png" }
6+
7+
before_create do |object|
8+
filename = "#{SecureRandom.hex(8)}-image"
9+
object.url = "https://local.com/attachment/image/#{filename}.png"
10+
object.name = filename
11+
end
12+
end

spec/jobs/discourse_activity_pub_deliver_spec.rb

+23
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,29 @@ def find_announce
335335
end
336336
end
337337
end
338+
339+
context "when object has attachments" do
340+
let!(:topic) { Fabricate(:topic, category: group.model) }
341+
let!(:post) { Fabricate(:post, topic: topic) }
342+
let!(:note) { Fabricate(:discourse_activity_pub_object_note, local: true, model: post) }
343+
let!(:attachment1) { Fabricate(:discourse_activity_pub_attachment, object: note) }
344+
let!(:attachment2) { Fabricate(:discourse_activity_pub_attachment, object: note) }
345+
let!(:activity) do
346+
Fabricate(:discourse_activity_pub_activity_create, object: note, actor: group)
347+
end
348+
349+
it "publishes the attachments" do
350+
json = published_json(activity)
351+
expect(json[:object][:attachment].size).to eq(2)
352+
expect(json[:object][:attachment].first[:url]).to eq(attachment1.url)
353+
expect(json[:object][:attachment].second[:url]).to eq(attachment2.url)
354+
end
355+
356+
it "performs the right request" do
357+
expect_request(body: published_json(activity), actor_id: group.id, uri: person.inbox)
358+
execute_job(object_id: activity.id, from_actor_id: activity.actor.id)
359+
end
360+
end
338361
end
339362
end
340363
end

0 commit comments

Comments
 (0)