From 04447a05cb495bc1e9d3b5088ac98cb126624e5c Mon Sep 17 00:00:00 2001 From: Andrew Dunham Date: Thu, 9 Feb 2023 14:58:12 -0500 Subject: [PATCH] PublicFeed: add a configuration setting to filter out bot accounts This can be useful when running a smaller instance that also has bot accounts, where you don't want them to clutter up the Local timeline. --- .../api/v1/timelines/public_controller.rb | 7 +++- app/models/account.rb | 1 + app/models/public_feed.rb | 11 ++++++- app/models/tag_feed.rb | 1 + config/initializers/99_hometown.rb | 5 +++ spec/models/public_feed_spec.rb | 33 +++++++++++++++++++ 6 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 config/initializers/99_hometown.rb diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index d253b744f99bf9..6bc8eb26f3e82e 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -37,10 +37,15 @@ def public_feed current_account, local: truthy_param?(:local), remote: truthy_param?(:remote), - only_media: truthy_param?(:only_media) + only_media: truthy_param?(:only_media), + without_bots: without_bots? ) end + def without_bots? + Rails.configuration.x.local_timeline_exclude_bots && truthy_param?(:local) + end + def insert_pagination_headers set_pagination_headers(next_path, prev_path) end diff --git a/app/models/account.rb b/app/models/account.rb index 262285a09ecad0..286291c72709b8 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -103,6 +103,7 @@ class Account < ApplicationRecord scope :without_suspended, -> { where(suspended_at: nil) } scope :without_silenced, -> { where(silenced_at: nil) } scope :without_instance_actor, -> { where.not(id: -99) } + scope :without_bots, -> { where.not(actor_type: %w(Application Service)).or(where(actor_type: nil)) } scope :recent, -> { reorder(id: :desc) } scope :bots, -> { where(actor_type: %w(Application Service)) } scope :groups, -> { where(actor_type: 'Group') } diff --git a/app/models/public_feed.rb b/app/models/public_feed.rb index fd0061c3953d02..929429aae8f6bd 100644 --- a/app/models/public_feed.rb +++ b/app/models/public_feed.rb @@ -8,6 +8,7 @@ class PublicFeed # @option [Boolean] :local # @option [Boolean] :remote # @option [Boolean] :only_media + # @option [Boolean] :without_bots def initialize(account, options = {}) @account = account @options = options @@ -68,8 +69,16 @@ def media_only? options[:only_media] end + def without_bots? + options[:without_bots] + end + def public_scope - Status.with_public_visibility.joins(:account).merge(Account.without_suspended.without_silenced) + account_scope = Account.without_suspended.without_silenced + if without_bots? + account_scope = account_scope.without_bots + end + scope = Status.with_public_visibility.joins(:account).merge(account_scope) end def local_only_scope diff --git a/app/models/tag_feed.rb b/app/models/tag_feed.rb index 9108aef1b26f15..961181d5114964 100644 --- a/app/models/tag_feed.rb +++ b/app/models/tag_feed.rb @@ -12,6 +12,7 @@ class TagFeed < PublicFeed # @option [Boolean] :local # @option [Boolean] :remote # @option [Boolean] :only_media + # @option [Boolean] :without_bots def initialize(tag, account, options = {}) @tag = tag super(account, options) diff --git a/config/initializers/99_hometown.rb b/config/initializers/99_hometown.rb new file mode 100644 index 00000000000000..afa2b9e7f4c7ba --- /dev/null +++ b/config/initializers/99_hometown.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Rails.application.configure do + config.x.local_timeline_exclude_bots = ENV['LOCAL_TIMELINE_EXCLUDE_BOTS'] == 'true' +end diff --git a/spec/models/public_feed_spec.rb b/spec/models/public_feed_spec.rb index 0ffc343f171625..ac811dc979f499 100644 --- a/spec/models/public_feed_spec.rb +++ b/spec/models/public_feed_spec.rb @@ -39,6 +39,39 @@ expect(subject).not_to include(silenced_status.id) end + describe '#without_bots=' do + let(:person_account) { Fabricate(:account, actor_type: 'Person') } + let(:bot_account) { Fabricate(:account, actor_type: 'Application') } + + context 'when the setting is false' do + subject { described_class.new(nil, without_bots: false).get(20).map(&:id) } + + it 'includes bots' do + status = Fabricate(:status, account: account) + person_status = Fabricate(:status, account: person_account) + bot_status = Fabricate(:status, account: bot_account) + + expect(subject).to include(status.id) + expect(subject).to include(person_status.id) + expect(subject).to include(bot_status.id) + end + end + + context 'when the setting is true' do + subject { described_class.new(nil, without_bots: true).get(20).map(&:id) } + + it 'filters out bot accounts' do + status = Fabricate(:status, account: account) + person_status = Fabricate(:status, account: person_account) + bot_status = Fabricate(:status, account: bot_account) + + expect(subject).to include(status.id) + expect(subject).to include(person_status.id) + expect(subject).not_to include(bot_status.id) + end + end + end + context 'without local_only option' do let(:viewer) { nil }