about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/settings/preferences_controller.rb1
-rw-r--r--app/lib/user_settings_decorator.rb5
-rw-r--r--app/models/account.rb1
-rw-r--r--app/models/user.rb5
-rw-r--r--app/services/fan_out_on_write_service.rb3
-rw-r--r--app/services/post_status_service.rb22
-rw-r--r--app/views/settings/preferences/show.html.haml3
-rw-r--r--app/workers/distribution_worker.rb4
-rw-r--r--app/workers/post_status_worker.rb43
9 files changed, 81 insertions, 6 deletions
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index e7d2008d0..0e5b0f536 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -54,6 +54,7 @@ class Settings::PreferencesController < Settings::BaseController
       :setting_hide_public_outbox,
       :setting_max_public_history,
       :setting_roar_lifespan,
+      :setting_delayed_roars,
 
       :setting_default_privacy,
       :setting_default_sensitive,
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index 50632ec44..801a71bc4 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/app/lib/user_settings_decorator.rb
@@ -38,6 +38,7 @@ class UserSettingsDecorator
     user.settings['larger_emoji']        = larger_emoji_preference if change?('setting_larger_emoji')
     user.settings['max_public_history']  = max_public_history_preference if change?('setting_max_public_history')
     user.settings['roar_lifespan']       = roar_lifespan_preference if change?('setting_roar_lifespan')
+    user.settings['delayed_roars']       = delayed_roars_preference if change?('setting_delayed_roars')
 
     user.settings['notification_emails'] = merged_notification_emails if change?('notification_emails')
     user.settings['interactions']        = merged_interactions if change?('interactions')
@@ -135,6 +136,10 @@ class UserSettingsDecorator
     settings['setting_roar_lifespan']
   end
 
+  def delayed_roars_preference
+    settings['setting_delayed_roars']
+  end
+
   def merged_notification_emails
     user.settings['notification_emails'].merge coerced_settings('notification_emails').to_h
   end
diff --git a/app/models/account.rb b/app/models/account.rb
index efa6b8fbd..05fb387aa 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -130,6 +130,7 @@ class Account < ApplicationRecord
            :always_local_only?,
            :max_public_history,
            :roar_lifespan,
+           :delayed_roars?,
 
            :hides_public_profile?,
            :hides_public_outbox?,
diff --git a/app/models/user.rb b/app/models/user.rb
index 2f01c2e5a..3ca019cb9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -126,6 +126,7 @@ class User < ApplicationRecord
     :hide_public_outbox,
     :max_public_history,
     :roar_lifespan,
+    :delayed_roars,
 
     :auto_play_gif,
     :default_sensitive,
@@ -304,6 +305,10 @@ class User < ApplicationRecord
     @_roar_lifespan ||= (settings.roar_lifespan || 0)
   end
 
+  def delayed_roars?
+    @delayed_roars ||= (settings.delayed_roars || false)
+  end
+
   def defaults_to_local_only?
     @defaults_to_local_only ||= (settings.default_local || false)
   end
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 235c156f5..b1db23acd 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -3,10 +3,11 @@
 class FanOutOnWriteService < BaseService
   # Push a status into home and mentions feeds
   # @param [Status] status
-  def call(status)
+  def call(status, delayed = false)
     raise Mastodon::RaceConditionError if status.visibility.nil?
 
     deliver_to_self(status) if status.account.local?
+    return if delayed
 
     render_anonymous_payload(status)
 
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index b24ca02b3..144cc19c9 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -32,6 +32,7 @@ class PostStatusService < BaseService
   # @option [String] :delete_after
   # @option [Boolean] :nocrawl Optional skip link card generation
   # @option [Boolean] :nomentions Optional skip mention processing
+  # @option [Boolean] :delayed Optional publishing delay of 30 secs
   # @option [Hash] :poll Optional poll to attach
   # @option [Enumerable] :media_ids Optional array of media IDs to attach
   # @option [Doorkeeper::Application] :application
@@ -57,8 +58,23 @@ class PostStatusService < BaseService
       schedule_status!
     else
       return unless process_status!
-      postprocess_status!
-      bump_potential_friendship!
+      if @options[:delayed] || @account&.user&.delayed_roars?
+        delay_until = Time.now.utc + 30.seconds
+        opts = {
+          visibility: @visibility,
+          federate: @options[:federate],
+          distribute: @options[:distribute],
+          nocrawl: @options[:nocrawl],
+          nomentions: @options[:nomentions],
+          delete_after: @delete_after.nil? ? nil : @delete_after + 30.seconds,
+        }.compact
+
+        PostStatusWorker.perform_at(delay_until, @status.id, opts)
+        DistributionWorker.perform_async(@status.id, delayed = true) unless @options[:distribute] == false
+      else
+        postprocess_status!
+        bump_potential_friendship!
+      end
     end
 
     redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given?
@@ -149,7 +165,7 @@ class PostStatusService < BaseService
     return false if @status.destroyed?
 
     process_hashtags_service.call(@status, @tags, @preloaded_tags)
-    process_mentions_service.call(@status) unless @options[:nomentions]
+    process_mentions_service.call(@status) unless @delayed || @options[:nomentions]
 
     return true
   end
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index aeacd2830..f1e9656b6 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/app/views/settings/preferences/show.html.haml
@@ -31,6 +31,9 @@
     = f.input :setting_always_local, as: :boolean, wrapper: :with_label
     = f.input :setting_default_sensitive, as: :boolean, wrapper: :with_label
 
+  .fields-group
+    = f.input :setting_delayed_roars, as: :boolean, wrapper: :with_label
+
   %hr#settings_other/
 
   .fields-group
diff --git a/app/workers/distribution_worker.rb b/app/workers/distribution_worker.rb
index 4e20ef31b..0775e30e6 100644
--- a/app/workers/distribution_worker.rb
+++ b/app/workers/distribution_worker.rb
@@ -3,10 +3,10 @@
 class DistributionWorker
   include Sidekiq::Worker
 
-  def perform(status_id)
+  def perform(status_id, delayed = false)
     RedisLock.acquire(redis: Redis.current, key: "distribute:#{status_id}") do |lock|
       if lock.acquired?
-        FanOutOnWriteService.new.call(Status.find(status_id))
+        FanOutOnWriteService.new.call(Status.find(status_id), delayed)
       else
         raise Mastodon::RaceConditionError
       end
diff --git a/app/workers/post_status_worker.rb b/app/workers/post_status_worker.rb
new file mode 100644
index 000000000..e9b73b8bc
--- /dev/null
+++ b/app/workers/post_status_worker.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class PostStatusWorker
+  include Sidekiq::Worker
+
+  sidekiq_options unique: :until_executed
+
+  def perform(status_id, options = {})
+    status = Status.find(status_id)
+    return false if status.destroyed?
+
+    if options[:visibility]
+      status.visibility = options[:visibility]
+      status.save!
+    end
+
+    process_mentions_service.call(status) unless options[:nomentions]
+
+    LinkCrawlWorker.perform_async(status.id) unless options[:nocrawl] || status.spoiler_text?
+    DistributionWorker.perform_async(status.id) unless options[:distribute] == false
+
+    unless status.local_only? || options[:distribute] == false || options[:federate] == false
+      ActivityPub::DistributionWorker.perform_async(status.id)
+    end
+
+    PollExpirationNotifyWorker.perform_at(status.poll.expires_at, status.poll.id) if status.poll
+
+    status.delete_after = options[:delete_after] if options[:delete_after]
+
+    return true if !status.reply? || status.account.id == status.in_reply_to_account_id
+    ActivityTracker.increment('activity:interactions')
+    return if status.account.following?(status.in_reply_to_account_id)
+    PotentialFriendshipTracker.record(status.account.id, status.in_reply_to_account_id, :reply)
+
+    true
+  rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
+    true
+  end
+
+  def process_mentions_service
+    ProcessMentionsService.new
+  end
+end