fetch_featured_collection_service.rb 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # frozen_string_literal: true
  2. class ActivityPub::FetchFeaturedCollectionService < BaseService
  3. include JsonLdHelper
  4. def call(account, **options)
  5. return if account.featured_collection_url.blank? || account.suspended? || account.local?
  6. @account = account
  7. @options = options
  8. @json = fetch_resource(@account.featured_collection_url, true, local_follower)
  9. return unless supported_context?(@json)
  10. process_items(collection_items(@json))
  11. end
  12. private
  13. def collection_items(collection)
  14. collection = fetch_collection(collection['first']) if collection['first'].present?
  15. return unless collection.is_a?(Hash)
  16. case collection['type']
  17. when 'Collection', 'CollectionPage'
  18. collection['items']
  19. when 'OrderedCollection', 'OrderedCollectionPage'
  20. collection['orderedItems']
  21. end
  22. end
  23. def fetch_collection(collection_or_uri)
  24. return collection_or_uri if collection_or_uri.is_a?(Hash)
  25. return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
  26. fetch_resource_without_id_validation(collection_or_uri, local_follower, true)
  27. end
  28. def process_items(items)
  29. return if items.nil?
  30. process_note_items(items) if @options[:note]
  31. process_hashtag_items(items) if @options[:hashtag]
  32. end
  33. def process_note_items(items)
  34. status_ids = items.filter_map do |item|
  35. next unless item.is_a?(String) || item['type'] == 'Note'
  36. uri = value_or_id(item)
  37. next if ActivityPub::TagManager.instance.local_uri?(uri) || non_matching_uri_hosts?(@account.uri, uri)
  38. status = ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: local_follower, expected_actor_uri: @account.uri, request_id: @options[:request_id])
  39. next unless status&.account_id == @account.id
  40. status.id
  41. rescue ActiveRecord::RecordInvalid => e
  42. Rails.logger.debug { "Invalid pinned status #{uri}: #{e.message}" }
  43. nil
  44. end
  45. to_remove = []
  46. to_add = status_ids
  47. StatusPin.where(account: @account).pluck(:status_id).each do |status_id|
  48. if status_ids.include?(status_id)
  49. to_add.delete(status_id)
  50. else
  51. to_remove << status_id
  52. end
  53. end
  54. StatusPin.where(account: @account, status_id: to_remove).delete_all unless to_remove.empty?
  55. to_add.each do |status_id|
  56. StatusPin.create!(account: @account, status_id: status_id)
  57. end
  58. end
  59. def process_hashtag_items(items)
  60. names = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.map { |name| HashtagNormalizer.new.normalize(name) }
  61. to_remove = []
  62. to_add = names
  63. FeaturedTag.where(account: @account).map(&:name).each do |name|
  64. if names.include?(name)
  65. to_add.delete(name)
  66. else
  67. to_remove << name
  68. end
  69. end
  70. FeaturedTag.includes(:tag).where(account: @account, tags: { name: to_remove }).delete_all unless to_remove.empty?
  71. to_add.each do |name|
  72. FeaturedTag.create!(account: @account, name: name)
  73. end
  74. end
  75. def local_follower
  76. return @local_follower if defined?(@local_follower)
  77. @local_follower = @account.followers.local.without_suspended.first
  78. end
  79. end