activity.rb 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. # frozen_string_literal: true
  2. class ActivityPub::Activity
  3. include JsonLdHelper
  4. include Redisable
  5. SUPPORTED_TYPES = %w(Note Question).freeze
  6. CONVERTED_TYPES = %w(Image Audio Video Article Page Event).freeze
  7. def initialize(json, account, **options)
  8. @json = json
  9. @account = account
  10. @object = @json['object']
  11. @options = options
  12. end
  13. def perform
  14. raise NotImplementedError
  15. end
  16. class << self
  17. def factory(json, account, **options)
  18. @json = json
  19. klass&.new(json, account, **options)
  20. end
  21. private
  22. def klass
  23. case @json['type']
  24. when 'Create'
  25. ActivityPub::Activity::Create
  26. when 'Announce'
  27. ActivityPub::Activity::Announce
  28. when 'Delete'
  29. ActivityPub::Activity::Delete
  30. when 'Follow'
  31. ActivityPub::Activity::Follow
  32. when 'Like'
  33. ActivityPub::Activity::Like
  34. when 'Block'
  35. ActivityPub::Activity::Block
  36. when 'Update'
  37. ActivityPub::Activity::Update
  38. when 'Undo'
  39. ActivityPub::Activity::Undo
  40. when 'Accept'
  41. ActivityPub::Activity::Accept
  42. when 'Reject'
  43. ActivityPub::Activity::Reject
  44. when 'Flag'
  45. ActivityPub::Activity::Flag
  46. when 'Add'
  47. ActivityPub::Activity::Add
  48. when 'Remove'
  49. ActivityPub::Activity::Remove
  50. when 'Move'
  51. ActivityPub::Activity::Move
  52. end
  53. end
  54. end
  55. protected
  56. def status_from_uri(uri)
  57. ActivityPub::TagManager.instance.uri_to_resource(uri, Status)
  58. end
  59. def account_from_uri(uri)
  60. ActivityPub::TagManager.instance.uri_to_resource(uri, Account)
  61. end
  62. def object_uri
  63. @object_uri ||= value_or_id(@object)
  64. end
  65. def unsupported_object_type?
  66. @object.is_a?(String) || !(supported_object_type? || converted_object_type?)
  67. end
  68. def supported_object_type?
  69. equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
  70. end
  71. def converted_object_type?
  72. equals_or_includes_any?(@object['type'], CONVERTED_TYPES)
  73. end
  74. def distribute(status)
  75. crawl_links(status)
  76. notify_about_reblog(status) if reblog_of_local_account?(status) && !reblog_by_following_group_account?(status)
  77. notify_about_mentions(status)
  78. # Only continue if the status is supposed to have arrived in real-time.
  79. # Note that if @options[:override_timestamps] isn't set, the status
  80. # may have a lower snowflake id than other existing statuses, potentially
  81. # "hiding" it from paginated API calls
  82. return unless @options[:override_timestamps] || status.within_realtime_window?
  83. distribute_to_followers(status)
  84. end
  85. def reblog_of_local_account?(status)
  86. status.reblog? && status.reblog.account.local?
  87. end
  88. def reblog_by_following_group_account?(status)
  89. status.reblog? && status.account.group? && status.reblog.account.following?(status.account)
  90. end
  91. def notify_about_reblog(status)
  92. NotifyService.new.call(status.reblog.account, status)
  93. end
  94. def notify_about_mentions(status)
  95. status.active_mentions.includes(:account).each do |mention|
  96. next unless mention.account.local? && audience_includes?(mention.account)
  97. NotifyService.new.call(mention.account, mention)
  98. end
  99. end
  100. def crawl_links(status)
  101. return if status.spoiler_text?
  102. # Spread out crawling randomly to avoid DDoSing the link
  103. LinkCrawlWorker.perform_in(rand(1..59).seconds, status.id)
  104. end
  105. def distribute_to_followers(status)
  106. ::DistributionWorker.perform_async(status.id)
  107. end
  108. def delete_arrived_first?(uri)
  109. redis.exists("delete_upon_arrival:#{@account.id}:#{uri}")
  110. end
  111. def delete_later!(uri)
  112. redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
  113. end
  114. def status_from_object
  115. # If the status is already known, return it
  116. status = status_from_uri(object_uri)
  117. return status unless status.nil?
  118. # If the boosted toot is embedded and it is a self-boost, handle it like a Create
  119. unless unsupported_object_type?
  120. actor_id = value_or_id(first_of_value(@object['attributedTo']))
  121. if actor_id == @account.uri
  122. return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account).perform
  123. end
  124. end
  125. fetch_remote_original_status
  126. end
  127. def follow_request_from_object
  128. @follow_request ||= FollowRequest.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
  129. end
  130. def follow_from_object
  131. @follow ||= ::Follow.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
  132. end
  133. def fetch_remote_original_status
  134. if object_uri.start_with?('http')
  135. return if ActivityPub::TagManager.instance.local_uri?(object_uri)
  136. ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first)
  137. elsif @object['url'].present?
  138. ::FetchRemoteStatusService.new.call(@object['url'])
  139. end
  140. end
  141. def lock_or_return(key, expire_after = 7.days.seconds)
  142. yield if redis.set(key, true, nx: true, ex: expire_after)
  143. ensure
  144. redis.del(key)
  145. end
  146. def fetch?
  147. !@options[:delivery]
  148. end
  149. def followed_by_local_accounts?
  150. @account.passive_relationships.exists?
  151. end
  152. def requested_through_relay?
  153. @options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled?
  154. end
  155. def reject_payload!
  156. Rails.logger.info("Rejected #{@json['type']} activity #{@json['id']} from #{@account.uri}#{@options[:relayed_through_account] && "via #{@options[:relayed_through_account].uri}"}")
  157. nil
  158. end
  159. end