74 lines
		
	
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			74 lines
		
	
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| class ActivityPub::SynchronizeFollowersService < BaseService
 | |
|   include JsonLdHelper
 | |
|   include Payloadable
 | |
| 
 | |
|   def call(account, partial_collection_url)
 | |
|     @account = account
 | |
| 
 | |
|     items = collection_items(partial_collection_url)
 | |
|     return if items.nil?
 | |
| 
 | |
|     # There could be unresolved accounts (hence the call to .compact) but this
 | |
|     # should never happen in practice, since in almost all cases we keep an
 | |
|     # Account record, and should we not do that, we should have sent a Delete.
 | |
|     # In any case there is not much we can do if that occurs.
 | |
|     @expected_followers = items.filter_map { |uri| ActivityPub::TagManager.instance.uri_to_resource(uri, Account) }
 | |
| 
 | |
|     remove_unexpected_local_followers!
 | |
|     handle_unexpected_outgoing_follows!
 | |
|   end
 | |
| 
 | |
|   private
 | |
| 
 | |
|   def remove_unexpected_local_followers!
 | |
|     @account.followers.local.where.not(id: @expected_followers.map(&:id)).each do |unexpected_follower|
 | |
|       UnfollowService.new.call(unexpected_follower, @account)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   def handle_unexpected_outgoing_follows!
 | |
|     @expected_followers.each do |expected_follower|
 | |
|       next if expected_follower.following?(@account)
 | |
| 
 | |
|       if expected_follower.requested?(@account)
 | |
|         # For some reason the follow request went through but we missed it
 | |
|         expected_follower.follow_requests.find_by(target_account: @account)&.authorize!
 | |
|       else
 | |
|         # Since we were not aware of the follow from our side, we do not have an
 | |
|         # ID for it that we can include in the Undo activity. For this reason,
 | |
|         # the Undo may not work with software that relies exclusively on
 | |
|         # matching activity IDs and not the actor and target
 | |
|         follow = Follow.new(account: expected_follower, target_account: @account)
 | |
|         ActivityPub::DeliveryWorker.perform_async(build_undo_follow_json(follow), follow.account_id, follow.target_account.inbox_url)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   def build_undo_follow_json(follow)
 | |
|     Oj.dump(serialize_payload(follow, ActivityPub::UndoFollowSerializer))
 | |
|   end
 | |
| 
 | |
|   def collection_items(collection_or_uri)
 | |
|     collection = fetch_collection(collection_or_uri)
 | |
|     return unless collection.is_a?(Hash)
 | |
| 
 | |
|     collection = fetch_collection(collection['first']) if collection['first'].present?
 | |
|     return unless collection.is_a?(Hash)
 | |
| 
 | |
|     case collection['type']
 | |
|     when 'Collection', 'CollectionPage'
 | |
|       collection['items']
 | |
|     when 'OrderedCollection', 'OrderedCollectionPage'
 | |
|       collection['orderedItems']
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   def fetch_collection(collection_or_uri)
 | |
|     return collection_or_uri if collection_or_uri.is_a?(Hash)
 | |
|     return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
 | |
| 
 | |
|     fetch_resource_without_id_validation(collection_or_uri, nil, true)
 | |
|   end
 | |
| end
 |