synchronize_followers_service.rb 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. # frozen_string_literal: true
  2. class ActivityPub::SynchronizeFollowersService < BaseService
  3. include JsonLdHelper
  4. include Payloadable
  5. def call(account, partial_collection_url)
  6. @account = account
  7. items = collection_items(partial_collection_url)
  8. return if items.nil?
  9. # There could be unresolved accounts (hence the call to .compact) but this
  10. # should never happen in practice, since in almost all cases we keep an
  11. # Account record, and should we not do that, we should have sent a Delete.
  12. # In any case there is not much we can do if that occurs.
  13. @expected_followers = items.filter_map { |uri| ActivityPub::TagManager.instance.uri_to_resource(uri, Account) }
  14. remove_unexpected_local_followers!
  15. handle_unexpected_outgoing_follows!
  16. end
  17. private
  18. def remove_unexpected_local_followers!
  19. @account.followers.local.where.not(id: @expected_followers.map(&:id)).each do |unexpected_follower|
  20. UnfollowService.new.call(unexpected_follower, @account)
  21. end
  22. end
  23. def handle_unexpected_outgoing_follows!
  24. @expected_followers.each do |expected_follower|
  25. next if expected_follower.following?(@account)
  26. if expected_follower.requested?(@account)
  27. # For some reason the follow request went through but we missed it
  28. expected_follower.follow_requests.find_by(target_account: @account)&.authorize!
  29. else
  30. # Since we were not aware of the follow from our side, we do not have an
  31. # ID for it that we can include in the Undo activity. For this reason,
  32. # the Undo may not work with software that relies exclusively on
  33. # matching activity IDs and not the actor and target
  34. follow = Follow.new(account: expected_follower, target_account: @account)
  35. ActivityPub::DeliveryWorker.perform_async(build_undo_follow_json(follow), follow.account_id, follow.target_account.inbox_url)
  36. end
  37. end
  38. end
  39. def build_undo_follow_json(follow)
  40. Oj.dump(serialize_payload(follow, ActivityPub::UndoFollowSerializer))
  41. end
  42. def collection_items(collection_or_uri)
  43. collection = fetch_collection(collection_or_uri)
  44. return unless collection.is_a?(Hash)
  45. collection = fetch_collection(collection['first']) if collection['first'].present?
  46. return unless collection.is_a?(Hash)
  47. case collection['type']
  48. when 'Collection', 'CollectionPage'
  49. collection['items']
  50. when 'OrderedCollection', 'OrderedCollectionPage'
  51. collection['orderedItems']
  52. end
  53. end
  54. def fetch_collection(collection_or_uri)
  55. return collection_or_uri if collection_or_uri.is_a?(Hash)
  56. return if invalid_origin?(collection_or_uri)
  57. fetch_resource_without_id_validation(collection_or_uri, nil, true)
  58. end
  59. end