diff --git a/app/helpers/kmyblue_capabilities_helper.rb b/app/helpers/kmyblue_capabilities_helper.rb index 07f22e8db5a958..2e6187ce262582 100644 --- a/app/helpers/kmyblue_capabilities_helper.rb +++ b/app/helpers/kmyblue_capabilities_helper.rb @@ -27,6 +27,7 @@ def fedibird_capabilities capabilities << :enable_wide_emoji_reaction end capabilities << :kmyblue_visibility_public_unlisted if Setting.enable_public_unlisted_visibility + capabilities << :timeline_no_local unless Setting.enable_local_timeline capabilities end @@ -58,6 +59,7 @@ def capabilities_for_nodeinfo capabilities << :emoji_reaction capabilities << :enable_wide_emoji_reaction end + capabilities << :timeline_no_local unless Setting.enable_local_timeline capabilities end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 55909bc0ce98f8..bec1d4c869b3b4 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -51,6 +51,7 @@ class Form::AdminSettings check_lts_version_only enable_public_unlisted_visibility unlocked_friend + enable_local_timeline ).freeze INTEGER_KEYS = %i( @@ -81,6 +82,7 @@ class Form::AdminSettings enable_public_unlisted_visibility unlocked_friend stranger_mention_from_local_ng + enable_local_timeline ).freeze UPLOAD_KEYS = %i( diff --git a/app/models/public_feed.rb b/app/models/public_feed.rb index 0ca7621060a8a6..cc91741c5f5a84 100644 --- a/app/models/public_feed.rb +++ b/app/models/public_feed.rb @@ -19,6 +19,8 @@ def initialize(account, options = {}) # @param [Integer] min_id # @return [Array] def get(limit, max_id = nil, since_id = nil, min_id = nil) + return [] if local_only? && !Setting.enable_local_timeline + scope = public_scope scope.merge!(without_replies_scope) unless with_replies? diff --git a/app/models/tag_feed.rb b/app/models/tag_feed.rb index 9c679a02495453..027306350e9e97 100644 --- a/app/models/tag_feed.rb +++ b/app/models/tag_feed.rb @@ -23,6 +23,8 @@ def initialize(tag, account, options = {}) # @param [Integer] min_id # @return [Array] def get(limit, max_id = nil, since_id = nil, min_id = nil) + return [] if local_only? && !Setting.enable_local_timeline + scope = public_search_scope scope.merge!(tagged_with_any_scope) diff --git a/app/services/delivery_antenna_service.rb b/app/services/delivery_antenna_service.rb index fb60473c302643..f17b7815984518 100644 --- a/app/services/delivery_antenna_service.rb +++ b/app/services/delivery_antenna_service.rb @@ -27,7 +27,8 @@ def delivery! return if must_dtl_tag && !DTL_ENABLED tag_ids = @status.tags.pluck(:id) - domain = @account.domain || Rails.configuration.x.local_domain + domain = @account.domain + domain ||= Rails.configuration.x.local_domain if Setting.enable_local_timeline follower_ids = @status.unlisted_visibility? ? @status.account.followers.pluck(:id) : [] antennas = Antenna.availables @@ -77,6 +78,8 @@ def delivery! end def delivery_stl! + return unless Setting.enable_local_timeline + antennas = Antenna.available_stls antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)) @@ -101,6 +104,7 @@ def delivery_ltl! return if %i(public public_unlisted login).exclude?(@status.visibility.to_sym) return unless @account.local? return if @status.reblog? + return unless Setting.enable_local_timeline antennas = Antenna.available_ltls antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)) diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 6df3364d85e26c..07b887a2d8b341 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -50,12 +50,12 @@ def fan_out_to_local_recipients! deliver_to_all_followers! deliver_to_lists! deliver_to_antennas! if !@account.dissubscribable || (@status.dtl? && DTL_ENABLED && @account.user&.setting_dtl_force_subscribable && @status.tags.exists?(name: DTL_TAG)) - deliver_to_stl_antennas! - deliver_to_ltl_antennas! + deliver_to_stl_antennas! if Setting.enable_local_timeline + deliver_to_ltl_antennas! if Setting.enable_local_timeline when :limited deliver_to_lists_mentioned_accounts_only! deliver_to_antennas! unless @account.dissubscribable - deliver_to_stl_antennas! + deliver_to_stl_antennas! if Setting.enable_local_timeline deliver_to_mentioned_followers! else deliver_to_mentioned_followers! @@ -152,7 +152,7 @@ def deliver_to_mentioned_followers! def broadcast_to_hashtag_streams! @status.tags.map(&:name).each do |hashtag| redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}", anonymous_payload) - redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", anonymous_payload) if @status.local? + redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", anonymous_payload) if @status.local? && Setting.enable_local_timeline end end @@ -160,11 +160,13 @@ def broadcast_to_public_streams! return if @status.reply? && @status.in_reply_to_account_id != @account.id redis.publish('timeline:public', anonymous_payload) - redis.publish(@status.local? ? 'timeline:public:local' : 'timeline:public:remote', anonymous_payload) + redis.publish('timeline:public:remote', anonymous_payload) unless @status.local? + redis.publish('timeline:public:local', anonymous_payload) if @status.local? && Setting.enable_local_timeline if @status.with_media? redis.publish('timeline:public:media', anonymous_payload) - redis.publish(@status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', anonymous_payload) + redis.publish('timeline:public:remote:media', anonymous_payload) unless @status.local? + redis.publish('timeline:public:local:media', anonymous_payload) if @status.local? && Setting.enable_local_timeline end end diff --git a/config/settings.yml b/config/settings.yml index 65128db7f0ff7f..1c27e1f542837d 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -45,6 +45,7 @@ defaults: &defaults enable_public_unlisted_visibility: true unlocked_friend: false stranger_mention_from_local_ng: true + enable_local_timeline: true development: <<: *defaults diff --git a/spec/models/public_feed_spec.rb b/spec/models/public_feed_spec.rb index 3d13783a50f103..34ff929dc78629 100644 --- a/spec/models/public_feed_spec.rb +++ b/spec/models/public_feed_spec.rb @@ -130,6 +130,16 @@ it 'includes public_unlisted statuses' do expect(subject).to include(public_unlisted_status.id) end + + context 'when local timeline is disabled' do + before do + Form::AdminSettings.new(enable_local_timeline: '0').save + end + + it 'does not include all statuses' do + expect(subject).to eq [] + end + end end context 'with a viewer' do @@ -150,6 +160,16 @@ expect(subject).to include(public_unlisted_status.id) end end + + context 'when local timeline is disabled' do + before do + Form::AdminSettings.new(enable_local_timeline: '0').save + end + + it 'does not include all statuses' do + expect(subject).to eq [] + end + end end context 'with a remote_only option set' do diff --git a/spec/models/tag_feed_spec.rb b/spec/models/tag_feed_spec.rb index 5206e7cedec436..a4286f570f2aa8 100644 --- a/spec/models/tag_feed_spec.rb +++ b/spec/models/tag_feed_spec.rb @@ -59,6 +59,12 @@ expect(results).to_not include status_tagged_with_cats end + it 'can restrict to local but local timeline is disabled' do + Form::AdminSettings.new(enable_local_timeline: '0').save + results = described_class.new(tag_cats, nil, any: [tag_dogs.name], local: true).get(20) + expect(results).to_not include status_tagged_with_cats + end + it 'allows replies to be included' do original = Fabricate(:status) status = Fabricate(:status, tags: [tag_cats], in_reply_to_id: original.id) diff --git a/spec/services/delivery_antenna_service_spec.rb b/spec/services/delivery_antenna_service_spec.rb index 684a5da9dd5247..becb7fa410bf66 100644 --- a/spec/services/delivery_antenna_service_spec.rb +++ b/spec/services/delivery_antenna_service_spec.rb @@ -5,6 +5,8 @@ RSpec.describe DeliveryAntennaService, type: :service do subject { described_class.new } + let(:ltl_enabled) { true } + let(:last_active_at) { Time.now.utc } let(:last_active_at_tom) { Time.now.utc } let(:visibility) { :public } @@ -36,6 +38,8 @@ bob.follow!(alice) alice.block!(ohagi) + Form::AdminSettings.new(enable_local_timeline: '0').save unless ltl_enabled + allow(redis).to receive(:publish) subject.call(status, false, mode: mode) @@ -124,6 +128,29 @@ def list(owner) end end + context 'with local domain' do + let(:domain) { nil } + let!(:antenna) { antenna_with_domain(bob, 'cb6e6126.ngrok.io') } + let!(:empty_antenna) { antenna_with_domain(tom, 'ohagi.example.com') } + + it 'detecting antenna' do + expect(antenna_feed_of(antenna)).to include status.id + end + + it 'not detecting antenna' do + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'not detecting antenna' do + expect(antenna_feed_of(antenna)).to_not include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + end + end + context 'with tag' do let!(:antenna) { antenna_with_tag(bob, 'hoge') } let!(:empty_antenna) { antenna_with_tag(tom, 'hog') } diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb index 1df413b7a8d6d9..e7339e15744027 100644 --- a/spec/services/fan_out_on_write_service_spec.rb +++ b/spec/services/fan_out_on_write_service_spec.rb @@ -5,6 +5,8 @@ RSpec.describe FanOutOnWriteService, type: :service do subject { described_class.new } + let(:ltl_enabled) { true } + let(:last_active_at) { Time.now.utc } let(:visibility) { 'public' } let(:searchability) { 'public' } @@ -28,6 +30,8 @@ tom.follow!(alice) ohagi.follow!(bob) + Form::AdminSettings.new(enable_local_timeline: '0').save unless ltl_enabled + ProcessMentionsService.new.call(status) ProcessHashtagsService.new.call(status) @@ -86,6 +90,20 @@ def antenna_with_options(owner, **options) expect(redis).to have_received(:publish).with('timeline:public:local', anything) end + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is broadcast to the hashtag stream' do + expect(redis).to have_received(:publish).with('timeline:hashtag:hoge', anything) + expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge:local', anything) + end + + it 'is broadcast to the public stream' do + expect(redis).to have_received(:publish).with('timeline:public', anything) + expect(redis).to_not have_received(:publish).with('timeline:public:local', anything) + end + end + context 'with list' do let!(:list) { list_with_account(bob, alice) } let!(:empty_list) { Fabricate(:list, account: tom) } @@ -130,6 +148,15 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(antenna)).to include status.id end end + + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is not added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to_not include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + end end context 'with LTL antenna' do @@ -148,6 +175,15 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(antenna)).to include status.id end end + + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is not added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to_not include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + end end end @@ -255,6 +291,15 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is not added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to_not include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + end end context 'with LTL antenna' do @@ -263,6 +308,14 @@ def antenna_with_options(owner, **options) it 'is added to the antenna feed of antenna follower' do expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is not added to the antenna feed of antenna follower' do + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + end end end @@ -284,6 +337,20 @@ def antenna_with_options(owner, **options) expect(redis).to have_received(:publish).with('timeline:public', anything) end + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is broadcast to the hashtag stream' do + expect(redis).to have_received(:publish).with('timeline:hashtag:hoge', anything) + expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge:local', anything) + end + + it 'is broadcast to the public stream' do + expect(redis).to have_received(:publish).with('timeline:public', anything) + expect(redis).to_not have_received(:publish).with('timeline:public:local', anything) + end + end + context 'with list' do let!(:list) { list_with_account(bob, alice) } let!(:empty_list) { list_with_account(ohagi, bob) } @@ -328,6 +395,15 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(antenna)).to include status.id end end + + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is not added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to_not include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + end end context 'with LTL antenna' do @@ -346,6 +422,15 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(antenna)).to include status.id end end + + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is not added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to_not include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + end end end @@ -384,6 +469,15 @@ def antenna_with_options(owner, **options) end end + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is broadcast to the hashtag stream' do + expect(redis).to have_received(:publish).with('timeline:hashtag:hoge', anything) + expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge:local', anything) + end + end + context 'with list' do let!(:list) { list_with_account(bob, alice) } let!(:empty_list) { list_with_account(ohagi, bob) } @@ -412,6 +506,15 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is not added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to_not include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + end end context 'with LTL antenna' do @@ -420,6 +523,14 @@ def antenna_with_options(owner, **options) it 'is added to the antenna feed of antenna follower' do expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when local timeline is disabled' do + let(:ltl_enabled) { false } + + it 'is not added to the antenna feed of antenna follower' do + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + end end context 'with non-public searchability' do