fetch_featured_collection_service.rb 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  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 invalid_origin?(collection_or_uri)
  26. fetch_resource_without_id_validation(collection_or_uri, local_follower, true)
  27. end
  28. def process_items(items)
  29. process_note_items(items) if @options[:note]
  30. process_hashtag_items(items) if @options[:hashtag]
  31. end
  32. def process_note_items(items)
  33. status_ids = items.filter_map do |item|
  34. next unless item.is_a?(String) || item['type'] == 'Note'
  35. uri = value_or_id(item)
  36. next if ActivityPub::TagManager.instance.local_uri?(uri)
  37. status = ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: local_follower)
  38. next unless status&.account_id == @account.id
  39. status.id
  40. rescue ActiveRecord::RecordInvalid => e
  41. Rails.logger.debug "Invalid pinned status #{uri}: #{e.message}"
  42. nil
  43. end
  44. to_remove = []
  45. to_add = status_ids
  46. StatusPin.where(account: @account).pluck(:status_id).each do |status_id|
  47. if status_ids.include?(status_id)
  48. to_add.delete(status_id)
  49. else
  50. to_remove << status_id
  51. end
  52. end
  53. StatusPin.where(account: @account, status_id: to_remove).delete_all unless to_remove.empty?
  54. to_add.each do |status_id|
  55. StatusPin.create!(account: @account, status_id: status_id)
  56. end
  57. end
  58. def process_hashtag_items(items)
  59. names = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.map { |name| HashtagNormalizer.new.normalize(name) }
  60. to_remove = []
  61. to_add = names
  62. FeaturedTag.where(account: @account).map(&:name).each do |name|
  63. if names.include?(name)
  64. to_add.delete(name)
  65. else
  66. to_remove << name
  67. end
  68. end
  69. FeaturedTag.includes(:tag).where(account: @account, tags: { name: to_remove }).delete_all unless to_remove.empty?
  70. to_add.each do |name|
  71. FeaturedTag.create!(account: @account, name: name)
  72. end
  73. end
  74. def local_follower
  75. return @local_follower if defined?(@local_follower)
  76. @local_follower = @account.followers.local.without_suspended.first
  77. end
  78. end