about summary refs log tree commit diff
path: root/app/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/services')
-rw-r--r--app/services/activitypub/fetch_featured_collection_service.rb3
-rw-r--r--app/services/activitypub/fetch_remote_account_service.rb14
-rw-r--r--app/services/activitypub/fetch_remote_poll_service.rb2
-rw-r--r--app/services/activitypub/fetch_remote_status_service.rb21
-rw-r--r--app/services/activitypub/process_account_service.rb3
-rw-r--r--app/services/activitypub/process_collection_service.rb4
-rw-r--r--app/services/activitypub/process_poll_service.rb1
-rw-r--r--app/services/authorize_follow_service.rb2
-rw-r--r--app/services/batched_remove_status_service.rb9
-rw-r--r--app/services/block_service.rb2
-rw-r--r--app/services/concerns/payloadable.rb2
-rw-r--r--app/services/fetch_link_card_service.rb4
-rw-r--r--app/services/fetch_remote_account_service.rb2
-rw-r--r--app/services/fetch_remote_status_service.rb2
-rw-r--r--app/services/fetch_resource_service.rb (renamed from app/services/fetch_atom_service.rb)44
-rw-r--r--app/services/follow_service.rb21
-rw-r--r--app/services/post_status_service.rb7
-rw-r--r--app/services/reblog_service.rb2
-rw-r--r--app/services/reject_follow_service.rb2
-rw-r--r--app/services/remove_status_service.rb20
-rw-r--r--app/services/resolve_account_service.rb106
-rw-r--r--app/services/resolve_url_service.rb41
-rw-r--r--app/services/suspend_account_service.rb1
-rw-r--r--app/services/unblock_service.rb2
-rw-r--r--app/services/unfavourite_service.rb2
-rw-r--r--app/services/unfollow_service.rb5
26 files changed, 181 insertions, 143 deletions
diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb
index 6a137b520..2c2770466 100644
--- a/app/services/activitypub/fetch_featured_collection_service.rb
+++ b/app/services/activitypub/fetch_featured_collection_service.rb
@@ -4,13 +4,12 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService
   include JsonLdHelper
 
   def call(account)
-    return if account.featured_collection_url.blank?
+    return if account.featured_collection_url.blank? || account.suspended? || account.local?
 
     @account = account
     @json    = fetch_resource(@account.featured_collection_url, true)
 
     return unless supported_context?
-    return if @account.suspended? || @account.local?
 
     case @json['type']
     when 'Collection', 'CollectionPage'
diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb
index df1e79d7d..381726c35 100644
--- a/app/services/activitypub/fetch_remote_account_service.rb
+++ b/app/services/activitypub/fetch_remote_account_service.rb
@@ -3,19 +3,23 @@
 class ActivityPub::FetchRemoteAccountService < BaseService
   include JsonLdHelper
   include AutorejectHelper
+  include DomainControlHelper
 
   SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
 
   # Does a WebFinger roundtrip on each call, unless `only_key` is true
   def call(uri, id: true, prefetched_body: nil, break_on_redirect: false, only_key: false)
+    return if domain_not_allowed?(uri)
     return ActivityPub::TagManager.instance.uri_to_resource(uri, Account) if ActivityPub::TagManager.instance.local_uri?(uri)
 
     return if autoreject?(uri)
-    @json = if prefetched_body.nil?
-              fetch_resource(uri, id)
-            else
-              body_to_json(prefetched_body, compare_id: id ? uri : nil)
-            end
+    @json = begin
+      if prefetched_body.nil?
+        fetch_resource(uri, id)
+      else
+        body_to_json(prefetched_body, compare_id: id ? uri : nil)
+      end
+    end
 
     return if autoreject?
     return if !supported_context? || !expected_type? || (break_on_redirect && @json['movedTo'].present?)
diff --git a/app/services/activitypub/fetch_remote_poll_service.rb b/app/services/activitypub/fetch_remote_poll_service.rb
index 854a32d05..1c79ecf11 100644
--- a/app/services/activitypub/fetch_remote_poll_service.rb
+++ b/app/services/activitypub/fetch_remote_poll_service.rb
@@ -5,7 +5,9 @@ class ActivityPub::FetchRemotePollService < BaseService
 
   def call(poll, on_behalf_of = nil)
     json = fetch_resource(poll.status.uri, true, on_behalf_of)
+
     return unless supported_context?(json)
+
     ActivityPub::ProcessPollService.new.call(poll, json)
   end
 end
diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb
index 423c7bc9a..9795cf651 100644
--- a/app/services/activitypub/fetch_remote_status_service.rb
+++ b/app/services/activitypub/fetch_remote_status_service.rb
@@ -7,20 +7,19 @@ class ActivityPub::FetchRemoteStatusService < BaseService
   # Should be called when uri has already been checked for locality
   def call(uri, id: true, prefetched_body: nil, on_behalf_of: nil, announced_by: nil, requested: false)
     return if autoreject?(uri)
-
-    @json = if prefetched_body.nil?
-              fetch_resource(uri, id, on_behalf_of)
-            else
-              body_to_json(prefetched_body, compare_id: id ? uri : nil)
-            end
+    @json = begin
+      if prefetched_body.nil?
+        fetch_resource(uri, id, on_behalf_of)
+      else
+        body_to_json(prefetched_body, compare_id: id ? uri : nil)
+      end
+    end
 
     return if autoreject?
-    return unless supported_context? && expected_type?
-
-    return if actor_id.nil? || !trustworthy_attribution?(@json['id'], actor_id)
+    return if !(supported_context? && expected_type?) || actor_id.nil? || !trustworthy_attribution?(@json['id'], actor_id)
 
     actor = ActivityPub::TagManager.instance.uri_to_resource(actor_id, Account)
-    actor = ActivityPub::FetchRemoteAccountService.new.call(actor_id, id: true) if actor.nil? || needs_update(actor)
+    actor = ActivityPub::FetchRemoteAccountService.new.call(actor_id, id: true) if actor.nil? || needs_update?(actor)
 
     return if actor.nil? || actor.suspended?
 
@@ -50,7 +49,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
     equals_or_includes_any?(@json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
   end
 
-  def needs_update(actor)
+  def needs_update?(actor)
     actor.possibly_stale?
   end
 
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 165c3e9ba..c4671a744 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -3,11 +3,12 @@
 class ActivityPub::ProcessAccountService < BaseService
   include JsonLdHelper
   include LogHelper
+  include DomainControlHelper
 
   # Should be called with confirmed valid JSON
   # and WebFinger-resolved username and domain
   def call(username, domain, json, options = {})
-    return if json['inbox'].blank? || unsupported_uri_scheme?(json['id'])
+    return if json['inbox'].blank? || unsupported_uri_scheme?(json['id']) || domain_not_allowed?(domain)
 
     @options     = options
     @json        = json
diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb
index 881df478b..a2a2e7071 100644
--- a/app/services/activitypub/process_collection_service.rb
+++ b/app/services/activitypub/process_collection_service.rb
@@ -8,9 +8,7 @@ class ActivityPub::ProcessCollectionService < BaseService
     @json    = Oj.load(body, mode: :strict)
     @options = options
 
-    return unless supported_context?
-    return if different_actor? && verify_account!.nil?
-    return if @account.suspended? || @account.local?
+    return if !supported_context? || (different_actor? && verify_account!.nil?) || @account.suspended? || @account.local?
 
     case @json['type']
     when 'Collection', 'CollectionPage'
diff --git a/app/services/activitypub/process_poll_service.rb b/app/services/activitypub/process_poll_service.rb
index 61357abd3..2fbce65b9 100644
--- a/app/services/activitypub/process_poll_service.rb
+++ b/app/services/activitypub/process_poll_service.rb
@@ -5,6 +5,7 @@ class ActivityPub::ProcessPollService < BaseService
 
   def call(poll, json)
     @json = json
+
     return unless expected_type?
 
     previous_expires_at = poll.expires_at
diff --git a/app/services/authorize_follow_service.rb b/app/services/authorize_follow_service.rb
index 77a389cc3..49bef727e 100644
--- a/app/services/authorize_follow_service.rb
+++ b/app/services/authorize_follow_service.rb
@@ -11,7 +11,7 @@ class AuthorizeFollowService < BaseService
       follow_request.authorize!
     end
 
-    create_notification(follow_request) unless source_account.local?
+    create_notification(follow_request) if !source_account.local? && source_account.activitypub?
     follow_request
   end
 
diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb
index 3110cb511..09d094c15 100644
--- a/app/services/batched_remove_status_service.rb
+++ b/app/services/batched_remove_status_service.rb
@@ -10,12 +10,12 @@ class BatchedRemoveStatusService < BaseService
   # @param [Hash] options
   # @option [Boolean] :skip_side_effects
   def call(statuses, **options)
-    statuses = Status.where(id: statuses.map(&:id)).includes(:account, :stream_entry).flat_map { |status| [status] + status.reblogs.includes(:account, :stream_entry).to_a }
+    statuses = Status.where(id: statuses.map(&:id)).includes(:account).flat_map { |status| [status] + status.reblogs.includes(:account).to_a }
 
     @mentions = statuses.each_with_object({}) { |s, h| h[s.id] = s.active_mentions.includes(:account).to_a }
     @tags     = statuses.each_with_object({}) { |s, h| h[s.id] = s.tags.pluck(:name) }
 
-    @json_payloads         = statuses.each_with_object({}) { |s, h| h[s.id] = Oj.dump(event: :delete, payload: s.id.to_s) }
+    @json_payloads = statuses.each_with_object({}) { |s, h| h[s.id] = Oj.dump(event: :delete, payload: s.id.to_s) }
 
     # Ensure that rendered XML reflects destroyed state
     statuses.each do |status|
@@ -89,9 +89,12 @@ class BatchedRemoveStatusService < BaseService
     payload = @json_payloads[status.id]
     redis.pipelined do
       @mentions[status.id].each do |mention|
-        redis.publish("timeline:direct:#{mention.account.id}", payload) if mention.account.local?
+# TODO: Pull https://github.com/ThibG/mastodon/commit/ca17bae904783dfb1f4899a533d28a79da0c6fe9
+       redis.publish("timeline:direct:#{mention.account.id}", payload) if mention.account.local?
+#        FeedManager.instance.unpush_from_direct(mention.account, status) if mention.account.local?
       end
       redis.publish("timeline:direct:#{status.account.id}", payload) if status.account.local?
+#      FeedManager.instance.unpush_from_direct(status.account, status) if status.account.local?
     end
   end
 end
diff --git a/app/services/block_service.rb b/app/services/block_service.rb
index 0057dfb4a..da06361c2 100644
--- a/app/services/block_service.rb
+++ b/app/services/block_service.rb
@@ -13,7 +13,7 @@ class BlockService < BaseService
     block = account.block!(target_account)
 
     BlockWorker.perform_async(account.id, target_account.id)
-    create_notification(block) unless target_account.local?
+    create_notification(block) if !target_account.local? && target_account.activitypub?
     block
   end
 
diff --git a/app/services/concerns/payloadable.rb b/app/services/concerns/payloadable.rb
index 13d9c3548..953740faa 100644
--- a/app/services/concerns/payloadable.rb
+++ b/app/services/concerns/payloadable.rb
@@ -14,6 +14,6 @@ module Payloadable
   end
 
   def signing_enabled?
-    true
+    ENV['AUTHORIZED_FETCH'] != 'true'
   end
 end
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index cd0b77426..0c401ea83 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -34,7 +34,7 @@ class FetchLinkCardService < BaseService
     end
 
     attach_card if @card&.persisted?
-  rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
+  rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
     Rails.logger.debug "Error fetching link #{@url}: #{e}"
     nil
   end
@@ -89,7 +89,7 @@ class FetchLinkCardService < BaseService
 
   def mention_link?(a)
     @status.mentions.any? do |mention|
-      a['href'] == TagManager.instance.url_for(mention.account)
+      a['href'] == ActivityPub::TagManager.instance.url_for(mention.account)
     end
   end
 
diff --git a/app/services/fetch_remote_account_service.rb b/app/services/fetch_remote_account_service.rb
index aed6a90c8..1227bf276 100644
--- a/app/services/fetch_remote_account_service.rb
+++ b/app/services/fetch_remote_account_service.rb
@@ -3,7 +3,7 @@
 class FetchRemoteAccountService < BaseService
   def call(url, prefetched_body = nil)
     if prefetched_body.nil?
-      resource_url, resource_options = FetchAtomService.new.call(url)
+      resource_url, resource_options = FetchResourceService.new.call(url)
     else
       resource_url     = url
       resource_options = { prefetched_body: prefetched_body }
diff --git a/app/services/fetch_remote_status_service.rb b/app/services/fetch_remote_status_service.rb
index e8f9a671b..4d28a492d 100644
--- a/app/services/fetch_remote_status_service.rb
+++ b/app/services/fetch_remote_status_service.rb
@@ -3,7 +3,7 @@
 class FetchRemoteStatusService < BaseService
   def call(url, prefetched_body = nil, announced_by: nil, requested: false)
     if prefetched_body.nil?
-      resource_url, resource_options = FetchAtomService.new.call(url)
+      resource_url, resource_options = FetchResourceService.new.call(url)
       resource_options = {} if resource_options.nil?
     else
       resource_url     = url
diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_resource_service.rb
index b6a33706f..3676d899d 100644
--- a/app/services/fetch_atom_service.rb
+++ b/app/services/fetch_resource_service.rb
@@ -1,19 +1,16 @@
 # frozen_string_literal: true
 
-class FetchAtomService < BaseService
+class FetchResourceService < BaseService
   include JsonLdHelper
-  include AutorejectHelper
+
+  ACCEPT_HEADER = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", text/html'
 
   def call(url)
     return if url.blank?
-    return if autoreject?(url)
 
-    result = process(url)
-  rescue OpenSSL::SSL::SSLError => e
-    Rails.logger.debug "SSL error: #{e}"
-    nil
-  rescue HTTP::ConnectionError => e
-    Rails.logger.debug "HTTP ConnectionError: #{e}"
+    process(url)
+  rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
+    Rails.logger.debug "Error fetching resource #{@url}: #{e}"
     nil
   end
 
@@ -21,13 +18,12 @@ class FetchAtomService < BaseService
 
   def process(url, terminal = false)
     @url = url
+
     perform_request { |response| process_response(response, terminal) }
   end
 
   def perform_request(&block)
-    accept = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", text/html'
-
-    Request.new(:get, @url).add_headers('Accept' => accept).perform(&block)
+    Request.new(:get, @url).add_headers('Accept' => ACCEPT_HEADER).on_behalf_of(Account.representative).perform(&block)
   end
 
   def process_response(response, terminal = false)
@@ -36,13 +32,8 @@ class FetchAtomService < BaseService
     if ['application/activity+json', 'application/ld+json'].include?(response.mime_type)
       body = response.body_with_limit
       json = body_to_json(body)
-      if supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && json['inbox'].present?
-        [json['id'], { prefetched_body: body, id: true }]
-      elsif supported_context?(json) && expected_type?(json)
-        [json['id'], { prefetched_body: body, id: true }]
-      else
-        nil
-      end
+
+      [json['id'], { prefetched_body: body, id: true }, :activitypub] if supported_context?(json) && (equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) || expected_type?(json))
     elsif !terminal
       link_header = response['Link'] && parse_link_header(response)
 
@@ -59,28 +50,19 @@ class FetchAtomService < BaseService
   end
 
   def process_html(response)
-    page = Nokogiri::HTML(response.body_with_limit)
-
+    page      = Nokogiri::HTML(response.body_with_limit)
     json_link = page.xpath('//link[@rel="alternate"]').find { |link| ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(link['type']) }
 
-    result = process(json_link['href'], terminal: true) unless json_link.nil?
-    result ||= nil
-    result
+    process(json_link['href'], terminal: true) unless json_link.nil?
   end
 
   def process_link_headers(link_header)
     json_link = link_header.find_link(%w(rel alternate), %w(type application/activity+json)) || link_header.find_link(%w(rel alternate), ['type', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'])
 
-    result = process(json_link.href, terminal: true) unless json_link.nil?
-    result ||= nil
-    result
+    process(json_link.href, terminal: true) unless json_link.nil?
   end
 
   def parse_link_header(response)
     LinkHeader.parse(response['Link'].is_a?(Array) ? response['Link'].first : response['Link'])
   end
-
-  def object_uri
-    nil
-  end
 end
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
index 93f5b6b16..e3f4bf19d 100644
--- a/app/services/follow_service.rb
+++ b/app/services/follow_service.rb
@@ -13,7 +13,7 @@ class FollowService < BaseService
     target_account = ResolveAccountService.new.call(target_account, skip_webfinger: true)
 
     raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
-    raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account) || target_account.moved?
+    raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account) || target_account.moved? || (!target_account.local? && target_account.ostatus?)
 
     target_account.mark_known! unless !Setting.auto_mark_known || target_account.known?
 
@@ -35,9 +35,11 @@ class FollowService < BaseService
     ActivityTracker.increment('activity:interactions')
 
     if target_account.local? && !target_account.locked?
-      follow = source_account.follow!(target_account, reblogs: reblogs)
-      LocalNotificationWorker.perform_async(target_account.id, follow.id, follow.class.name)
-      follow
+      direct_follow(source_account, target_account, reblogs: reblogs)
+#      follow = source_account.follow!(target_account, reblogs: reblogs)
+#      LocalNotificationWorker.perform_async(target_account.id, follow.id, follow.class.name)
+#      MergeWorker.perform_async(target_account.id, source_account.id)
+#      follow
     else
       request_follow(source_account, target_account, reblogs: reblogs)
     end
@@ -50,13 +52,22 @@ class FollowService < BaseService
 
     if target_account.local?
       LocalNotificationWorker.perform_async(target_account.id, follow_request.id, follow_request.class.name)
-    else
+    elsif target_account.activitypub?
       ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), source_account.id, target_account.inbox_url)
     end
 
     follow_request
   end
 
+  def direct_follow(source_account, target_account, reblogs: true)
+    follow = source_account.follow!(target_account, reblogs: reblogs)
+
+    LocalNotificationWorker.perform_async(target_account.id, follow.id, follow.class.name)
+    MergeWorker.perform_async(target_account.id, source_account.id)
+
+    follow
+  end
+
   def build_json(follow_request)
     Oj.dump(serialize_payload(follow_request, ActivityPub::FollowSerializer))
   end
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 2a065f58b..6718b5a69 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -254,6 +254,13 @@ class PostStatusService < BaseService
     end
   end
 
+  def postprocess_status!
+    LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text?
+    DistributionWorker.perform_async(@status.id)
+    ActivityPub::DistributionWorker.perform_async(@status.id) unless @status.local_only?
+    PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll
+  end
+
   def validate_media!
     return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
 
diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb
index 54a0adf45..74cbdd31c 100644
--- a/app/services/reblog_service.rb
+++ b/app/services/reblog_service.rb
@@ -26,6 +26,8 @@ class ReblogService < BaseService
 
       reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility)
     end
+    DistributionWorker.perform_async(reblog.id)
+    ActivityPub::DistributionWorker.perform_async(reblog.id) unless reblogged_status.local_only?
 
     if !options[:distribute] && account&.user&.boost_interval?
       QueuedBoost.find_or_create_by!(account_id: account.id, status_id: reblogged_status.id) if account&.user&.boost_interval?
diff --git a/app/services/reject_follow_service.rb b/app/services/reject_follow_service.rb
index 2e51b11d7..bc0000c8c 100644
--- a/app/services/reject_follow_service.rb
+++ b/app/services/reject_follow_service.rb
@@ -6,7 +6,7 @@ class RejectFollowService < BaseService
   def call(source_account, target_account)
     follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account)
     follow_request.reject!
-    create_notification(follow_request) unless source_account.local?
+    create_notification(follow_request) if !source_account.local? && source_account.activitypub?
     follow_request
   end
 
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index 797794a19..51d200361 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -7,14 +7,13 @@ class RemoveStatusService < BaseService
   MIN_SCHEDULE_OFFSET = 60.seconds.freeze
 
   def call(status, **options)
-    @payload      = Oj.dump(event: :delete, payload: status.id.to_s)
-    @status       = status
-    @account      = status.account
-    @tags         = status.tags.pluck(:name).to_a
-    @mentions     = status.active_mentions.includes(:account).to_a
-    @reblogs      = status.reblogs.includes(:account).to_a
-    @stream_entry = status.stream_entry
-    @options      = options
+    @payload  = Oj.dump(event: :delete, payload: status.id.to_s)
+    @status   = status
+    @account  = status.account
+    @tags     = status.tags.pluck(:name).to_a
+    @mentions = status.active_mentions.includes(:account).to_a
+    @reblogs  = status.reblogs.includes(:account).to_a
+    @options  = options
 
     unless options[:defederate_only]
       RedisLock.acquire(lock_options) do |lock|
@@ -29,6 +28,7 @@ class RemoveStatusService < BaseService
           remove_from_public
           remove_from_media if status.media_attachments.any?
           remove_from_direct if status.direct_visibility?
+          remove_from_spam_check
 
           @status.destroy!
         else
@@ -162,6 +162,10 @@ class RemoveStatusService < BaseService
     Redis.current.publish("timeline:direct:#{@account.id}", @payload) if @account.local?
   end
 
+  def remove_from_spam_check
+    redis.zremrangebyscore("spam_check:#{@status.account_id}", @status.id, @status.id)
+  end
+
   def lock_options
     { redis: Redis.current, key: "distribute:#{@status.id}" }
   end
diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb
index 415abd78f..7c03bfc3e 100644
--- a/app/services/resolve_account_service.rb
+++ b/app/services/resolve_account_service.rb
@@ -2,88 +2,114 @@
 
 class ResolveAccountService < BaseService
   include JsonLdHelper
+  include DomainControlHelper
 
-  DFRN_NS = 'http://purl.org/macgirvin/dfrn/1.0'
+  class WebfingerRedirectError < StandardError; end
 
-  # Find or create a local account for a remote user.
-  # When creating, look up the user's webfinger and fetch all
-  # important information from their feed
-  # @param [String, Account] uri User URI in the form of username@domain
+  # Find or create an account record for a remote user. When creating,
+  # look up the user's webfinger and fetch ActivityPub data
+  # @param [String, Account] uri URI in the username@domain format or account record
   # @param [Hash] options
+  # @option options [Boolean] :redirected Do not follow further Webfinger redirects
+  # @option options [Boolean] :skip_webfinger Do not attempt to refresh account data
   # @return [Account]
   def call(uri, options = {})
+    return if uri.blank?
+
+    process_options!(uri, options)
+
+    # First of all we want to check if we've got the account
+    # record with the URI already, and if so, we can exit early
+
+    return if domain_not_allowed?(@domain)
+
+    @account ||= Account.find_remote(@username, @domain)
+
+    return @account if @account&.local? || !webfinger_update_due?
+
+    # At this point we are in need of a Webfinger query, which may
+    # yield us a different username/domain through a redirect
+
+    process_webfinger!(@uri)
+
+    # Because the username/domain pair may be different than what
+    # we already checked, we need to check if we've already got
+    # the record with that URI, again
+
+    return if domain_not_allowed?(@domain)
+
+    @account ||= Account.find_remote(@username, @domain)
+
+    return @account if @account&.local? || !webfinger_update_due?
+
+    # Now it is certain, it is definitely a remote account, and it
+    # either needs to be created, or updated from fresh data
+
+    process_account!
+  rescue Goldfinger::Error, WebfingerRedirectError, Oj::ParseError => e
+    Rails.logger.debug "Webfinger query for #{@uri} failed: #{e}"
+    nil
+  end
+
+  private
+
+  def process_options!(uri, options)
     @options = options
 
     if uri.is_a?(Account)
       @account  = uri
       @username = @account.username
       @domain   = @account.domain
-      uri       = "#{@username}@#{@domain}"
-
-      return @account if @account.local? || !webfinger_update_due?
+      @uri      = [@username, @domain].compact.join('@')
     else
+      @uri               = uri
       @username, @domain = uri.split('@')
-
-      return Account.find_local(@username) if TagManager.instance.local_domain?(@domain)
-
-      @account = Account.find_remote(@username, @domain)
-
-      return @account unless webfinger_update_due?
     end
 
-    Rails.logger.debug "Looking up webfinger for #{uri}"
-
-    @webfinger = Goldfinger.finger("acct:#{uri}")
+    @domain = nil if TagManager.instance.local_domain?(@domain)
+  end
 
+  def process_webfinger!(uri, redirected = false)
+    @webfinger                           = Goldfinger.finger("acct:#{@uri}")
     confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@')
 
     if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
       @username = confirmed_username
       @domain   = confirmed_domain
-    elsif options[:redirected].nil?
-      return call("#{confirmed_username}@#{confirmed_domain}", options.merge(redirected: true))
+      @uri      = uri
+    elsif !redirected
+      return process_webfinger!("#{confirmed_username}@#{confirmed_domain}", true)
     else
-      Rails.logger.debug 'Requested and returned acct URIs do not match'
-      return
+      raise WebfingerRedirectError, "The URI #{uri} tries to hijack #{@username}@#{@domain}"
     end
 
+    @domain = nil if TagManager.instance.local_domain?(@domain)
+  end
+
+  def process_account!
     return unless activitypub_ready?
-    return Account.find_local(@username) if TagManager.instance.local_domain?(@domain)
 
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
         @account = Account.find_remote(@username, @domain)
-        handle_activitypub if activitypub_ready?
+
+        next if (@account.present? && !@account.activitypub?) || actor_json.nil?
+
+        @account = ActivityPub::ProcessAccountService.new.call(@username, @domain, actor_json)
       else
         raise Mastodon::RaceConditionError
       end
     end
 
     @account
-  rescue Goldfinger::Error => e
-    Rails.logger.debug "Webfinger query for #{uri} unsuccessful: #{e}"
-    nil
   end
 
-  private
-
   def webfinger_update_due?
     @account.nil? || @account.inbox_url.blank? || (!@options[:skip_webfinger] && @account.possibly_stale?)
   end
 
   def activitypub_ready?
-    !@webfinger.link('self').nil? &&
-      ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self').type) &&
-      !actor_json.nil? &&
-      actor_json['inbox'].present?
-  end
-
-  def handle_activitypub
-    return if actor_json.nil?
-
-    @account = ActivityPub::ProcessAccountService.new.call(@username, @domain, actor_json)
-  rescue Oj::ParseError
-    nil
+    !@webfinger.link('self').nil? && ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self').type)
   end
 
   def actor_url
diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb
index 0c265b0db..bab420945 100644
--- a/app/services/resolve_url_service.rb
+++ b/app/services/resolve_url_service.rb
@@ -4,37 +4,39 @@ class ResolveURLService < BaseService
   include JsonLdHelper
   include Authorization
 
-  attr_reader :url
-
   def call(url, on_behalf_of: nil)
-    @url = url
+    @url          = url
     @on_behalf_of = on_behalf_of
 
-    return process_local_url if local_url?
-
-    process_url unless fetched_atom_feed.nil?
+    if local_url?
+      process_local_url
+    elsif !fetched_resource.nil?
+      process_url
+    end
   end
 
   private
 
   def process_url
-    if equals_or_includes_any?(type, %w(Application Group Organization Person Service))
-      FetchRemoteAccountService.new.call(atom_url, body)
-    elsif equals_or_includes_any?(type, %w(Note Article Image Video Page Question))
-      FetchRemoteStatusService.new.call(atom_url, body, requested: true)
+    if equals_or_includes_any?(type, ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES)
+      FetchRemoteAccountService.new.call(resource_url, body)
+    elsif equals_or_includes_any?(type, ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
+      status = FetchRemoteStatusService.new.call(atom_url, body, requested: true)
+      authorize_with @on_behalf_of, status, :show? unless status.nil?
+      status
     end
   end
 
-  def fetched_atom_feed
-    @_fetched_atom_feed ||= FetchAtomService.new.call(url)
+  def fetched_resource
+    @fetched_resource ||= FetchResourceService.new.call(@url)
   end
 
-  def atom_url
-    fetched_atom_feed.first
+  def resource_url
+    fetched_resource.first
   end
 
   def body
-    fetched_atom_feed.second[:prefetched_body]
+    fetched_resource.second[:prefetched_body]
   end
 
   def type
@@ -42,7 +44,7 @@ class ResolveURLService < BaseService
   end
 
   def json_data
-    @_json_data ||= body_to_json(body)
+    @json_data ||= body_to_json(body)
   end
 
   def local_url?
@@ -54,10 +56,7 @@ class ResolveURLService < BaseService
 
     return unless recognized_params[:action] == 'show'
 
-    if recognized_params[:controller] == 'stream_entries'
-      status = StreamEntry.find_by(id: recognized_params[:id])&.status
-      check_local_status(status)
-    elsif recognized_params[:controller] == 'statuses'
+    if recognized_params[:controller] == 'statuses'
       status = Status.find_by(id: recognized_params[:id])
       check_local_status(status)
     elsif recognized_params[:controller] == 'accounts'
@@ -67,10 +66,10 @@ class ResolveURLService < BaseService
 
   def check_local_status(status)
     return if status.nil?
+
     authorize_with @on_behalf_of, status, :show?
     status
   rescue Mastodon::NotPermittedError
-    # Do not disclose the existence of status the user is not authorized to see
     nil
   end
 end
diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb
index f79257334..b592629a1 100644
--- a/app/services/suspend_account_service.rb
+++ b/app/services/suspend_account_service.rb
@@ -25,6 +25,7 @@ class SuspendAccountService < BaseService
     scheduled_statuses
     status_pins
     stream_entries
+    subscriptions
   ).freeze
 
   ASSOCIATIONS_ON_DESTROY = %w(
diff --git a/app/services/unblock_service.rb b/app/services/unblock_service.rb
index 24f567603..c263ac8af 100644
--- a/app/services/unblock_service.rb
+++ b/app/services/unblock_service.rb
@@ -7,7 +7,7 @@ class UnblockService < BaseService
     return unless account.blocking?(target_account)
 
     unblock = account.unblock!(target_account)
-    create_notification(unblock) unless target_account.local?
+    create_notification(unblock) if !target_account.local? && target_account.activitypub?
     unblock
   end
 
diff --git a/app/services/unfavourite_service.rb b/app/services/unfavourite_service.rb
index 88c288126..37917a64f 100644
--- a/app/services/unfavourite_service.rb
+++ b/app/services/unfavourite_service.rb
@@ -6,7 +6,7 @@ class UnfavouriteService < BaseService
   def call(account, status)
     favourite = Favourite.find_by!(account: account, status: status)
     favourite.destroy!
-    create_notification(favourite) unless status.local?
+    create_notification(favourite) if !status.account.local? && status.account.activitypub?
     favourite
   end
 
diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb
index 4404492a7..b7033d7eb 100644
--- a/app/services/unfollow_service.rb
+++ b/app/services/unfollow_service.rb
@@ -21,8 +21,8 @@ class UnfollowService < BaseService
     return unless follow
 
     follow.destroy!
-    create_notification(follow) unless @target_account.local?
-    create_reject_notification(follow) if @target_account.local? && !@source_account.local?
+    create_notification(follow) if !@target_account.local? && @target_account.activitypub?
+    create_reject_notification(follow) if @target_account.local? && !@source_account.local? && @source_account.activitypub?
     UnmergeWorker.perform_async(@target_account.id, @source_account.id)
     follow
   end
@@ -42,7 +42,6 @@ class UnfollowService < BaseService
   end
 
   def create_reject_notification(follow)
-    # Rejecting an already-existing follow request
     ActivityPub::DeliveryWorker.perform_async(build_reject_json(follow), follow.target_account_id, follow.account.inbox_url)
   end