Make follow requests federate
This commit is contained in:
		
							
								
								
									
										11
									
								
								app/services/authorize_follow_service.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/services/authorize_follow_service.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class AuthorizeFollowService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  def call(source_account, target_account)
 | 
			
		||||
    follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account)
 | 
			
		||||
    follow_request.authorize!
 | 
			
		||||
    NotificationWorker.perform_async(stream_entry_to_xml(follow_request.stream_entry), target_account.id, source_account.id) unless source_account.local?
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class BlockService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  def call(account, target_account)
 | 
			
		||||
    return if account.id == target_account.id
 | 
			
		||||
 | 
			
		||||
@@ -10,6 +12,6 @@ class BlockService < BaseService
 | 
			
		||||
    block = account.block!(target_account)
 | 
			
		||||
 | 
			
		||||
    BlockWorker.perform_async(account.id, target_account.id)
 | 
			
		||||
    NotificationWorker.perform_async(block.stream_entry.id, target_account.id) unless target_account.local?
 | 
			
		||||
    NotificationWorker.perform_async(stream_entry_to_xml(block.stream_entry), account.id, target_account.id) unless target_account.local?
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								app/services/concerns/stream_entry_renderer.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/services/concerns/stream_entry_renderer.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module StreamEntryRenderer
 | 
			
		||||
  def stream_entry_to_xml(stream_entry)
 | 
			
		||||
    renderer = StreamEntriesController.renderer.new(method: 'get', http_host: Rails.configuration.x.local_domain, https: Rails.configuration.x.use_https)
 | 
			
		||||
    renderer.render(:show, assigns: { stream_entry: stream_entry }, formats: [:atom])
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class FavouriteService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  # Favourite a status and notify remote user
 | 
			
		||||
  # @param [Account] account
 | 
			
		||||
  # @param [Status] status
 | 
			
		||||
@@ -15,7 +17,7 @@ class FavouriteService < BaseService
 | 
			
		||||
    if status.local?
 | 
			
		||||
      NotifyService.new.call(favourite.status.account, favourite)
 | 
			
		||||
    else
 | 
			
		||||
      NotificationWorker.perform_async(favourite.stream_entry.id, status.account_id)
 | 
			
		||||
      NotificationWorker.perform_async(stream_entry_to_xml(favourite.stream_entry), account.id, status.account_id)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    favourite
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class FollowService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  # Follow a remote user, notify remote user about the follow
 | 
			
		||||
  # @param [Account] source_account From which to follow
 | 
			
		||||
  # @param [String] uri User URI to follow in the form of username@domain
 | 
			
		||||
@@ -20,10 +22,13 @@ class FollowService < BaseService
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def request_follow(source_account, target_account)
 | 
			
		||||
    return unless target_account.local?
 | 
			
		||||
 | 
			
		||||
    follow_request = FollowRequest.create!(account: source_account, target_account: target_account)
 | 
			
		||||
    NotifyService.new.call(target_account, follow_request)
 | 
			
		||||
 | 
			
		||||
    if target_account.local?
 | 
			
		||||
      NotifyService.new.call(target_account, follow_request)
 | 
			
		||||
    else
 | 
			
		||||
      NotificationWorker.perform_async(stream_entry_to_xml(follow_request.stream_entry), source_account.id, target_account.id)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    follow_request
 | 
			
		||||
  end
 | 
			
		||||
@@ -35,7 +40,7 @@ class FollowService < BaseService
 | 
			
		||||
      NotifyService.new.call(target_account, follow)
 | 
			
		||||
    else
 | 
			
		||||
      subscribe_service.call(target_account)
 | 
			
		||||
      NotificationWorker.perform_async(follow.stream_entry.id, target_account.id)
 | 
			
		||||
      NotificationWorker.perform_async(stream_entry_to_xml(follow.stream_entry), source_account.id, target_account.id)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    MergeWorker.perform_async(target_account.id, source_account.id)
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,10 @@ class ProcessInteractionService < BaseService
 | 
			
		||||
      case verb(xml)
 | 
			
		||||
      when :follow
 | 
			
		||||
        follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account)
 | 
			
		||||
      when :request_friend
 | 
			
		||||
        follow_request!(account, target_account) unless !target_account.locked? || target_account.blocking?(account)
 | 
			
		||||
      when :authorize
 | 
			
		||||
        authorize_follow_request!(account, target_account)
 | 
			
		||||
      when :unfollow
 | 
			
		||||
        unfollow!(account, target_account)
 | 
			
		||||
      when :favorite
 | 
			
		||||
@@ -72,6 +76,16 @@ class ProcessInteractionService < BaseService
 | 
			
		||||
    NotifyService.new.call(target_account, follow)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def follow_request(account, target_account)
 | 
			
		||||
    follow_request = FollowRequest.create!(account: account, target_account: target_account)
 | 
			
		||||
    NotifyService.new.call(target_account, follow_request)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def authorize_target_account!(account, target_account)
 | 
			
		||||
    follow_request = FollowRequest.find_by(account: target_account, target_account: account)
 | 
			
		||||
    follow_request&.authorize!
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def unfollow!(account, target_account)
 | 
			
		||||
    account.unfollow!(target_account)
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class ProcessMentionsService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  # Scan status for mentions and fetch remote mentioned users, create
 | 
			
		||||
  # local mention pointers, send Salmon notifications to mentioned
 | 
			
		||||
  # remote users
 | 
			
		||||
@@ -33,7 +35,7 @@ class ProcessMentionsService < BaseService
 | 
			
		||||
      if mentioned_account.local?
 | 
			
		||||
        NotifyService.new.call(mentioned_account, mention)
 | 
			
		||||
      else
 | 
			
		||||
        NotificationWorker.perform_async(status.stream_entry.id, mentioned_account.id)
 | 
			
		||||
        NotificationWorker.perform_async(stream_entry_to_xml(status.stream_entry), status.account_id, mentioned_account.id)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class ReblogService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  # Reblog a status and notify its remote author
 | 
			
		||||
  # @param [Account] account Account to reblog from
 | 
			
		||||
  # @param [Status] reblogged_status Status to be reblogged
 | 
			
		||||
@@ -18,15 +20,9 @@ class ReblogService < BaseService
 | 
			
		||||
    if reblogged_status.local?
 | 
			
		||||
      NotifyService.new.call(reblog.reblog.account, reblog)
 | 
			
		||||
    else
 | 
			
		||||
      NotificationWorker.perform_async(reblog.stream_entry.id, reblog.reblog.account_id)
 | 
			
		||||
      NotificationWorker.perform_async(stream_entry_to_xml(reblog.stream_entry), account.id, reblog.reblog.account_id)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    reblog
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def send_interaction_service
 | 
			
		||||
    @send_interaction_service ||= SendInteractionService.new
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								app/services/reject_follow_service.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/services/reject_follow_service.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class RejectFollowService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  def call(source_account, target_account)
 | 
			
		||||
    follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account)
 | 
			
		||||
    follow_request.reject!
 | 
			
		||||
    NotificationWorker.perform_async(stream_entry_to_xml(follow_request.stream_entry), target_account.id, source_account.id) unless source_account.local?
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class RemoveStatusService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  def call(status)
 | 
			
		||||
    remove_from_self(status) if status.account.local?
 | 
			
		||||
    remove_from_followers(status)
 | 
			
		||||
@@ -43,7 +45,7 @@ class RemoveStatusService < BaseService
 | 
			
		||||
 | 
			
		||||
  def send_delete_salmon(account, status)
 | 
			
		||||
    return unless status.local?
 | 
			
		||||
    NotificationWorker.perform_async(status.stream_entry.id, account.id)
 | 
			
		||||
    NotificationWorker.perform_async(stream_entry_to_xml(status.stream_entry), status.account_id, account.id)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def remove_reblogs(status)
 | 
			
		||||
 
 | 
			
		||||
@@ -2,27 +2,16 @@
 | 
			
		||||
 | 
			
		||||
class SendInteractionService < BaseService
 | 
			
		||||
  # Send an Atom representation of an interaction to a remote Salmon endpoint
 | 
			
		||||
  # @param [StreamEntry] stream_entry
 | 
			
		||||
  # @param [String] Entry XML
 | 
			
		||||
  # @param [Account] source_account
 | 
			
		||||
  # @param [Account] target_account
 | 
			
		||||
  def call(stream_entry, target_account)
 | 
			
		||||
    envelope = salmon.pack(entry_xml(stream_entry), stream_entry.account.keypair)
 | 
			
		||||
  def call(xml, source_account, target_account)
 | 
			
		||||
    envelope = salmon.pack(xml, source_account.keypair)
 | 
			
		||||
    salmon.post(target_account.salmon_url, envelope)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def entry_xml(stream_entry)
 | 
			
		||||
    Nokogiri::XML::Builder.new do |xml|
 | 
			
		||||
      entry(xml, true) do
 | 
			
		||||
        author(xml) do
 | 
			
		||||
          include_author xml, stream_entry.account
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        include_entry xml, stream_entry
 | 
			
		||||
      end
 | 
			
		||||
    end.to_xml
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def salmon
 | 
			
		||||
    @salmon ||= OStatus2::Salmon.new
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,12 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class UnblockService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  def call(account, target_account)
 | 
			
		||||
    return unless account.blocking?(target_account)
 | 
			
		||||
 | 
			
		||||
    unblock = account.unblock!(target_account)
 | 
			
		||||
    NotificationWorker.perform_async(unblock.stream_entry.id, target_account.id) unless target_account.local?
 | 
			
		||||
    NotificationWorker.perform_async(stream_entry_to_xml(unblock.stream_entry), account.id, target_account.id) unless target_account.local?
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class UnfavouriteService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  def call(account, status)
 | 
			
		||||
    favourite = Favourite.find_by!(account: account, status: status)
 | 
			
		||||
    favourite.destroy!
 | 
			
		||||
 | 
			
		||||
    unless status.local?
 | 
			
		||||
      NotificationWorker.perform_async(favourite.stream_entry.id, status.account_id)
 | 
			
		||||
      NotificationWorker.perform_async(stream_entry_to_xml(favourite.stream_entry), account.id, status.account_id)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    favourite
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class UnfollowService < BaseService
 | 
			
		||||
  include StreamEntryRenderer
 | 
			
		||||
 | 
			
		||||
  # Unfollow and notify the remote user
 | 
			
		||||
  # @param [Account] source_account Where to unfollow from
 | 
			
		||||
  # @param [Account] target_account Which to unfollow
 | 
			
		||||
  def call(source_account, target_account)
 | 
			
		||||
    follow = source_account.unfollow!(target_account)
 | 
			
		||||
    NotificationWorker.perform_async(follow.stream_entry.id, target_account.id) unless target_account.local?
 | 
			
		||||
    NotificationWorker.perform_async(stream_entry_to_xml(follow.stream_entry), source_account.id, target_account.id) unless target_account.local?
 | 
			
		||||
    UnmergeWorker.perform_async(target_account.id, source_account.id)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ class UpdateRemoteProfileService < BaseService
 | 
			
		||||
    unless author_xml.nil?
 | 
			
		||||
      account.display_name = author_xml.at_xpath('./poco:displayName', poco: TagManager::POCO_XMLNS).content unless author_xml.at_xpath('./poco:displayName', poco: TagManager::POCO_XMLNS).nil?
 | 
			
		||||
      account.note         = author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).content unless author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).nil?
 | 
			
		||||
      account.locked       = author_xml.at_xpath('./mastodon:scope', mastodon: TagManager::MTDN_XMLNS)&.content == 'private'
 | 
			
		||||
 | 
			
		||||
      unless account.suspended? || DomainBlock.find_by(domain: account.domain)&.reject_media?
 | 
			
		||||
        account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'] unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS).nil? || author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'].blank?
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user