fedibird-fe/app/services/process_status_reference_service.rb
2023-01-16 12:58:29 +09:00

87 lines
2.8 KiB
Ruby

# frozen_string_literal: true
class ProcessStatusReferenceService
def call(status, **options)
@status = status
urls = parse_urls(status, options).union(options[:urls]) - [ActivityPub::TagManager.instance.uri_for(status), ActivityPub::TagManager.instance.url_for(status)]
process_reference(urls, (options[:status_reference_ids] || []).compact.uniq, status.id)
end
private
def parse_urls(status, **options)
if status.local?
parse_local_urls(status.text)
else
mentions = options[:mentions] || status.mentions
parse_remote_urls(status.text, mentions)
end
end
def process_reference(urls, ids, status_id)
target_statuses = urls_to_target_statuses(urls, status_id).uniq
target_statuses = target_statuses + Status.where(id: ids - target_statuses.map(&:id)).where(visibility: [:public, :unlisted, :private])
references = target_statuses.filter_map do |target_status|
StatusReference.create(status_id: status_id, target_status_id: target_status.id)
end
references.group_by{|reference| reference.target_status.account}.each do |account, grouped_references|
create_notification(grouped_references.sort_by(&:id).first) if account.local?
end
end
def create_notification(reference)
NotifyService.new.call(reference.target_status.account, :status_reference, reference)
end
def parse_local_urls(text)
text.scan(FetchLinkCardService::URL_PATTERN).map(&:second).uniq.filter_map do |url|
Addressable::URI.parse(url).normalize.to_s
rescue Addressable::URI::InvalidURIError
nil
end
end
def parse_remote_urls(text, mentions = [])
html = Nokogiri::HTML(text)
links = html.css(':not(.reference-link-inline) > a')
links.filter_map do |anchor|
Addressable::URI.parse(anchor['href']).normalize.to_s unless skip_link?(anchor, mentions)
rescue Addressable::URI::InvalidURIError
nil
end
end
def urls_to_target_statuses(urls, status_id)
urls.uniq.filter_map do |url|
url_to_target_status(url).tap do |target_status|
if target_status.nil? && !TagManager.instance.local_url?(url)
StatusReferenceResolveWorker.perform_async(status_id, url)
end
end
end
end
def url_to_target_status(url)
if TagManager.instance.local_url?(url)
ActivityPub::TagManager.instance.uri_to_resource(url, Status)
else
EntityCache.instance.holding_status(url)
end
end
def mention_link?(anchor, mentions)
mentions.any? do |mention|
anchor['href'] == ActivityPub::TagManager.instance.url_for(mention.account)
end
end
def skip_link?(anchor, mentions)
# Avoid links for hashtags and mentions (microformats)
anchor['rel']&.include?('tag') || anchor['class']&.match?(/u-url|h-card/) || mention_link?(anchor, mentions)
end
end