From d5f8b08d69c75304ccaafc44f17a14d5072b59df Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Tue, 28 Apr 2026 13:09:43 +0200 Subject: [PATCH] Add collections to `Flag` activities (#38817) --- app/lib/activitypub/activity.rb | 4 +++ app/lib/activitypub/activity/flag.rb | 3 ++ .../activitypub/flag_serializer.rb | 16 +++++++++- spec/lib/activitypub/activity/flag_spec.rb | 29 +++++++++++++++++- .../activitypub/flag_serializer_spec.rb | 30 +++++++++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 57fd41dcb3..60f723eeab 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -75,6 +75,10 @@ class ActivityPub::Activity ActivityPub::TagManager.instance.uri_to_resource(uri, Account) end + def collection_from_uri(uri) + ActivityPub::TagManager.instance.uri_to_resource(uri, Collection) + end + def object_uri @object_uri ||= uri_from_bearcap(value_or_id(@object)) end diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb index b7a412485c..bc25e5c29f 100644 --- a/app/lib/activitypub/activity/flag.rb +++ b/app/lib/activitypub/activity/flag.rb @@ -8,9 +8,11 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity target_accounts = object_uris.filter_map { |uri| account_from_uri(uri) } target_statuses_by_account = object_uris.filter_map { |uri| status_from_uri(uri) }.group_by(&:account_id) + target_collections_by_account = object_uris.filter_map { |uri| collection_from_uri(uri) }.group_by(&:account_id) target_accounts.each do |target_account| target_statuses = target_statuses_by_account[target_account.id] + target_collections = target_collections_by_account.fetch(target_account.id, []) replied_to_accounts = target_statuses.nil? ? [] : Account.local.where(id: target_statuses.filter_map(&:in_reply_to_account_id)) next if target_account.suspended? || (!target_account.local? && replied_to_accounts.none?) @@ -19,6 +21,7 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity @account, target_account, status_ids: target_statuses.nil? ? [] : target_statuses.map(&:id), + collection_ids: target_collections.map(&:id), comment: report_comment, uri: report_uri ) diff --git a/app/serializers/activitypub/flag_serializer.rb b/app/serializers/activitypub/flag_serializer.rb index 2f2a707d36..fe6f26d23d 100644 --- a/app/serializers/activitypub/flag_serializer.rb +++ b/app/serializers/activitypub/flag_serializer.rb @@ -17,10 +17,24 @@ class ActivityPub::FlagSerializer < ActivityPub::Serializer end def virtual_object - [ActivityPub::TagManager.instance.uri_for(object.target_account)] + object.statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) } + target_account_uris + status_uris + collection_uris end def content object.comment end + + private + + def target_account_uris + [ActivityPub::TagManager.instance.uri_for(object.target_account)] + end + + def status_uris + object.statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) } + end + + def collection_uris + object.collections.map { |c| ActivityPub::TagManager.instance.uri_for(c) } + end end diff --git a/spec/lib/activitypub/activity/flag_spec.rb b/spec/lib/activitypub/activity/flag_spec.rb index be1bd42a14..54f76c9bad 100644 --- a/spec/lib/activitypub/activity/flag_spec.rb +++ b/spec/lib/activitypub/activity/flag_spec.rb @@ -143,7 +143,34 @@ RSpec.describe ActivityPub::Activity::Flag do end end - context 'when an account is passed but no status' do + context 'when the activity includes and account and a collection' do + let(:collection) { Fabricate(:collection, account: flagged) } + let(:json) do + { + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => flag_id, + 'type' => 'Flag', + 'content' => 'Boo!!', + 'actor' => ActivityPub::TagManager.instance.uri_for(sender), + 'object' => [ + ActivityPub::TagManager.instance.uri_for(flagged), + ActivityPub::TagManager.instance.uri_for(collection), + ], + } + end + + it 'creates a report with an attached collection' do + subject.perform + + report = Report.find_by(account: sender, target_account: flagged) + + expect(report).to_not be_nil + expect(report.comment).to eq 'Boo!!' + expect(report.collections).to contain_exactly(collection) + end + end + + context 'when an account is passed but no status or collection' do let(:mentioned) { Fabricate(:account) } let(:json) do diff --git a/spec/serializers/activitypub/flag_serializer_spec.rb b/spec/serializers/activitypub/flag_serializer_spec.rb index 75f787a73a..a66a49bc87 100644 --- a/spec/serializers/activitypub/flag_serializer_spec.rb +++ b/spec/serializers/activitypub/flag_serializer_spec.rb @@ -22,4 +22,34 @@ RSpec.describe ActivityPub::FlagSerializer do expect(subject).to_not have_key('cc') expect(subject).to_not have_key('target') end + + context 'with status' do + let(:target_account) { Fabricate(:account) } + let(:status) { Fabricate(:status, account: target_account) } + let(:report) { Fabricate(:report, target_account:, status_ids: [status.id]) } + + it 'includes the status URI in `object`' do + expect(subject).to include({ + 'object' => [ + tag_manager.uri_for(target_account), + tag_manager.uri_for(status), + ], + }) + end + end + + context 'with collection', feature: :collections do + let(:target_account) { Fabricate(:account) } + let(:collection) { Fabricate(:collection, account: target_account) } + let(:report) { Fabricate(:report, target_account:, collections: [collection]) } + + it 'includes the collection URI in `object`' do + expect(subject).to include({ + 'object' => [ + tag_manager.uri_for(target_account), + tag_manager.uri_for(collection), + ], + }) + end + end end