about summary refs log tree commit diff
path: root/app/services/resolve_mentions_service.rb
diff options
context:
space:
mode:
authorFire Demon <firedemon@creature.cafe>2020-06-30 17:33:55 -0500
committerFire Demon <firedemon@creature.cafe>2020-08-30 05:41:03 -0500
commiteaf9bc1a428b338ee666f1da1e32eed7e3b6b25e (patch)
treeaeec5fdde79d6e4fa354da326a540811b5576907 /app/services/resolve_mentions_service.rb
parent5d5d88e4f65df4c190afeb407167c153584be108 (diff)
[Feature] Add in-place post editing
Diffstat (limited to 'app/services/resolve_mentions_service.rb')
-rw-r--r--app/services/resolve_mentions_service.rb73
1 files changed, 73 insertions, 0 deletions
diff --git a/app/services/resolve_mentions_service.rb b/app/services/resolve_mentions_service.rb
new file mode 100644
index 000000000..cb00b5c19
--- /dev/null
+++ b/app/services/resolve_mentions_service.rb
@@ -0,0 +1,73 @@
+# 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 }).to_set
+    implicit_mention_acct_ids = mentions.pluck(:account_id).to_set
+    text                      = status.text if text.nil?
+
+    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?
+
+      mentions << mentioned_account.mentions.where(status: status).first_or_create(status: status)
+      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