Make follow requests federate
This commit is contained in:
		
							parent
							
								
									b612b57b5d
								
							
						
					
					
						commit
						59c8c2b28a
					
				
					 25 changed files with 148 additions and 61 deletions
				
			
		| 
						 | 
					@ -18,12 +18,12 @@ class Api::V1::FollowRequestsController < ApiController
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def authorize
 | 
					  def authorize
 | 
				
			||||||
    FollowRequest.find_by!(account_id: params[:id], target_account: current_account).authorize!
 | 
					    AuthorizeFollowService.new.call(Account.find(params[:id]), current_account)
 | 
				
			||||||
    render_empty
 | 
					    render_empty
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def reject
 | 
					  def reject
 | 
				
			||||||
    FollowRequest.find_by!(account_id: params[:id], target_account: current_account).reject!
 | 
					    RejectFollowService.new.call(Account.find(params[:id]), current_account)
 | 
				
			||||||
    render_empty
 | 
					    render_empty
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module ObfuscateFilename
 | 
					module ObfuscateFilename
 | 
				
			||||||
  extend ActiveSupport::Concern
 | 
					  extend ActiveSupport::Concern
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -143,6 +143,10 @@ module AtomBuilderHelper
 | 
				
			||||||
    xml.link(:rel => 'mentioned', :href => TagManager::COLLECTIONS[:public], 'ostatus:object-type' => TagManager::TYPES[:collection])
 | 
					    xml.link(:rel => 'mentioned', :href => TagManager::COLLECTIONS[:public], 'ostatus:object-type' => TagManager::TYPES[:collection])
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def privacy_scope(xml, level)
 | 
				
			||||||
 | 
					    xml['mastodon'].scope(level)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def include_author(xml, account)
 | 
					  def include_author(xml, account)
 | 
				
			||||||
    object_type      xml, :person
 | 
					    object_type      xml, :person
 | 
				
			||||||
    uri              xml, TagManager.instance.uri_for(account)
 | 
					    uri              xml, TagManager.instance.uri_for(account)
 | 
				
			||||||
| 
						 | 
					@ -152,6 +156,7 @@ module AtomBuilderHelper
 | 
				
			||||||
    link_alternate   xml, TagManager.instance.url_for(account)
 | 
					    link_alternate   xml, TagManager.instance.url_for(account)
 | 
				
			||||||
    link_avatar      xml, account
 | 
					    link_avatar      xml, account
 | 
				
			||||||
    portable_contact xml, account
 | 
					    portable_contact xml, account
 | 
				
			||||||
 | 
					    privacy_scope    xml, account.locked? ? :private : :public
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def rich_content(xml, activity)
 | 
					  def rich_content(xml, activity)
 | 
				
			||||||
| 
						 | 
					@ -216,6 +221,7 @@ module AtomBuilderHelper
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          category(xml, 'nsfw') if stream_entry.target.sensitive?
 | 
					          category(xml, 'nsfw') if stream_entry.target.sensitive?
 | 
				
			||||||
 | 
					          privacy_scope(xml, stream_entry.target.visibility)
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -237,6 +243,7 @@ module AtomBuilderHelper
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    category(xml, 'nsfw') if stream_entry.activity.sensitive?
 | 
					    category(xml, 'nsfw') if stream_entry.activity.sensitive?
 | 
				
			||||||
 | 
					    privacy_scope(xml, stream_entry.activity.visibility)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
| 
						 | 
					@ -249,6 +256,7 @@ module AtomBuilderHelper
 | 
				
			||||||
               'xmlns:poco'     => TagManager::POCO_XMLNS,
 | 
					               'xmlns:poco'     => TagManager::POCO_XMLNS,
 | 
				
			||||||
               'xmlns:media'    => TagManager::MEDIA_XMLNS,
 | 
					               'xmlns:media'    => TagManager::MEDIA_XMLNS,
 | 
				
			||||||
               'xmlns:ostatus'  => TagManager::OS_XMLNS,
 | 
					               'xmlns:ostatus'  => TagManager::OS_XMLNS,
 | 
				
			||||||
 | 
					               'xmlns:mastodon' => TagManager::MTDN_XMLNS,
 | 
				
			||||||
             }, &block)
 | 
					             }, &block)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,9 @@ class TagManager
 | 
				
			||||||
    unfavorite:     'http://activitystrea.ms/schema/1.0/unfavorite',
 | 
					    unfavorite:     'http://activitystrea.ms/schema/1.0/unfavorite',
 | 
				
			||||||
    delete:         'http://activitystrea.ms/schema/1.0/delete',
 | 
					    delete:         'http://activitystrea.ms/schema/1.0/delete',
 | 
				
			||||||
    follow:         'http://activitystrea.ms/schema/1.0/follow',
 | 
					    follow:         'http://activitystrea.ms/schema/1.0/follow',
 | 
				
			||||||
 | 
					    request_friend: 'http://activitystrea.ms/schema/1.0/request-friend',
 | 
				
			||||||
 | 
					    authorize:      'http://activitystrea.ms/schema/1.0/authorize',
 | 
				
			||||||
 | 
					    reject:         'http://activitystrea.ms/schema/1.0/reject',
 | 
				
			||||||
    unfollow:       'http://ostatus.org/schema/1.0/unfollow',
 | 
					    unfollow:       'http://ostatus.org/schema/1.0/unfollow',
 | 
				
			||||||
    block:          'http://mastodon.social/schema/1.0/block',
 | 
					    block:          'http://mastodon.social/schema/1.0/block',
 | 
				
			||||||
    unblock:        'http://mastodon.social/schema/1.0/unblock',
 | 
					    unblock:        'http://mastodon.social/schema/1.0/unblock',
 | 
				
			||||||
| 
						 | 
					@ -38,6 +41,7 @@ class TagManager
 | 
				
			||||||
  POCO_XMLNS  = 'http://portablecontacts.net/spec/1.0'
 | 
					  POCO_XMLNS  = 'http://portablecontacts.net/spec/1.0'
 | 
				
			||||||
  DFRN_XMLNS  = 'http://purl.org/macgirvin/dfrn/1.0'
 | 
					  DFRN_XMLNS  = 'http://purl.org/macgirvin/dfrn/1.0'
 | 
				
			||||||
  OS_XMLNS    = 'http://ostatus.org/schema/1.0'
 | 
					  OS_XMLNS    = 'http://ostatus.org/schema/1.0'
 | 
				
			||||||
 | 
					  MTDN_XMLNS  = 'http://mastodon.social/schema/1.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def unique_tag(date, id, type)
 | 
					  def unique_tag(date, id, type)
 | 
				
			||||||
    "tag:#{Rails.configuration.x.local_domain},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}"
 | 
					    "tag:#{Rails.configuration.x.local_domain},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,11 +12,11 @@ class Favourite < ApplicationRecord
 | 
				
			||||||
  validates :status_id, uniqueness: { scope: :account_id }
 | 
					  validates :status_id, uniqueness: { scope: :account_id }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def verb
 | 
					  def verb
 | 
				
			||||||
    :favorite
 | 
					    destroyed? ? :unfavorite : :favorite
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def title
 | 
					  def title
 | 
				
			||||||
    "#{account.acct} favourited a status by #{status.account.acct}"
 | 
					    destroyed? ? "#{account.acct} no longer favourites a status by #{status.account.acct}" : "#{account.acct} favourited a status by #{status.account.acct}"
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  delegate :object_type, to: :target
 | 
					  delegate :object_type, to: :target
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FollowRequest < ApplicationRecord
 | 
					class FollowRequest < ApplicationRecord
 | 
				
			||||||
  include Paginable
 | 
					  include Paginable
 | 
				
			||||||
 | 
					  include Streamable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  belongs_to :account
 | 
					  belongs_to :account
 | 
				
			||||||
  belongs_to :target_account, class_name: 'Account'
 | 
					  belongs_to :target_account, class_name: 'Account'
 | 
				
			||||||
| 
						 | 
					@ -12,12 +13,47 @@ class FollowRequest < ApplicationRecord
 | 
				
			||||||
  validates :account_id, uniqueness: { scope: :target_account_id }
 | 
					  validates :account_id, uniqueness: { scope: :target_account_id }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def authorize!
 | 
					  def authorize!
 | 
				
			||||||
 | 
					    @verb = :authorize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    account.follow!(target_account)
 | 
					    account.follow!(target_account)
 | 
				
			||||||
    MergeWorker.perform_async(target_account.id, account.id)
 | 
					    MergeWorker.perform_async(target_account.id, account.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    destroy!
 | 
					    destroy!
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def reject!
 | 
					  def reject!
 | 
				
			||||||
 | 
					    @verb = :reject
 | 
				
			||||||
    destroy!
 | 
					    destroy!
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def verb
 | 
				
			||||||
 | 
					    destroyed? ? (@verb || :delete) : :request_friend
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def target
 | 
				
			||||||
 | 
					    target_account
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def object_type
 | 
				
			||||||
 | 
					    :person
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def hidden?
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def title
 | 
				
			||||||
 | 
					    if destroyed?
 | 
				
			||||||
 | 
					      case @verb
 | 
				
			||||||
 | 
					      when :authorize
 | 
				
			||||||
 | 
					        "#{target_account.acct} authorized #{account.acct}'s request to follow"
 | 
				
			||||||
 | 
					      when :reject
 | 
				
			||||||
 | 
					        "#{target_account.acct} rejected #{account.acct}'s request to follow"
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        "#{account.acct} withdrew the request to follow #{target_account.acct}"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      "#{account.acct} requested to follow #{target_account.acct}"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@ class StreamEntry < ApplicationRecord
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def targeted?
 | 
					  def targeted?
 | 
				
			||||||
    [:follow, :unfollow, :block, :unblock, :share, :favorite].include? verb
 | 
					    [:follow, :request_friend, :authorize, :unfollow, :block, :unblock, :share, :favorite].include? verb
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def target
 | 
					  def target
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										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
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BlockService < BaseService
 | 
					class BlockService < BaseService
 | 
				
			||||||
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def call(account, target_account)
 | 
					  def call(account, target_account)
 | 
				
			||||||
    return if account.id == target_account.id
 | 
					    return if account.id == target_account.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +12,6 @@ class BlockService < BaseService
 | 
				
			||||||
    block = account.block!(target_account)
 | 
					    block = account.block!(target_account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BlockWorker.perform_async(account.id, target_account.id)
 | 
					    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
 | 
				
			||||||
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
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FavouriteService < BaseService
 | 
					class FavouriteService < BaseService
 | 
				
			||||||
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Favourite a status and notify remote user
 | 
					  # Favourite a status and notify remote user
 | 
				
			||||||
  # @param [Account] account
 | 
					  # @param [Account] account
 | 
				
			||||||
  # @param [Status] status
 | 
					  # @param [Status] status
 | 
				
			||||||
| 
						 | 
					@ -15,7 +17,7 @@ class FavouriteService < BaseService
 | 
				
			||||||
    if status.local?
 | 
					    if status.local?
 | 
				
			||||||
      NotifyService.new.call(favourite.status.account, favourite)
 | 
					      NotifyService.new.call(favourite.status.account, favourite)
 | 
				
			||||||
    else
 | 
					    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
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    favourite
 | 
					    favourite
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,8 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FollowService < BaseService
 | 
					class FollowService < BaseService
 | 
				
			||||||
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Follow a remote user, notify remote user about the follow
 | 
					  # Follow a remote user, notify remote user about the follow
 | 
				
			||||||
  # @param [Account] source_account From which to follow
 | 
					  # @param [Account] source_account From which to follow
 | 
				
			||||||
  # @param [String] uri User URI to follow in the form of username@domain
 | 
					  # @param [String] uri User URI to follow in the form of username@domain
 | 
				
			||||||
| 
						 | 
					@ -20,10 +22,13 @@ class FollowService < BaseService
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def request_follow(source_account, target_account)
 | 
					  def request_follow(source_account, target_account)
 | 
				
			||||||
    return unless target_account.local?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    follow_request = FollowRequest.create!(account: source_account, target_account: target_account)
 | 
					    follow_request = FollowRequest.create!(account: source_account, target_account: target_account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if target_account.local?
 | 
				
			||||||
      NotifyService.new.call(target_account, follow_request)
 | 
					      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
 | 
					    follow_request
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					@ -35,7 +40,7 @@ class FollowService < BaseService
 | 
				
			||||||
      NotifyService.new.call(target_account, follow)
 | 
					      NotifyService.new.call(target_account, follow)
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      subscribe_service.call(target_account)
 | 
					      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
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MergeWorker.perform_async(target_account.id, source_account.id)
 | 
					    MergeWorker.perform_async(target_account.id, source_account.id)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,10 @@ class ProcessInteractionService < BaseService
 | 
				
			||||||
      case verb(xml)
 | 
					      case verb(xml)
 | 
				
			||||||
      when :follow
 | 
					      when :follow
 | 
				
			||||||
        follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account)
 | 
					        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
 | 
					      when :unfollow
 | 
				
			||||||
        unfollow!(account, target_account)
 | 
					        unfollow!(account, target_account)
 | 
				
			||||||
      when :favorite
 | 
					      when :favorite
 | 
				
			||||||
| 
						 | 
					@ -72,6 +76,16 @@ class ProcessInteractionService < BaseService
 | 
				
			||||||
    NotifyService.new.call(target_account, follow)
 | 
					    NotifyService.new.call(target_account, follow)
 | 
				
			||||||
  end
 | 
					  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)
 | 
					  def unfollow!(account, target_account)
 | 
				
			||||||
    account.unfollow!(target_account)
 | 
					    account.unfollow!(target_account)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,8 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProcessMentionsService < BaseService
 | 
					class ProcessMentionsService < BaseService
 | 
				
			||||||
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Scan status for mentions and fetch remote mentioned users, create
 | 
					  # Scan status for mentions and fetch remote mentioned users, create
 | 
				
			||||||
  # local mention pointers, send Salmon notifications to mentioned
 | 
					  # local mention pointers, send Salmon notifications to mentioned
 | 
				
			||||||
  # remote users
 | 
					  # remote users
 | 
				
			||||||
| 
						 | 
					@ -33,7 +35,7 @@ class ProcessMentionsService < BaseService
 | 
				
			||||||
      if mentioned_account.local?
 | 
					      if mentioned_account.local?
 | 
				
			||||||
        NotifyService.new.call(mentioned_account, mention)
 | 
					        NotifyService.new.call(mentioned_account, mention)
 | 
				
			||||||
      else
 | 
					      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
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,8 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ReblogService < BaseService
 | 
					class ReblogService < BaseService
 | 
				
			||||||
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Reblog a status and notify its remote author
 | 
					  # Reblog a status and notify its remote author
 | 
				
			||||||
  # @param [Account] account Account to reblog from
 | 
					  # @param [Account] account Account to reblog from
 | 
				
			||||||
  # @param [Status] reblogged_status Status to be reblogged
 | 
					  # @param [Status] reblogged_status Status to be reblogged
 | 
				
			||||||
| 
						 | 
					@ -18,15 +20,9 @@ class ReblogService < BaseService
 | 
				
			||||||
    if reblogged_status.local?
 | 
					    if reblogged_status.local?
 | 
				
			||||||
      NotifyService.new.call(reblog.reblog.account, reblog)
 | 
					      NotifyService.new.call(reblog.reblog.account, reblog)
 | 
				
			||||||
    else
 | 
					    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
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    reblog
 | 
					    reblog
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					 | 
				
			||||||
  private
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def send_interaction_service
 | 
					 | 
				
			||||||
    @send_interaction_service ||= SendInteractionService.new
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
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
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RemoveStatusService < BaseService
 | 
					class RemoveStatusService < BaseService
 | 
				
			||||||
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def call(status)
 | 
					  def call(status)
 | 
				
			||||||
    remove_from_self(status) if status.account.local?
 | 
					    remove_from_self(status) if status.account.local?
 | 
				
			||||||
    remove_from_followers(status)
 | 
					    remove_from_followers(status)
 | 
				
			||||||
| 
						 | 
					@ -43,7 +45,7 @@ class RemoveStatusService < BaseService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def send_delete_salmon(account, status)
 | 
					  def send_delete_salmon(account, status)
 | 
				
			||||||
    return unless status.local?
 | 
					    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
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def remove_reblogs(status)
 | 
					  def remove_reblogs(status)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,27 +2,16 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SendInteractionService < BaseService
 | 
					class SendInteractionService < BaseService
 | 
				
			||||||
  # Send an Atom representation of an interaction to a remote Salmon endpoint
 | 
					  # 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
 | 
					  # @param [Account] target_account
 | 
				
			||||||
  def call(stream_entry, target_account)
 | 
					  def call(xml, source_account, target_account)
 | 
				
			||||||
    envelope = salmon.pack(entry_xml(stream_entry), stream_entry.account.keypair)
 | 
					    envelope = salmon.pack(xml, source_account.keypair)
 | 
				
			||||||
    salmon.post(target_account.salmon_url, envelope)
 | 
					    salmon.post(target_account.salmon_url, envelope)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  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
 | 
					  def salmon
 | 
				
			||||||
    @salmon ||= OStatus2::Salmon.new
 | 
					    @salmon ||= OStatus2::Salmon.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,12 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UnblockService < BaseService
 | 
					class UnblockService < BaseService
 | 
				
			||||||
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def call(account, target_account)
 | 
					  def call(account, target_account)
 | 
				
			||||||
    return unless account.blocking?(target_account)
 | 
					    return unless account.blocking?(target_account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    unblock = account.unblock!(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
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,14 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UnfavouriteService < BaseService
 | 
					class UnfavouriteService < BaseService
 | 
				
			||||||
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def call(account, status)
 | 
					  def call(account, status)
 | 
				
			||||||
    favourite = Favourite.find_by!(account: account, status: status)
 | 
					    favourite = Favourite.find_by!(account: account, status: status)
 | 
				
			||||||
    favourite.destroy!
 | 
					    favourite.destroy!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    unless status.local?
 | 
					    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
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    favourite
 | 
					    favourite
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,14 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UnfollowService < BaseService
 | 
					class UnfollowService < BaseService
 | 
				
			||||||
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Unfollow and notify the remote user
 | 
					  # Unfollow and notify the remote user
 | 
				
			||||||
  # @param [Account] source_account Where to unfollow from
 | 
					  # @param [Account] source_account Where to unfollow from
 | 
				
			||||||
  # @param [Account] target_account Which to unfollow
 | 
					  # @param [Account] target_account Which to unfollow
 | 
				
			||||||
  def call(source_account, target_account)
 | 
					  def call(source_account, target_account)
 | 
				
			||||||
    follow = source_account.unfollow!(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)
 | 
					    UnmergeWorker.perform_async(target_account.id, source_account.id)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ class UpdateRemoteProfileService < BaseService
 | 
				
			||||||
    unless author_xml.nil?
 | 
					    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.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.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?
 | 
					      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?
 | 
					        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?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ class NotificationWorker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sidekiq_options retry: 5
 | 
					  sidekiq_options retry: 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def perform(stream_entry_id, target_account_id)
 | 
					  def perform(xml, source_account_id, target_account_id)
 | 
				
			||||||
    SendInteractionService.new.call(StreamEntry.find(stream_entry_id), Account.find(target_account_id))
 | 
					    SendInteractionService.new.call(xml, Account.find(source_account_id), Account.find(target_account_id))
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class PushNotificationWorker
 | 
					 | 
				
			||||||
  include Sidekiq::Worker
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def perform(notification_id)
 | 
					 | 
				
			||||||
    SendPushNotificationService.new.call(Notification.find(notification_id))
 | 
					 | 
				
			||||||
  rescue ActiveRecord::RecordNotFound
 | 
					 | 
				
			||||||
    true
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ RSpec.describe AtomBuilderHelper, type: :helper do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe '#feed' do
 | 
					  describe '#feed' do
 | 
				
			||||||
    it 'creates a feed' do
 | 
					    it 'creates a feed' do
 | 
				
			||||||
      expect(used_in_builder { |xml| helper.feed(xml) }).to match '<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0"/>'
 | 
					      expect(used_in_builder { |xml| helper.feed(xml) }).to match '<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0"/>'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue