about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFire Demon <firedemon@creature.cafe>2020-09-21 21:15:33 -0500
committerFire Demon <firedemon@creature.cafe>2020-09-21 21:15:33 -0500
commit6fedf5a6e283a7a615920b48da40aa5d9ef1fd4e (patch)
tree4adffe4b81a8b1410f836cc8e46a8efb9464d58d
parent1575d744fb45605b918f75ebb9ea67e53cb774a2 (diff)
Add option to filter boosts from home timeline at backend level
-rw-r--r--app/controllers/settings/preferences_controller.rb7
-rw-r--r--app/lib/feed_manager.rb70
-rw-r--r--app/lib/user_settings_decorator.rb5
-rw-r--r--app/models/user.rb6
-rw-r--r--app/views/settings/preferences/filters/show.html.haml3
-rw-r--r--app/workers/clear_reblogs_worker.rb11
-rw-r--r--config/locales/simple_form.en-MP.yml2
7 files changed, 78 insertions, 26 deletions
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index f380c8310..01ee2ea47 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -8,10 +8,12 @@ class Settings::PreferencesController < Settings::BaseController
   def show; end
 
   def update
-    user_settings.update(user_settings_params.to_h)
+    if user_settings.update(user_settings_params.to_h)
+      Rails.cache.delete("filter_settings:#{current_user.account_id}")
+      ClearReblogsWorker.perform_async(current_user.account_id) if current_user.disables_home_reblogs?
+    end
 
     if current_user.update(user_params)
-      Rails.cache.delete("filter_settings:#{current_user.account_id}")
       I18n.locale = current_user.locale
       redirect_to after_update_redirect_path, notice: I18n.t('generic.changes_saved_msg')
     else
@@ -77,6 +79,7 @@ class Settings::PreferencesController < Settings::BaseController
       :setting_filter_from_unknown,
       :setting_unpublish_on_delete,
       :setting_rss_disabled,
+      :setting_no_boosts_home,
       notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
       interactions: %i(must_be_follower must_be_following must_be_following_dm)
     )
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index 5601785fe..7859e9831 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -56,7 +56,7 @@ class FeedManager
   # @param [Status] status
   # @return [Boolean]
   def push_to_home(account, status)
-    return false unless add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
+    return false unless add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?, account.user&.disables_home_reblogs?)
 
     trim(:home, account.id)
     PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
@@ -80,7 +80,7 @@ class FeedManager
   # @return [Boolean]
   def push_to_list(list, status)
     return false if filter_from_list?(status, list)
-    return false unless add_to_feed(:list, list.id, status, list.reblogs? && list.account.user&.aggregates_reblogs?)
+    return false unless add_to_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?, !list.reblogs?)
 
     trim(:list, list.id)
     PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
@@ -147,6 +147,7 @@ class FeedManager
   def merge_into_home(from_account, into_account)
     timeline_key = key(:home, into_account.id)
     aggregate    = into_account.user&.aggregates_reblogs?
+    no_reblogs   = into_account.user&.disables_home_reblogs?
     query        = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
 
     if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
@@ -161,7 +162,7 @@ class FeedManager
     statuses.each do |status|
       next if filter_from_home?(status, into_account.id, crutches, filter_options)
 
-      add_to_feed(:home, into_account.id, status, aggregate)
+      add_to_feed(:home, into_account.id, status, aggregate, no_reblogs)
     end
 
     trim(:home, into_account.id)
@@ -174,6 +175,7 @@ class FeedManager
   def merge_into_list(from_account, list)
     timeline_key = key(:list, list.id)
     aggregate    = list.account.user&.aggregates_reblogs?
+    no_reblogs   = list.account.user&.disables_home_reblogs?
     query        = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
 
     if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
@@ -188,7 +190,7 @@ class FeedManager
     statuses.each do |status|
       next if filter_from_home?(status, list.account_id, crutches, filter_options) || filter_from_list?(status, list)
 
-      add_to_feed(:list, list.id, status, aggregate)
+      add_to_feed(:list, list.id, status, aggregate, no_reblogs)
     end
 
     trim(:list, list.id)
@@ -240,6 +242,18 @@ class FeedManager
     end
   end
 
+  # Clear all reblogs from a home feed
+  # @param [Account] account
+  # @return [void]
+  def clear_reblogs_from_home(account)
+    timeline_key        = key(:home, account.id)
+    timeline_status_ids = redis.zrange(timeline_key, 0, -1)
+
+    Status.reblogs.where(id: timeline_status_ids).find_each do |status|
+      unpush_from_home(account, status)
+    end
+  end
+
   # Populate list feeds of account from scratch
   # @param [Account] account
   # @return [void]
@@ -267,7 +281,7 @@ class FeedManager
         statuses.each do |status|
           next if filter_from_list?(status, account.id) || filter_from_home?(status, account.id, crutches, filter_options)
 
-          add_to_feed(:list, list.id, status, list.reblogs? && list.account.user&.aggregates_reblogs?)
+          add_to_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?, !list.reblogs?)
         end
 
         trim(:list, list.id)
@@ -281,10 +295,11 @@ class FeedManager
   def populate_home(account)
     limit        = FeedManager::MAX_ITEMS / 2
     aggregate    = account.user&.aggregates_reblogs?
+    no_reblogs   = account.user&.disables_home_reblogs?
     timeline_key = key(:home, account.id)
 
     account.statuses.limit(limit).each do |status|
-      add_to_feed(:home, account.id, status, aggregate)
+      add_to_feed(:home, account.id, status, aggregate, no_reblogs)
     end
 
     account.following.includes(:account_stat).find_each do |target_account|
@@ -305,7 +320,7 @@ class FeedManager
       statuses.each do |status|
         next if filter_from_home?(status, account.id, crutches, filter_options)
 
-        add_to_feed(:home, account.id, status, aggregate)
+        add_to_feed(:home, account.id, status, aggregate, no_reblogs)
       end
 
       trim(:home, account.id)
@@ -547,10 +562,15 @@ class FeedManager
   # @param [Status] status
   # @param [Boolean] aggregate_reblogs
   # @return [Boolean]
-  def add_to_feed(timeline_type, account_id, status, aggregate_reblogs = true)
+  def add_to_feed(timeline_type, account_id, status, aggregate_reblogs = true, skip_reblogs = false)
     timeline_key = key(timeline_type, account_id)
     reblog_key   = key(timeline_type, account_id, 'reblogs')
 
+    if status.reblog?
+      add_to_reblogs(account_id, status, aggregate_reblogs) if timeline_type == :home
+      return false if skip_reblogs
+    end
+
     if status.reblog? && (aggregate_reblogs.nil? || aggregate_reblogs)
       # If the original status or a reblog of it is within
       # REBLOG_FALLOFF statuses from the top, do not re-insert it into
@@ -583,15 +603,7 @@ class FeedManager
       redis.zadd(timeline_key, status.id, status.id)
     end
 
-    if timeline_type == :home && status.reblog?
-      reblogs_list_id = find_or_create_reblogs_list(account_id).id
-      if add_to_feed(:list, reblogs_list_id, status, aggregate_reblogs)
-        trim(:list, reblogs_list_id)
-        if push_update_required?("timeline:list:#{reblogs_list_id}")
-          PushUpdateWorker.perform_async(account_id, status.id, "timeline:list:#{reblogs_list_id}")
-        end
-      end
-    end
+    add_to_reblogs(account_id, status, aggregate_reblogs) if timeline_type == :home && status.reblog?
 
     true
   end
@@ -609,12 +621,7 @@ class FeedManager
     timeline_key = key(timeline_type, account_id)
     reblog_key   = key(timeline_type, account_id, 'reblogs')
 
-    if timeline_type == :home && status.reblog?
-      reblogs_list_id = find_or_create_reblogs_list(account_id).id
-      if remove_from_feed(:list, reblogs_list_id, status, aggregate_reblogs)
-        redis.publish("timeline:list:#{reblogs_list_id}", Oj.dump(event: :delete, payload: status.id.to_s))
-      end
-    end
+    remove_from_reblogs(account_id, status, aggregate_reblogs) if timeline_type == :home && status.reblog?
 
     if status.reblog? && (aggregate_reblogs.nil? || aggregate_reblogs)
       # 1. If the reblogging status is not in the feed, stop.
@@ -686,4 +693,21 @@ class FeedManager
       list.replies_policy = :no_replies
     end
   end
+
+  def add_to_reblogs(account_id, status, aggregate_reblogs = true)
+    reblogs_list_id = find_or_create_reblogs_list(account_id).id
+    return unless add_to_feed(:list, reblogs_list_id, status, aggregate_reblogs)
+
+    trim(:list, reblogs_list_id)
+    return unless push_update_required?("timeline:list:#{reblogs_list_id}")
+
+    PushUpdateWorker.perform_async(account_id, status.id, "timeline:list:#{reblogs_list_id}")
+  end
+
+  def remove_from_reblogs(account_id, status, aggregate_reblogs)
+    reblogs_list_id = find_or_create_reblogs_list(account_id).id
+    return unless remove_from_feed(:list, reblogs_list_id, status, aggregate_reblogs)
+
+    redis.publish("timeline:list:#{reblogs_list_id}", Oj.dump(event: :delete, payload: status.id.to_s))
+  end
 end
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index 41b420ad0..e863550da 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/app/lib/user_settings_decorator.rb
@@ -63,6 +63,7 @@ class UserSettingsDecorator
     user.settings['filter_from_unknown'] = filter_from_unknown_preference if change?('setting_filter_from_unknown')
     user.settings['unpublish_on_delete'] = unpublish_on_delete_preference if change?('setting_unpublish_on_delete')
     user.settings['rss_disabled']        = rss_disabled_preference if change?('setting_rss_disabled')
+    user.settings['no_boosts_home']      = no_boosts_home_preference if change?('setting_no_boosts_home')
   end
 
   def merged_notification_emails
@@ -241,6 +242,10 @@ class UserSettingsDecorator
     boolean_cast_setting 'setting_rss_disabled'
   end
 
+  def no_boosts_home_preference
+    boolean_cast_setting 'setting_no_boosts_home'
+  end
+
   def boolean_cast_setting(key)
     ActiveModel::Type::Boolean.new.cast(settings[key])
   end
diff --git a/app/models/user.rb b/app/models/user.rb
index 5f2edab57..9218abbb7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -124,7 +124,7 @@ class User < ApplicationRecord
            :style_wide_media,
            :publish_in, :unpublish_in, :unpublish_delete, :boost_every, :boost_jitter,
            :boost_random, :filter_from_unknown, :unpublish_on_delete,
-           :rss_disabled,
+           :rss_disabled, :no_boosts_home,
            to: :settings, prefix: :setting, allow_nil: false
 
   attr_reader :invite_code, :sign_in_token_attempt
@@ -263,6 +263,10 @@ class User < ApplicationRecord
     @shows_application ||= settings.show_application
   end
 
+  def disables_home_reblogs?
+    @disables_home_reblogs ||= settings.no_boosts_home
+  end
+
   # rubocop:disable Naming/MethodParameterName
   def token_for_app(a)
     return nil if a.nil? || a.owner != self
diff --git a/app/views/settings/preferences/filters/show.html.haml b/app/views/settings/preferences/filters/show.html.haml
index 20787d164..5794859b6 100644
--- a/app/views/settings/preferences/filters/show.html.haml
+++ b/app/views/settings/preferences/filters/show.html.haml
@@ -10,6 +10,9 @@
   %h4= t 'preferences.filtering'
 
   .fields-group
+    = f.input :setting_no_boosts_home, as: :boolean, wrapper: :with_label
+
+  .fields-group
     = f.input :setting_filter_from_unknown, as: :boolean, wrapper: :with_label
 
   %h4= t 'preferences.public_timelines'
diff --git a/app/workers/clear_reblogs_worker.rb b/app/workers/clear_reblogs_worker.rb
new file mode 100644
index 000000000..69c8afc59
--- /dev/null
+++ b/app/workers/clear_reblogs_worker.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class ClearReblogsWorker
+  include Sidekiq::Worker
+
+  def perform(account_id)
+    FeedManager.instance.clear_reblogs_from_home(Account.find(account_id))
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+end
diff --git a/config/locales/simple_form.en-MP.yml b/config/locales/simple_form.en-MP.yml
index 200e8e26d..65b62b9de 100644
--- a/config/locales/simple_form.en-MP.yml
+++ b/config/locales/simple_form.en-MP.yml
@@ -27,6 +27,7 @@ en-MP:
         setting_default_content_type_bbcode_html: "<strong>[b]Bold[/b]</strong>, <u>[u]Underline[/u]</u>, <em>[i]Italic[/i]</em>, <code>[code]Console[/code]</code>, ..."
         setting_default_language: The language of your roars can be detected automatically, but it's not always accurate
         setting_filter_from_unknown: Do not show boosts from unfollowed accounts on your home timeline.  Takes effect for newly-pushed items.
+        setting_no_boosts_home: Filters boosts from the home timeline, reguardless of the app you use.  These boosts will still be available in the Boosts list.
         setting_manual_publish: This allows you to draft, proofread, and edit your roars before publishing them.  You can publish a roar from its <strong>action menu</strong> (the three dots).
         setting_rss_disabled: Improves privacy by turning off your account's public RSS feed.
         setting_show_application: The application you use to toot will be displayed in the detailed view of your roars
@@ -56,6 +57,7 @@ en-MP:
         setting_favourite_modal: Show confirmation dialog before admiring (applies to Glitch flavour only)
         setting_filter_from_unknown: Filter boosts from unfollowed accounts
         setting_manual_publish: Manually publish roars
+        setting_no_boosts_home: Disable boosts in home timeline
         setting_publish_in: Auto-publish
         setting_show_application: Disclose application used to send roars
         setting_style_css_profile: Custom CSS for profile page