about summary refs log tree commit diff
path: root/app/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/services')
-rw-r--r--app/services/activitypub/fetch_remote_status_service.rb5
-rw-r--r--app/services/activitypub/process_account_service.rb13
-rw-r--r--app/services/activitypub/process_collection_service.rb3
-rw-r--r--app/services/batched_remove_status_service.rb5
-rw-r--r--app/services/fan_out_on_write_service.rb12
-rw-r--r--app/services/fetch_link_card_service.rb4
-rw-r--r--app/services/notify_service.rb21
-rw-r--r--app/services/post_status_service.rb10
-rw-r--r--app/services/process_hashtags_service.rb6
-rw-r--r--app/services/remove_status_service.rb8
-rw-r--r--app/services/resolve_account_service.rb2
-rw-r--r--app/services/update_remote_profile_service.rb12
12 files changed, 75 insertions, 26 deletions
diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb
index 930fbad1f..2b447abb3 100644
--- a/app/services/activitypub/fetch_remote_status_service.rb
+++ b/app/services/activitypub/fetch_remote_status_service.rb
@@ -4,9 +4,9 @@ class ActivityPub::FetchRemoteStatusService < BaseService
   include JsonLdHelper
 
   # Should be called when uri has already been checked for locality
-  def call(uri, id: true, prefetched_body: nil)
+  def call(uri, id: true, prefetched_body: nil, on_behalf_of: nil)
     @json = if prefetched_body.nil?
-              fetch_resource(uri, id)
+              fetch_resource(uri, id, on_behalf_of)
             else
               body_to_json(prefetched_body)
             end
@@ -34,6 +34,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
   end
 
   def trustworthy_attribution?(uri, attributed_to)
+    return false if uri.nil? || attributed_to.nil?
     Addressable::URI.parse(uri).normalized_host.casecmp(Addressable::URI.parse(attributed_to).normalized_host).zero?
   end
 
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index f67ebb443..453253db4 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -23,6 +23,8 @@ class ActivityPub::ProcessAccountService < BaseService
         create_account if @account.nil?
         update_account
         process_tags
+      else
+        raise Mastodon::RaceConditionError
       end
     end
 
@@ -44,7 +46,6 @@ class ActivityPub::ProcessAccountService < BaseService
     @account.protocol    = :activitypub
     @account.username    = @username
     @account.domain      = @domain
-    @account.uri         = @uri
     @account.suspended   = true if auto_suspend?
     @account.silenced    = true if auto_silence?
     @account.private_key = nil
@@ -67,10 +68,12 @@ class ActivityPub::ProcessAccountService < BaseService
     @account.followers_url           = @json['followers'] || ''
     @account.featured_collection_url = @json['featured'] || ''
     @account.url                     = url || @uri
+    @account.uri                     = @uri
     @account.display_name            = @json['name'] || ''
     @account.note                    = @json['summary'] || ''
     @account.locked                  = @json['manuallyApprovesFollowers'] || false
     @account.fields                  = property_values || {}
+    @account.actor_type              = actor_type
   end
 
   def set_fetchable_attributes!
@@ -95,6 +98,14 @@ class ActivityPub::ProcessAccountService < BaseService
     ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id)
   end
 
+  def actor_type
+    if @json['type'].is_a?(Array)
+      @json['type'].find { |type| ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(type) }
+    else
+      @json['type']
+    end
+  end
+
   def image_url(key)
     value = first_of_value(@json[key])
 
diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb
index eb93329e9..79cdca297 100644
--- a/app/services/activitypub/process_collection_service.rb
+++ b/app/services/activitypub/process_collection_service.rb
@@ -45,5 +45,8 @@ class ActivityPub::ProcessCollectionService < BaseService
 
   def verify_account!
     @account = ActivityPub::LinkedDataSignature.new(@json).verify_account!
+  rescue JSON::LD::JsonLdError => e
+    Rails.logger.debug "Could not verify LD-Signature for #{value_or_id(@json['actor'])}: #{e.message}"
+    nil
   end
 end
diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb
index cb65a2256..ace51a1fc 100644
--- a/app/services/batched_remove_status_service.rb
+++ b/app/services/batched_remove_status_service.rb
@@ -81,6 +81,11 @@ class BatchedRemoveStatusService < BaseService
       redis.publish('timeline:public', payload)
       redis.publish('timeline:public:local', payload) if status.local?
 
+      if status.media_attachments.any?
+        redis.publish('timeline:public:media', payload)
+        redis.publish('timeline:public:local:media', payload) if status.local?
+      end
+
       @tags[status.id].each do |hashtag|
         redis.publish("timeline:hashtag:#{hashtag}", payload)
         redis.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local?
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 510b80c82..8b3630229 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -25,6 +25,7 @@ class FanOutOnWriteService < BaseService
     return if status.reply? && status.in_reply_to_account_id != status.account_id
 
     deliver_to_public(status)
+    deliver_to_media(status) if status.media_attachments.any?
   end
 
   private
@@ -37,7 +38,7 @@ class FanOutOnWriteService < BaseService
   def deliver_to_followers(status)
     Rails.logger.debug "Delivering status #{status.id} to followers"
 
-    status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).reorder(nil).find_in_batches do |followers|
+    status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).select(:id).reorder(nil).find_in_batches do |followers|
       FeedInsertWorker.push_bulk(followers) do |follower|
         [status.id, follower.id, :home]
       end
@@ -47,7 +48,7 @@ class FanOutOnWriteService < BaseService
   def deliver_to_lists(status)
     Rails.logger.debug "Delivering status #{status.id} to lists"
 
-    status.account.lists.joins(account: :user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).reorder(nil).find_in_batches do |lists|
+    status.account.lists.joins(account: :user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).select(:id).reorder(nil).find_in_batches do |lists|
       FeedInsertWorker.push_bulk(lists) do |list|
         [status.id, list.id, :list]
       end
@@ -85,6 +86,13 @@ class FanOutOnWriteService < BaseService
     Redis.current.publish('timeline:public:local', @payload) if status.local?
   end
 
+  def deliver_to_media(status)
+    Rails.logger.debug "Delivering status #{status.id} to media timeline"
+
+    Redis.current.publish('timeline:public:media', @payload)
+    Redis.current.publish('timeline:public:local:media', @payload) if status.local?
+  end
+
   def deliver_to_direct_timelines(status)
     Rails.logger.debug "Delivering status #{status.id} to direct timelines"
 
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 77d4aa538..86d0f9971 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -23,11 +23,13 @@ class FetchLinkCardService < BaseService
       if lock.acquired?
         @card = PreviewCard.find_by(url: @url)
         process_url if @card.nil? || @card.updated_at <= 2.weeks.ago
+      else
+        raise Mastodon::RaceConditionError
       end
     end
 
     attach_card if @card&.persisted?
-  rescue HTTP::Error, Addressable::URI::InvalidURIError => e
+  rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::LengthValidationError => e
     Rails.logger.debug "Error fetching link #{@url}: #{e}"
     nil
   end
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index ba086449c..6490d2735 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -9,6 +9,7 @@ class NotifyService < BaseService
     return if recipient.user.nil? || blocked?
 
     create_notification
+    push_notification if @notification.browserable?
     send_email if email_enabled?
   rescue ActiveRecord::RecordInvalid
     return
@@ -101,25 +102,27 @@ class NotifyService < BaseService
 
   def create_notification
     @notification.save!
-    return unless @notification.browserable?
+  end
+
+  def push_notification
+    return if @notification.activity.nil?
+
     Redis.current.publish("timeline:#{@recipient.id}", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, :notification)))
     send_push_notifications
   end
 
   def send_push_notifications
-    # HACK: Can be caused by quickly unfavouriting a status, since creating
-    # a favourite and creating a notification are not wrapped in a transaction.
-    return if @notification.activity.nil?
-
-    sessions_with_subscriptions = @recipient.user.session_activations.where.not(web_push_subscription: nil)
-    sessions_with_subscriptions_ids = sessions_with_subscriptions.select { |session| session.web_push_subscription.pushable? @notification }.map(&:id)
+    subscriptions_ids = ::Web::PushSubscription.where(user_id: @recipient.user.id)
+                                               .select { |subscription| subscription.pushable?(@notification) }
+                                               .map(&:id)
 
-    WebPushNotificationWorker.push_bulk(sessions_with_subscriptions_ids) do |session_activation_id|
-      [session_activation_id, @notification.id]
+    ::Web::PushNotificationWorker.push_bulk(subscriptions_ids) do |subscription_id|
+      [subscription_id, @notification.id]
     end
   end
 
   def send_email
+    return if @notification.activity.nil?
     NotificationMailer.public_send(@notification.type, @recipient, @notification).deliver_later
   end
 
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index fe03c044c..b1d5bd3a7 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -22,7 +22,7 @@ class PostStatusService < BaseService
     media  = validate_media!(options[:media_ids])
     status = nil
     text   = options.delete(:spoiler_text) if text.blank? && options[:spoiler_text].present?
-    text   = '.' if text.blank? && !media.empty?
+    text   = '.' if text.blank? && media.present?
 
     ApplicationRecord.transaction do
       status = account.statuses.create!(text: text,
@@ -31,12 +31,12 @@ class PostStatusService < BaseService
                                         sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]),
                                         spoiler_text: options[:spoiler_text] || '',
                                         visibility: options[:visibility] || account.user&.setting_default_privacy,
-                                        language: LanguageDetector.instance.detect(text, account),
+                                        language: language_from_option(options[:language]) || LanguageDetector.instance.detect(text, account),
                                         application: options[:application])
     end
 
-    process_mentions_service.call(status)
     process_hashtags_service.call(status)
+    process_mentions_service.call(status)
 
     LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
     DistributionWorker.perform_async(status.id)
@@ -68,6 +68,10 @@ class PostStatusService < BaseService
     media
   end
 
+  def language_from_option(str)
+    ISO_639.find(str)&.alpha2
+  end
+
   def process_mentions_service
     ProcessMentionsService.new
   end
diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb
index 5b45c865f..0695922b8 100644
--- a/app/services/process_hashtags_service.rb
+++ b/app/services/process_hashtags_service.rb
@@ -4,8 +4,10 @@ class ProcessHashtagsService < BaseService
   def call(status, tags = [])
     tags = Extractor.extract_hashtags(status.text) if status.local?
 
-    tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |tag|
-      status.tags << Tag.where(name: tag).first_or_initialize(name: tag)
+    tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name|
+      tag = Tag.where(name: name).first_or_create(name: name)
+      status.tags << tag
+      TrendingTags.record_use!(tag, status.account, status.created_at)
     end
   end
 end
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index e164c03ab..8c3e18444 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -20,6 +20,7 @@ class RemoveStatusService < BaseService
     remove_reblogs
     remove_from_hashtags
     remove_from_public
+    remove_from_media if status.media_attachments.any?
     remove_from_direct if status.direct_visibility?
 
     @status.destroy!
@@ -131,6 +132,13 @@ class RemoveStatusService < BaseService
     Redis.current.publish('timeline:public:local', @payload) if @status.local?
   end
 
+  def remove_from_media
+    return unless @status.public_visibility?
+
+    Redis.current.publish('timeline:public:media', @payload)
+    Redis.current.publish('timeline:public:local:media', @payload) if @status.local?
+  end
+
   def remove_from_direct
     @mentions.each do |mention|
       Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local?
diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb
index de8d1151d..4323e7f06 100644
--- a/app/services/resolve_account_service.rb
+++ b/app/services/resolve_account_service.rb
@@ -49,6 +49,8 @@ class ResolveAccountService < BaseService
         else
           handle_ostatus
         end
+      else
+        raise Mastodon::RaceConditionError
       end
     end
 
diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb
index aca1185de..68d36addf 100644
--- a/app/services/update_remote_profile_service.rb
+++ b/app/services/update_remote_profile_service.rb
@@ -41,24 +41,24 @@ class UpdateRemoteProfileService < BaseService
         account.header.destroy
       end
 
-      save_emojis(account) if remote_profile.emojis.present?
+      save_emojis if remote_profile.emojis.present?
     end
   end
 
-  def save_emojis(parent)
-    do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
+  def save_emojis
+    do_not_download = DomainBlock.find_by(domain: account.domain)&.reject_media?
 
     return if do_not_download
 
-    remote_account.emojis.each do |link|
+    remote_profile.emojis.each do |link|
       next unless link['href'] && link['name']
 
       shortcode = link['name'].delete(':')
-      emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: parent.account.domain)
+      emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: account.domain)
 
       next unless emoji.nil?
 
-      emoji = CustomEmoji.new(shortcode: shortcode, domain: parent.account.domain)
+      emoji = CustomEmoji.new(shortcode: shortcode, domain: account.domain)
       emoji.image_remote_url = link['href']
       emoji.save
     end