Fetch an actor's featured collections (#38306)

This commit is contained in:
David Roetzel
2026-03-20 16:34:04 +01:00
committed by GitHub
parent 8bce0b99d4
commit 7aa696149f
15 changed files with 220 additions and 18 deletions

View File

@@ -0,0 +1,31 @@
# frozen_string_literal: true
class ActivityPub::FetchFeaturedCollectionsCollectionService < BaseService
include JsonLdHelper
MAX_PAGES = 10
MAX_ITEMS = 50
def call(account, request_id: nil)
return if account.collections_url.blank? || account.suspended? || account.local?
@request_id = request_id
@account = account
@items, = collection_items(@account.collections_url, max_pages: MAX_PAGES, reference_uri: @account.uri)
process_items(@items)
end
private
def process_items(items)
return if items.nil?
items.take(MAX_ITEMS).each do |collection_json|
if collection_json.is_a?(String)
ActivityPub::FetchRemoteFeaturedCollectionService.new.call(collection_json, request_id: @request_id)
else
ActivityPub::ProcessFeaturedCollectionService.new.call(@account, collection_json, request_id: @request_id)
end
end
end
end

View File

@@ -3,7 +3,7 @@
class ActivityPub::FetchRemoteFeaturedCollectionService < BaseService
include JsonLdHelper
def call(uri, on_behalf_of = nil)
def call(uri, request_id: nil, on_behalf_of: nil)
json = fetch_resource(uri, true, on_behalf_of)
return unless supported_context?(json)
@@ -17,6 +17,6 @@ class ActivityPub::FetchRemoteFeaturedCollectionService < BaseService
existing_collection = account.collections.find_by(uri:)
return existing_collection if existing_collection.present?
ActivityPub::ProcessFeaturedCollectionService.new.call(account, json)
ActivityPub::ProcessFeaturedCollectionService.new.call(account, json, request_id:)
end
end

View File

@@ -60,6 +60,7 @@ class ActivityPub::ProcessAccountService < BaseService
unless @options[:only_key] || @account.suspended?
check_featured_collection! if @json['featured'].present?
check_featured_tags_collection! if @json['featuredTags'].present?
check_featured_collections_collection! if @json['featuredCollections'].present? && Mastodon::Feature.collections_federation_enabled?
check_links! if @account.fields.any?(&:requires_verification?)
end
@@ -201,6 +202,10 @@ class ActivityPub::ProcessAccountService < BaseService
ActivityPub::SynchronizeFeaturedTagsCollectionWorker.perform_async(@account.id, @json['featuredTags'])
end
def check_featured_collections_collection!
ActivityPub::SynchronizeFeaturedCollectionsCollectionWorker.perform_async(@account.id, @options[:request_id])
end
def check_links!
VerifyAccountLinksWorker.perform_in(rand(10.minutes.to_i), @account.id)
end

View File

@@ -7,9 +7,10 @@ class ActivityPub::ProcessFeaturedCollectionService
ITEMS_LIMIT = 150
def call(account, json)
def call(account, json, request_id: nil)
@account = account
@json = json
@request_id = request_id
return if non_matching_uri_hosts?(@account.uri, @json['id'])
with_redis_lock("collection:#{@json['id']}") do
@@ -46,7 +47,7 @@ class ActivityPub::ProcessFeaturedCollectionService
def process_items!
@json['orderedItems'].take(ITEMS_LIMIT).each do |item_json|
ActivityPub::ProcessFeaturedItemWorker.perform_async(@collection.id, item_json)
ActivityPub::ProcessFeaturedItemWorker.perform_async(@collection.id, item_json, @request_id)
end
end
end

View File

@@ -5,7 +5,8 @@ class ActivityPub::ProcessFeaturedItemService
include Lockable
include Redisable
def call(collection, uri_or_object)
def call(collection, uri_or_object, request_id: nil)
@request_id = request_id
item_json = uri_or_object.is_a?(String) ? fetch_resource(uri_or_object, true) : uri_or_object
return if non_matching_uri_hosts?(collection.uri, item_json['id'])
@@ -35,8 +36,8 @@ class ActivityPub::ProcessFeaturedItemService
private
def verify_authorization!
ActivityPub::VerifyFeaturedItemService.new.call(@collection_item, @approval_uri)
ActivityPub::VerifyFeaturedItemService.new.call(@collection_item, @approval_uri, request_id: @request_id)
rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
ActivityPub::VerifyFeaturedItemWorker.perform_in(rand(30..600).seconds, @collection_item.id, @approval_uri)
ActivityPub::VerifyFeaturedItemWorker.perform_in(rand(30..600).seconds, @collection_item.id, @approval_uri, @request_id)
end
end

View File

@@ -3,7 +3,7 @@
class ActivityPub::VerifyFeaturedItemService
include JsonLdHelper
def call(collection_item, approval_uri)
def call(collection_item, approval_uri, request_id: nil)
@collection_item = collection_item
@authorization = fetch_resource(approval_uri, true, raise_on_error: :temporary)
@@ -16,7 +16,7 @@ class ActivityPub::VerifyFeaturedItemService
return unless matching_type? && matching_collection_uri?
account = Account.where(uri: @collection_item.object_uri).first
account ||= ActivityPub::FetchRemoteAccountService.new.call(@collection_item.object_uri)
account ||= ActivityPub::FetchRemoteAccountService.new.call(@collection_item.object_uri, request_id:)
return if account.blank?
@collection_item.update!(account:, approval_uri:, state: :accepted)

View File

@@ -6,10 +6,10 @@ class ActivityPub::ProcessFeaturedItemWorker
sidekiq_options queue: 'pull', retry: 3
def perform(collection_id, id_or_json)
def perform(collection_id, id_or_json, request_id = nil)
collection = Collection.find(collection_id)
ActivityPub::ProcessFeaturedItemService.new.call(collection, id_or_json)
ActivityPub::ProcessFeaturedItemService.new.call(collection, id_or_json, request_id:)
rescue ActiveRecord::RecordNotFound
true
end

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
class ActivityPub::SynchronizeFeaturedCollectionsCollectionWorker
include Sidekiq::Worker
sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i
def perform(account_id, request_id = nil)
account = Account.find(account_id)
ActivityPub::FetchFeaturedCollectionsCollectionService.new.call(account, request_id:)
rescue ActiveRecord::RecordNotFound
true
end
end

View File

@@ -7,10 +7,10 @@ class ActivityPub::VerifyFeaturedItemWorker
sidekiq_options queue: 'pull', retry: 5
def perform(collection_item_id, approval_uri)
def perform(collection_item_id, approval_uri, request_id = nil)
collection_item = CollectionItem.find(collection_item_id)
ActivityPub::VerifyFeaturedItemService.new.call(collection_item, approval_uri)
ActivityPub::VerifyFeaturedItemService.new.call(collection_item, approval_uri, request_id:)
rescue ActiveRecord::RecordNotFound
# Do nothing
nil