creation.rb 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. # frozen_string_literal: true
  2. class OStatus::Activity::Creation < OStatus::Activity::Base
  3. def perform
  4. if redis.exists("delete_upon_arrival:#{@account.id}:#{id}")
  5. Rails.logger.debug "Delete for status #{id} was queued, ignoring"
  6. return [nil, false]
  7. end
  8. return [nil, false] if @account.suspended?
  9. RedisLock.acquire(lock_options) do |lock|
  10. if lock.acquired?
  11. # Return early if status already exists in db
  12. @status = find_status(id)
  13. return [@status, false] unless @status.nil?
  14. @status = process_status
  15. end
  16. end
  17. [@status, true]
  18. end
  19. def process_status
  20. Rails.logger.debug "Creating remote status #{id}"
  21. cached_reblog = reblog
  22. status = nil
  23. ApplicationRecord.transaction do
  24. status = Status.create!(
  25. uri: id,
  26. url: url,
  27. account: @account,
  28. reblog: cached_reblog,
  29. text: content,
  30. spoiler_text: content_warning,
  31. created_at: published,
  32. reply: thread?,
  33. language: content_language,
  34. visibility: visibility_scope,
  35. conversation: find_or_create_conversation,
  36. thread: thread? ? find_status(thread.first) || find_activitypub_status(thread.first, thread.second) : nil
  37. )
  38. save_mentions(status)
  39. save_hashtags(status)
  40. save_media(status)
  41. save_emojis(status)
  42. end
  43. if thread? && status.thread.nil?
  44. Rails.logger.debug "Trying to attach #{status.id} (#{id}) to #{thread.first}"
  45. ThreadResolveWorker.perform_async(status.id, thread.second)
  46. end
  47. Rails.logger.debug "Queuing remote status #{status.id} (#{id}) for distribution"
  48. LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
  49. DistributionWorker.perform_async(status.id)
  50. status
  51. end
  52. def content
  53. @xml.at_xpath('./xmlns:content', xmlns: OStatus::TagManager::XMLNS).content
  54. end
  55. def content_language
  56. @xml.at_xpath('./xmlns:content', xmlns: OStatus::TagManager::XMLNS)['xml:lang']&.presence || 'en'
  57. end
  58. def content_warning
  59. @xml.at_xpath('./xmlns:summary', xmlns: OStatus::TagManager::XMLNS)&.content || ''
  60. end
  61. def visibility_scope
  62. @xml.at_xpath('./mastodon:scope', mastodon: OStatus::TagManager::MTDN_XMLNS)&.content&.to_sym || :public
  63. end
  64. def published
  65. @xml.at_xpath('./xmlns:published', xmlns: OStatus::TagManager::XMLNS).content
  66. end
  67. def thread?
  68. !@xml.at_xpath('./thr:in-reply-to', thr: OStatus::TagManager::THR_XMLNS).nil?
  69. end
  70. def thread
  71. thr = @xml.at_xpath('./thr:in-reply-to', thr: OStatus::TagManager::THR_XMLNS)
  72. [thr['ref'], thr['href']]
  73. end
  74. private
  75. def find_or_create_conversation
  76. uri = @xml.at_xpath('./ostatus:conversation', ostatus: OStatus::TagManager::OS_XMLNS)&.attribute('ref')&.content
  77. return if uri.nil?
  78. if OStatus::TagManager.instance.local_id?(uri)
  79. local_id = OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')
  80. return Conversation.find_by(id: local_id)
  81. end
  82. Conversation.find_by(uri: uri) || Conversation.create!(uri: uri)
  83. end
  84. def save_mentions(parent)
  85. processed_account_ids = []
  86. @xml.xpath('./xmlns:link[@rel="mentioned"]', xmlns: OStatus::TagManager::XMLNS).each do |link|
  87. next if [OStatus::TagManager::TYPES[:group], OStatus::TagManager::TYPES[:collection]].include? link['ostatus:object-type']
  88. mentioned_account = account_from_href(link['href'])
  89. next if mentioned_account.nil? || processed_account_ids.include?(mentioned_account.id)
  90. mentioned_account.mentions.where(status: parent).first_or_create(status: parent)
  91. # So we can skip duplicate mentions
  92. processed_account_ids << mentioned_account.id
  93. end
  94. end
  95. def save_hashtags(parent)
  96. tags = @xml.xpath('./xmlns:category', xmlns: OStatus::TagManager::XMLNS).map { |category| category['term'] }.select(&:present?)
  97. ProcessHashtagsService.new.call(parent, tags)
  98. end
  99. def save_media(parent)
  100. do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
  101. @xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link|
  102. next unless link['href']
  103. media = MediaAttachment.where(status: parent, remote_url: link['href']).first_or_initialize(account: parent.account, status: parent, remote_url: link['href'])
  104. parsed_url = Addressable::URI.parse(link['href']).normalize
  105. next if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.empty?
  106. media.save
  107. next if do_not_download
  108. begin
  109. media.file_remote_url = link['href']
  110. media.save!
  111. rescue ActiveRecord::RecordInvalid
  112. next
  113. end
  114. end
  115. end
  116. def save_emojis(parent)
  117. do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
  118. return if do_not_download
  119. @xml.xpath('./xmlns:link[@rel="emoji"]', xmlns: OStatus::TagManager::XMLNS).each do |link|
  120. next unless link['href'] && link['name']
  121. shortcode = link['name'].delete(':')
  122. emoji = CustomEmoji.find_by(shortcode: shortcode, domain: parent.account.domain)
  123. next unless emoji.nil?
  124. emoji = CustomEmoji.new(shortcode: shortcode, domain: parent.account.domain)
  125. emoji.image_remote_url = link['href']
  126. emoji.save
  127. end
  128. end
  129. def account_from_href(href)
  130. url = Addressable::URI.parse(href).normalize
  131. if TagManager.instance.web_domain?(url.host)
  132. Account.find_local(url.path.gsub('/users/', ''))
  133. else
  134. Account.where(uri: href).or(Account.where(url: href)).first || FetchRemoteAccountService.new.call(href)
  135. end
  136. end
  137. def lock_options
  138. { redis: Redis.current, key: "create:#{id}" }
  139. end
  140. end