about summary refs log tree commit diff
path: root/app/services/process_interaction_service.rb
blob: 1fca3832b74fc65251111d3d5b716aabf226204e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# frozen_string_literal: true

class ProcessInteractionService < BaseService
  include AuthorExtractor
  include Authorization

  # Record locally the remote interaction with our user
  # @param [String] envelope Salmon envelope
  # @param [Account] target_account Account the Salmon was addressed to
  def call(envelope, target_account)
    body = salmon.unpack(envelope)

    xml = Nokogiri::XML(body)
    xml.encoding = 'utf-8'

    account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: OStatus::TagManager::XMLNS))

    return if account.nil? || account.suspended?

    if salmon.verify(envelope, account.keypair)
      RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true)

      case verb(xml)
      when :follow
        follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account) || target_account.domain_blocking?(account.domain)
      when :request_friend
        follow_request!(account, target_account) unless !target_account.locked? || target_account.blocking?(account) || target_account.domain_blocking?(account.domain)
      when :authorize
        authorize_follow_request!(account, target_account)
      when :reject
        reject_follow_request!(account, target_account)
      when :unfollow
        unfollow!(account, target_account)
      when :favorite
        favourite!(xml, account)
      when :unfavorite
        unfavourite!(xml, account)
      when :post
        add_post!(body, account) if mentions_account?(xml, target_account)
      when :share
        add_post!(body, account) unless status(xml).nil?
      when :delete
        delete_post!(xml, account)
      when :block
        reflect_block!(account, target_account)
      when :unblock
        reflect_unblock!(account, target_account)
      end
    end
  rescue HTTP::Error, OStatus2::BadSalmonError, Mastodon::NotPermittedError
    nil
  end

  private

  def mentions_account?(xml, account)
    xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]', xmlns: OStatus::TagManager::XMLNS).each { |mention_link| return true if [OStatus::TagManager.instance.uri_for(account), OStatus::TagManager.instance.url_for(account)].include?(mention_link.attribute('href').value) }
    false
  end

  def verb(xml)
    raw = xml.at_xpath('//activity:verb', activity: OStatus::TagManager::AS_XMLNS).content
    OStatus::TagManager::VERBS.key(raw)
  rescue
    :post
  end

  def follow!(account, target_account)
    follow = account.follow!(target_account)
    FollowRequest.find_by(account: account, target_account: target_account)&.destroy
    NotifyService.new.call(target_account, follow)
  end

  def follow_request!(account, target_account)
    return if account.requested?(target_account)

    follow_request = FollowRequest.create!(account: account, target_account: target_account)
    NotifyService.new.call(target_account, follow_request)
  end

  def authorize_follow_request!(account, target_account)
    follow_request = FollowRequest.find_by(account: target_account, target_account: account)
    follow_request&.authorize!
    Pubsubhubbub::SubscribeWorker.perform_async(account.id) unless account.subscribed?
  end

  def reject_follow_request!(account, target_account)
    follow_request = FollowRequest.find_by(account: target_account, target_account: account)
    follow_request&.reject!
  end

  def unfollow!(account, target_account)
    account.unfollow!(target_account)
    FollowRequest.find_by(account: account, target_account: target_account)&.destroy
  end

  def reflect_block!(account, target_account)
    UnfollowService.new.call(target_account, account) if target_account.following?(account)
    account.block!(target_account)
  end

  def reflect_unblock!(account, target_account)
    UnblockService.new.call(account, target_account)
  end

  def delete_post!(xml, account)
    status = Status.find(xml.at_xpath('//xmlns:id', xmlns: OStatus::TagManager::XMLNS).content)

    return if status.nil?

    authorize_with account, status, :destroy?

    RemovalWorker.perform_async(status.id)
  end

  def favourite!(xml, from_account)
    current_status = status(xml)

    return if current_status.nil?

    favourite = current_status.favourites.where(account: from_account).first_or_create!(account: from_account)
    NotifyService.new.call(current_status.account, favourite)
  end

  def unfavourite!(xml, from_account)
    current_status = status(xml)

    return if current_status.nil?

    favourite = current_status.favourites.where(account: from_account).first
    favourite&.destroy
  end

  def add_post!(body, account)
    ProcessingWorker.perform_async(account.id, body.force_encoding('UTF-8'))
  end

  def status(xml)
    uri = activity_id(xml)
    return nil unless OStatus::TagManager.instance.local_id?(uri)
    Status.find(OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Status'))
  end

  def activity_id(xml)
    xml.at_xpath('//activity:object', activity: OStatus::TagManager::AS_XMLNS).at_xpath('./xmlns:id', xmlns: OStatus::TagManager::XMLNS).content
  end

  def salmon
    @salmon ||= OStatus2::Salmon.new
  end
end