about summary refs log tree commit diff
path: root/app/services/resolve_mentions_service.rb
blob: 2fcb176b2f73beb96d235a587e1aa635202fcb8d (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
# frozen_string_literal: true

class ResolveMentionsService < BaseService
  # Scan text for mentions and create local mention pointers
  # @param [Status] status Status to attach to mention pointers
  # @option [String] :text Text containing mentions to resolve (default: use status text)
  # @option [Enumerable] :mentions Additional mentions to include
  # @option [Boolean] :reveal_implicit_mentions Append implicit mentions to text
  # @return [Array] Array containing text with mentions resolved (String) and mention pointers (Set)
  def call(status, text: nil, mentions: [], reveal_implicit_mentions: true)
    mentions                  = Mention.includes(:account).where(id: mentions.pluck(:id), accounts: { suspended_at: nil }).or(status.mentions.includes(:account))
    implicit_mention_acct_ids = mentions.active.pluck(:account_id).to_set
    text                      = status.text if text.nil?
    mentions                  = mentions.to_set

    text.gsub(Account::MENTION_RE) do |match|
      username, domain = Regexp.last_match(1).split('@')

      domain = begin
        if TagManager.instance.local_domain?(domain)
          nil
        else
          TagManager.instance.normalize_domain(domain)
        end
      end

      mentioned_account = Account.find_remote(username, domain)

      if mention_undeliverable?(mentioned_account)
        begin
          mentioned_account = resolve_account_service.call(Regexp.last_match(1))
        rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError
          mentioned_account = nil
        end
      end

      next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended?

      mention = mentioned_account.mentions.where(status: status).first_or_create(status: status, silent: false)
      mention.update(silent: false) if mention.silent?

      mentions << mention
      implicit_mention_acct_ids.delete(mentioned_account.id)

      "@#{mentioned_account.acct}"
    end

    if reveal_implicit_mentions && implicit_mention_acct_ids.present?
      implicit_mention_accts = Account.where(id: implicit_mention_acct_ids, suspended_at: nil)
      formatted_accts = format_mentions(implicit_mention_accts)
      formatted_accts = Formatter.instance.linkify(formatted_accts, implicit_mention_accts) unless status.local?
      text << formatted_accts
    end

    [text, mentions]
  end

  private

  def mention_undeliverable?(mentioned_account)
    mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus?)
  end

  def resolve_account_service
    ResolveAccountService.new
  end

  def format_mentions(accounts)
    "\n\n#{accounts_to_mentions(accounts).join(' ')}"
  end

  def accounts_to_mentions(accounts)
    accounts.reorder(:username, :domain).pluck(:username, :domain).map do |username, domain|
      domain.blank? ? "@#{username}" : "@#{username}@#{domain}"
    end
  end
end