Add: #62 ローカルタイムラインを無効にする管理者向け設定 (#179)

* Bump version to 8.0

* Add: 他のサーバーに公開する情報に、制限設定などを追加

* Fix: `quote_of_id`のインデックス

* Fix: #172 他のサーバーからの相乗り絵文字削除が反映されない

* Test: #166 リモートから自分の絵文字を受け取った時、ライセンスが上書きされないことを確認するテスト

* Add: #62 ローカルタイムラインを無効にする管理者設定(内部挙動のみ)

* Add: 画面部分を追加
This commit is contained in:
KMY(雪あすか) 2023-10-27 08:08:50 +09:00 committed by GitHub
parent ae865975d4
commit 1d8862712a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 238 additions and 16 deletions

View file

@ -27,6 +27,7 @@ module KmyblueCapabilitiesHelper
capabilities << :enable_wide_emoji_reaction capabilities << :enable_wide_emoji_reaction
end end
capabilities << :kmyblue_visibility_public_unlisted if Setting.enable_public_unlisted_visibility capabilities << :kmyblue_visibility_public_unlisted if Setting.enable_public_unlisted_visibility
capabilities << :timeline_no_local unless Setting.enable_local_timeline
capabilities capabilities
end end
@ -58,6 +59,7 @@ module KmyblueCapabilitiesHelper
capabilities << :emoji_reaction capabilities << :emoji_reaction
capabilities << :enable_wide_emoji_reaction capabilities << :enable_wide_emoji_reaction
end end
capabilities << :timeline_no_local unless Setting.enable_local_timeline
capabilities capabilities
end end

View file

@ -25,6 +25,7 @@ const messages = defineMessages({
title: { id: 'column.about', defaultMessage: 'About' }, title: { id: 'column.about', defaultMessage: 'About' },
rules: { id: 'about.rules', defaultMessage: 'Server rules' }, rules: { id: 'about.rules', defaultMessage: 'Server rules' },
blocks: { id: 'about.blocks', defaultMessage: 'Moderated servers' }, blocks: { id: 'about.blocks', defaultMessage: 'Moderated servers' },
localTimeline: { id: 'column.community', defaultMessage: 'Local timeline' },
noop: { id: 'about.domain_blocks.noop.title', defaultMessage: 'Soft limited' }, noop: { id: 'about.domain_blocks.noop.title', defaultMessage: 'Soft limited' },
noopExplanation: { id: 'about.domain_blocks.noop.explanation', defaultMessage: 'This server is limited partically.' }, noopExplanation: { id: 'about.domain_blocks.noop.explanation', defaultMessage: 'This server is limited partically.' },
silenced: { id: 'about.domain_blocks.silenced.title', defaultMessage: 'Limited' }, silenced: { id: 'about.domain_blocks.silenced.title', defaultMessage: 'Limited' },
@ -133,6 +134,7 @@ class About extends PureComponent {
const fedibirdCapabilities = server.get('fedibird_capabilities') || []; // thinking about isLoading is true const fedibirdCapabilities = server.get('fedibird_capabilities') || []; // thinking about isLoading is true
const isPublicUnlistedVisibility = fedibirdCapabilities.includes('kmyblue_visibility_public_unlisted'); const isPublicUnlistedVisibility = fedibirdCapabilities.includes('kmyblue_visibility_public_unlisted');
const isEmojiReaction = fedibirdCapabilities.includes('emoji_reaction'); const isEmojiReaction = fedibirdCapabilities.includes('emoji_reaction');
const isLocalTimeline = !fedibirdCapabilities.includes('timeline_no_local');
return ( return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
@ -204,6 +206,9 @@ class About extends PureComponent {
<li> <li>
<span className='rules-list__text'>{intl.formatMessage(messages.publicUnlistedVisibility)}: {intl.formatMessage(isPublicUnlistedVisibility ? messages.enabled : messages.disabled)}</span> <span className='rules-list__text'>{intl.formatMessage(messages.publicUnlistedVisibility)}: {intl.formatMessage(isPublicUnlistedVisibility ? messages.enabled : messages.disabled)}</span>
</li> </li>
<li>
<span className='rules-list__text'>{intl.formatMessage(messages.localTimeline)}: {intl.formatMessage(isLocalTimeline ? messages.enabled : messages.disabled)}</span>
</li>
</ol> </ol>
)} )}
</Section> </Section>

View file

@ -46,6 +46,7 @@ import ColumnHeader from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { enableLocalTimeline } from 'mastodon/initial_state';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import RadioPanel from './components/radio_panel'; import RadioPanel from './components/radio_panel';
@ -401,7 +402,7 @@ class AntennaSetting extends PureComponent {
</button> </button>
</div> </div>
{!isLtl && ( {!isLtl && (enableLocalTimeline || isStl) && (
<div className='setting-toggle'> <div className='setting-toggle'>
<Toggle id={`antenna-${id}-stl`} defaultChecked={isStl} onChange={this.onStlToggle} /> <Toggle id={`antenna-${id}-stl`} defaultChecked={isStl} onChange={this.onStlToggle} />
<label htmlFor={`antenna-${id}-stl`} className='setting-toggle__label'> <label htmlFor={`antenna-${id}-stl`} className='setting-toggle__label'>
@ -410,7 +411,7 @@ class AntennaSetting extends PureComponent {
</div> </div>
)} )}
{!isStl && ( {!isStl && (enableLocalTimeline || isLtl) && (
<div className='setting-toggle'> <div className='setting-toggle'>
<Toggle id={`antenna-${id}-ltl`} defaultChecked={isLtl} onChange={this.onLtlToggle} /> <Toggle id={`antenna-${id}-ltl`} defaultChecked={isLtl} onChange={this.onLtlToggle} />
<label htmlFor={`antenna-${id}-ltl`} className='setting-toggle__label'> <label htmlFor={`antenna-${id}-ltl`} className='setting-toggle__label'>

View file

@ -13,7 +13,7 @@ import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming'; import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines'; import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import { DismissableBanner } from 'mastodon/components/dismissable_banner'; import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import initialState, { domain } from 'mastodon/initial_state'; import initialState, { domain, enableLocalTimeline } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store';
import Column from '../../components/column'; import Column from '../../components/column';
@ -173,9 +173,11 @@ const Firehose = ({ feedType, multiColumn }) => {
</ColumnHeader> </ColumnHeader>
<div className='account__section-headline'> <div className='account__section-headline'>
<NavLink exact to='/public/local'> {enableLocalTimeline && (
<FormattedMessage tagName='div' id='firehose.local' defaultMessage='This server' /> <NavLink exact to='/public/local'>
</NavLink> <FormattedMessage tagName='div' id='firehose.local' defaultMessage='This server' />
</NavLink>
)}
<NavLink exact to='/public/remote'> <NavLink exact to='/public/remote'>
<FormattedMessage tagName='div' id='firehose.remote' defaultMessage='Other servers' /> <FormattedMessage tagName='div' id='firehose.remote' defaultMessage='Other servers' />

View file

@ -21,7 +21,7 @@ import { ReactComponent as AntennaIcon } from '@material-symbols/svg-600/outline
import { WordmarkLogo } from 'mastodon/components/logo'; import { WordmarkLogo } from 'mastodon/components/logo';
import { NavigationPortal } from 'mastodon/components/navigation_portal'; import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { enableDtlMenu, timelinePreview, trendsEnabled, dtlTag } from 'mastodon/initial_state'; import { enableDtlMenu, timelinePreview, trendsEnabled, dtlTag, enableLocalTimeline } from 'mastodon/initial_state';
import { transientSingleColumn } from 'mastodon/is_mobile'; import { transientSingleColumn } from 'mastodon/is_mobile';
import ColumnLink from './column_link'; import ColumnLink from './column_link';
@ -108,10 +108,13 @@ class NavigationPanel extends Component {
<> <>
<ColumnLink transparent to='/home' icon='home' iconComponent={HomeIcon} text={intl.formatMessage(messages.home)} /> <ColumnLink transparent to='/home' icon='home' iconComponent={HomeIcon} text={intl.formatMessage(messages.home)} />
<ColumnLink transparent to='/notifications' icon={<NotificationsCounterIcon className='column-link__icon' />} text={intl.formatMessage(messages.notifications)} /> <ColumnLink transparent to='/notifications' icon={<NotificationsCounterIcon className='column-link__icon' />} text={intl.formatMessage(messages.notifications)} />
<ColumnLink transparent to='/public/local/fixed' icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.local)} />
</> </>
)} )}
{signedIn && enableLocalTimeline && (
<ColumnLink transparent to='/public/local/fixed' icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.local)} />
)}
{signedIn && enableDtlMenu && dtlTag && ( {signedIn && enableDtlMenu && dtlTag && (
<ColumnLink transparent to={`/tags/${dtlTag}`} icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.deepLocal)} /> <ColumnLink transparent to={`/tags/${dtlTag}`} icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.deepLocal)} />
)} )}
@ -123,7 +126,7 @@ class NavigationPanel extends Component {
)} )}
{(!signedIn && timelinePreview) && ( {(!signedIn && timelinePreview) && (
<ColumnLink transparent to='/public/local' isActive={this.isFirehoseActive} icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.firehose)} /> <ColumnLink transparent to={enableLocalTimeline ? '/public/local' : '/public'} isActive={this.isFirehoseActive} icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.firehose)} />
)} )}
{signedIn && ( {signedIn && (

View file

@ -62,6 +62,7 @@
* @property {boolean} enable_emoji_reaction * @property {boolean} enable_emoji_reaction
* @property {boolean} enable_login_privacy * @property {boolean} enable_login_privacy
* @property {boolean} enable_local_privacy * @property {boolean} enable_local_privacy
* @property {boolean} enable_local_timeline
* @property {boolean} enable_dtl_menu * @property {boolean} enable_dtl_menu
* @property {boolean=} expand_spoilers * @property {boolean=} expand_spoilers
* @property {boolean} hide_blocking_quote * @property {boolean} hide_blocking_quote
@ -136,6 +137,7 @@ export const domain = getMeta('domain');
export const dtlTag = getMeta('dtl_tag'); export const dtlTag = getMeta('dtl_tag');
export const enableEmojiReaction = getMeta('enable_emoji_reaction'); export const enableEmojiReaction = getMeta('enable_emoji_reaction');
export const enableLocalPrivacy = getMeta('enable_local_privacy'); export const enableLocalPrivacy = getMeta('enable_local_privacy');
export const enableLocalTimeline = getMeta('enable_local_timeline');
export const enableLoginPrivacy = getMeta('enable_login_privacy'); export const enableLoginPrivacy = getMeta('enable_login_privacy');
export const enableDtlMenu = getMeta('enable_dtl_menu'); export const enableDtlMenu = getMeta('enable_dtl_menu');
export const expandSpoilers = getMeta('expand_spoilers'); export const expandSpoilers = getMeta('expand_spoilers');

View file

@ -51,6 +51,7 @@ class Form::AdminSettings
check_lts_version_only check_lts_version_only
enable_public_unlisted_visibility enable_public_unlisted_visibility
unlocked_friend unlocked_friend
enable_local_timeline
).freeze ).freeze
INTEGER_KEYS = %i( INTEGER_KEYS = %i(
@ -81,6 +82,7 @@ class Form::AdminSettings
enable_public_unlisted_visibility enable_public_unlisted_visibility
unlocked_friend unlocked_friend
stranger_mention_from_local_ng stranger_mention_from_local_ng
enable_local_timeline
).freeze ).freeze
UPLOAD_KEYS = %i( UPLOAD_KEYS = %i(

View file

@ -19,6 +19,8 @@ class PublicFeed
# @param [Integer] min_id # @param [Integer] min_id
# @return [Array<Status>] # @return [Array<Status>]
def get(limit, max_id = nil, since_id = nil, min_id = nil) def get(limit, max_id = nil, since_id = nil, min_id = nil)
return [] if local_only? && !Setting.enable_local_timeline
scope = public_scope scope = public_scope
scope.merge!(without_replies_scope) unless with_replies? scope.merge!(without_replies_scope) unless with_replies?

View file

@ -23,6 +23,8 @@ class TagFeed < PublicFeed
# @param [Integer] min_id # @param [Integer] min_id
# @return [Array<Status>] # @return [Array<Status>]
def get(limit, max_id = nil, since_id = nil, min_id = nil) 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 = public_search_scope
scope.merge!(tagged_with_any_scope) scope.merge!(tagged_with_any_scope)

View file

@ -38,6 +38,7 @@ class InitialStateSerializer < ActiveModel::Serializer
sso_redirect: sso_redirect, sso_redirect: sso_redirect,
dtl_tag: DTL_ENABLED ? DTL_TAG : nil, dtl_tag: DTL_ENABLED ? DTL_TAG : nil,
enable_local_privacy: Setting.enable_public_unlisted_visibility, enable_local_privacy: Setting.enable_public_unlisted_visibility,
enable_local_timeline: Setting.enable_local_timeline,
} }
if object.current_account if object.current_account

View file

@ -27,7 +27,8 @@ class DeliveryAntennaService
return if must_dtl_tag && !DTL_ENABLED return if must_dtl_tag && !DTL_ENABLED
tag_ids = @status.tags.pluck(:id) 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) : [] follower_ids = @status.unlisted_visibility? ? @status.account.followers.pluck(:id) : []
antennas = Antenna.availables antennas = Antenna.availables
@ -77,6 +78,8 @@ class DeliveryAntennaService
end end
def delivery_stl! def delivery_stl!
return unless Setting.enable_local_timeline
antennas = Antenna.available_stls 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)) 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 @@ class DeliveryAntennaService
return if %i(public public_unlisted login).exclude?(@status.visibility.to_sym) return if %i(public public_unlisted login).exclude?(@status.visibility.to_sym)
return unless @account.local? return unless @account.local?
return if @status.reblog? return if @status.reblog?
return unless Setting.enable_local_timeline
antennas = Antenna.available_ltls 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)) antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))

View file

@ -50,12 +50,12 @@ class FanOutOnWriteService < BaseService
deliver_to_all_followers! deliver_to_all_followers!
deliver_to_lists! 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_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_stl_antennas! if Setting.enable_local_timeline
deliver_to_ltl_antennas! deliver_to_ltl_antennas! if Setting.enable_local_timeline
when :limited when :limited
deliver_to_lists_mentioned_accounts_only! deliver_to_lists_mentioned_accounts_only!
deliver_to_antennas! unless @account.dissubscribable deliver_to_antennas! unless @account.dissubscribable
deliver_to_stl_antennas! deliver_to_stl_antennas! if Setting.enable_local_timeline
deliver_to_mentioned_followers! deliver_to_mentioned_followers!
else else
deliver_to_mentioned_followers! deliver_to_mentioned_followers!
@ -152,7 +152,7 @@ class FanOutOnWriteService < BaseService
def broadcast_to_hashtag_streams! def broadcast_to_hashtag_streams!
@status.tags.map(&:name).each do |hashtag| @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}", 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
end end
@ -160,11 +160,13 @@ class FanOutOnWriteService < BaseService
return if @status.reply? && @status.in_reply_to_account_id != @account.id return if @status.reply? && @status.in_reply_to_account_id != @account.id
redis.publish('timeline:public', anonymous_payload) 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? if @status.with_media?
redis.publish('timeline:public:media', anonymous_payload) 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
end end

View file

@ -45,6 +45,9 @@
.fields-group .fields-group
= f.input :enable_public_unlisted_visibility, as: :boolean, wrapper: :with_label, kmyblue: true, hint: false = f.input :enable_public_unlisted_visibility, as: :boolean, wrapper: :with_label, kmyblue: true, hint: false
.fields-group
= f.input :enable_local_timeline, as: :boolean, wrapper: :with_label, kmyblue: true
%h4= t('admin.settings.discovery.friend_servers') %h4= t('admin.settings.discovery.friend_servers')
.fields-group .fields-group

View file

@ -98,6 +98,7 @@ en:
closed_registrations_message: Displayed when sign-ups are closed closed_registrations_message: Displayed when sign-ups are closed
content_cache_retention_period: All posts and boosts from other servers will be deleted after the specified number of days. Some posts may not be recoverable. All related bookmarks, favourites and boosts will also be lost and impossible to undo. content_cache_retention_period: All posts and boosts from other servers will be deleted after the specified number of days. Some posts may not be recoverable. All related bookmarks, favourites and boosts will also be lost and impossible to undo.
custom_css: You can apply custom styles on the web version of Mastodon. custom_css: You can apply custom styles on the web version of Mastodon.
enable_local_timeline: While enabling this feature will allow for interaction between like-minded users, it may also strengthen the internal atmosphere; since Mastodon is supposed to have a local timeline, we recommend annotating it in the server introduction if it is to be disabled.
enable_public_unlisted_visibility: If true, your community maybe closed-minded. If turn it false, strongly recommend that you disclose that you have disabled this setting! enable_public_unlisted_visibility: If true, your community maybe closed-minded. If turn it false, strongly recommend that you disclose that you have disabled this setting!
mascot: Overrides the illustration in the advanced web interface. mascot: Overrides the illustration in the advanced web interface.
media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand. media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand.
@ -336,6 +337,7 @@ en:
content_cache_retention_period: Content cache retention period content_cache_retention_period: Content cache retention period
custom_css: Custom CSS custom_css: Custom CSS
enable_emoji_reaction: Enable stamp function enable_emoji_reaction: Enable stamp function
enable_local_timeline: Enable local timeline
enable_public_unlisted_visibility: Enable public-unlisted visibility enable_public_unlisted_visibility: Enable public-unlisted visibility
mascot: Custom mascot (legacy) mascot: Custom mascot (legacy)
media_cache_retention_period: Media cache retention period media_cache_retention_period: Media cache retention period

View file

@ -112,6 +112,7 @@ ja:
closed_registrations_message: アカウント作成を停止している時に表示されます closed_registrations_message: アカウント作成を停止している時に表示されます
content_cache_retention_period: 指定した日数が経過した他のサーバーの投稿とブーストを削除します。削除された投稿は再取得できない場合があります。削除された投稿についたブックマークやお気に入り、ブーストも失われ、元に戻せません。 content_cache_retention_period: 指定した日数が経過した他のサーバーの投稿とブーストを削除します。削除された投稿は再取得できない場合があります。削除された投稿についたブックマークやお気に入り、ブーストも失われ、元に戻せません。
custom_css: ウェブ版のMastodonでカスタムスタイルを適用できます。 custom_css: ウェブ版のMastodonでカスタムスタイルを適用できます。
enable_local_timeline: 有効にすると気の合ったユーザー同士の交流が捗る反面、内輪の雰囲気が強くなるかもしれません。Mastodonはローカルタイムラインがあるものだと思われているので、無効にする場合はサーバー紹介での注記をおすすめします。
enable_public_unlisted_visibility: 有効にするとあなたのコミュニティは閉鎖的になるかもしれません。この設定はkmyblueの主要機能の1つであり、無効にする場合は概要などに記載することを強くおすすめします。 enable_public_unlisted_visibility: 有効にするとあなたのコミュニティは閉鎖的になるかもしれません。この設定はkmyblueの主要機能の1つであり、無効にする場合は概要などに記載することを強くおすすめします。
mascot: 上級者向けWebインターフェースのイラストを上書きします。 mascot: 上級者向けWebインターフェースのイラストを上書きします。
media_cache_retention_period: 正の値に設定されている場合、ダウンロードされたメディアファイルは指定された日数の後に削除され、リクエストに応じて再ダウンロードされます。 media_cache_retention_period: 正の値に設定されている場合、ダウンロードされたメディアファイルは指定された日数の後に削除され、リクエストに応じて再ダウンロードされます。
@ -352,6 +353,7 @@ ja:
content_cache_retention_period: コンテンツキャッシュの保持期間 content_cache_retention_period: コンテンツキャッシュの保持期間
custom_css: カスタムCSS custom_css: カスタムCSS
enable_emoji_reaction: スタンプ機能を有効にする enable_emoji_reaction: スタンプ機能を有効にする
enable_local_timeline: ローカルタイムラインを有効にする
enable_public_unlisted_visibility: 公開範囲「ローカル公開」を有効にする enable_public_unlisted_visibility: 公開範囲「ローカル公開」を有効にする
mascot: カスタムマスコット(レガシー) mascot: カスタムマスコット(レガシー)
media_cache_retention_period: メディアキャッシュの保持期間 media_cache_retention_period: メディアキャッシュの保持期間

View file

@ -45,6 +45,7 @@ defaults: &defaults
enable_public_unlisted_visibility: true enable_public_unlisted_visibility: true
unlocked_friend: false unlocked_friend: false
stranger_mention_from_local_ng: true stranger_mention_from_local_ng: true
enable_local_timeline: true
development: development:
<<: *defaults <<: *defaults

View file

@ -130,6 +130,16 @@ RSpec.describe PublicFeed do
it 'includes public_unlisted statuses' do it 'includes public_unlisted statuses' do
expect(subject).to include(public_unlisted_status.id) 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 end
context 'with a viewer' do context 'with a viewer' do
@ -150,6 +160,16 @@ RSpec.describe PublicFeed do
expect(subject).to include(public_unlisted_status.id) expect(subject).to include(public_unlisted_status.id)
end end
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 end
context 'with a remote_only option set' do context 'with a remote_only option set' do

View file

@ -59,6 +59,12 @@ describe TagFeed, type: :service do
expect(results).to_not include status_tagged_with_cats expect(results).to_not include status_tagged_with_cats
end 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 it 'allows replies to be included' do
original = Fabricate(:status) original = Fabricate(:status)
status = Fabricate(:status, tags: [tag_cats], in_reply_to_id: original.id) status = Fabricate(:status, tags: [tag_cats], in_reply_to_id: original.id)

View file

@ -7,9 +7,12 @@ describe 'Public' do
let(:scopes) { 'read:statuses' } let(:scopes) { 'read:statuses' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:ltl_enabled) { true }
shared_examples 'a successful request to the public timeline' do shared_examples 'a successful request to the public timeline' do
it 'returns the expected statuses successfully', :aggregate_failures do it 'returns the expected statuses successfully', :aggregate_failures do
Form::AdminSettings.new(enable_local_timeline: '0').save unless ltl_enabled
subject subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
@ -47,6 +50,13 @@ describe 'Public' do
let(:expected_statuses) { [local_status, media_status] } let(:expected_statuses) { [local_status, media_status] }
it_behaves_like 'a successful request to the public timeline' it_behaves_like 'a successful request to the public timeline'
context 'when local timeline is disabled' do
let(:expected_statuses) { [] }
let(:ltl_enabled) { false }
it_behaves_like 'a successful request to the public timeline'
end
end end
context 'with remote param' do context 'with remote param' do
@ -54,6 +64,12 @@ describe 'Public' do
let(:expected_statuses) { [remote_status] } let(:expected_statuses) { [remote_status] }
it_behaves_like 'a successful request to the public timeline' it_behaves_like 'a successful request to the public timeline'
context 'when local timeline is disabled' do
let(:ltl_enabled) { false }
it_behaves_like 'a successful request to the public timeline'
end
end end
context 'with local and remote params' do context 'with local and remote params' do
@ -61,6 +77,12 @@ describe 'Public' do
let(:expected_statuses) { [local_status, remote_status, media_status] } let(:expected_statuses) { [local_status, remote_status, media_status] }
it_behaves_like 'a successful request to the public timeline' it_behaves_like 'a successful request to the public timeline'
context 'when local timeline is disabled' do
let(:ltl_enabled) { false }
it_behaves_like 'a successful request to the public timeline'
end
end end
context 'with only_media param' do context 'with only_media param' do

View file

@ -5,6 +5,8 @@ require 'rails_helper'
RSpec.describe DeliveryAntennaService, type: :service do RSpec.describe DeliveryAntennaService, type: :service do
subject { described_class.new } subject { described_class.new }
let(:ltl_enabled) { true }
let(:last_active_at) { Time.now.utc } let(:last_active_at) { Time.now.utc }
let(:last_active_at_tom) { Time.now.utc } let(:last_active_at_tom) { Time.now.utc }
let(:visibility) { :public } let(:visibility) { :public }
@ -36,6 +38,8 @@ RSpec.describe DeliveryAntennaService, type: :service do
bob.follow!(alice) bob.follow!(alice)
alice.block!(ohagi) alice.block!(ohagi)
Form::AdminSettings.new(enable_local_timeline: '0').save unless ltl_enabled
allow(redis).to receive(:publish) allow(redis).to receive(:publish)
subject.call(status, false, mode: mode) subject.call(status, false, mode: mode)
@ -124,6 +128,29 @@ RSpec.describe DeliveryAntennaService, type: :service do
end end
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 context 'with tag' do
let!(:antenna) { antenna_with_tag(bob, 'hoge') } let!(:antenna) { antenna_with_tag(bob, 'hoge') }
let!(:empty_antenna) { antenna_with_tag(tom, 'hog') } let!(:empty_antenna) { antenna_with_tag(tom, 'hog') }

View file

@ -5,6 +5,8 @@ require 'rails_helper'
RSpec.describe FanOutOnWriteService, type: :service do RSpec.describe FanOutOnWriteService, type: :service do
subject { described_class.new } subject { described_class.new }
let(:ltl_enabled) { true }
let(:last_active_at) { Time.now.utc } let(:last_active_at) { Time.now.utc }
let(:visibility) { 'public' } let(:visibility) { 'public' }
let(:searchability) { 'public' } let(:searchability) { 'public' }
@ -28,6 +30,8 @@ RSpec.describe FanOutOnWriteService, type: :service do
tom.follow!(alice) tom.follow!(alice)
ohagi.follow!(bob) ohagi.follow!(bob)
Form::AdminSettings.new(enable_local_timeline: '0').save unless ltl_enabled
ProcessMentionsService.new.call(status) ProcessMentionsService.new.call(status)
ProcessHashtagsService.new.call(status) ProcessHashtagsService.new.call(status)
@ -86,6 +90,20 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(redis).to have_received(:publish).with('timeline:public:local', anything) expect(redis).to have_received(:publish).with('timeline:public:local', anything)
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
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 context 'with list' do
let!(:list) { list_with_account(bob, alice) } let!(:list) { list_with_account(bob, alice) }
let!(:empty_list) { Fabricate(:list, account: tom) } let!(:empty_list) { Fabricate(:list, account: tom) }
@ -130,6 +148,15 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(antenna)).to include status.id
end end
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
context 'with LTL antenna' do context 'with LTL antenna' do
@ -148,6 +175,15 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(antenna)).to include status.id
end end
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
end end
@ -255,6 +291,15 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(antenna)).to include status.id
expect(antenna_feed_of(empty_antenna)).to_not include status.id expect(antenna_feed_of(empty_antenna)).to_not 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
context 'with LTL antenna' do context 'with LTL antenna' do
@ -263,6 +308,14 @@ RSpec.describe FanOutOnWriteService, type: :service do
it 'is added to the antenna feed of antenna follower' do it 'is added to the antenna feed of antenna follower' do
expect(antenna_feed_of(empty_antenna)).to_not include status.id expect(antenna_feed_of(empty_antenna)).to_not 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(empty_antenna)).to_not include status.id
end
end
end end
end end
@ -284,6 +337,20 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(redis).to have_received(:publish).with('timeline:public', anything) expect(redis).to have_received(:publish).with('timeline:public', anything)
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
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 context 'with list' do
let!(:list) { list_with_account(bob, alice) } let!(:list) { list_with_account(bob, alice) }
let!(:empty_list) { list_with_account(ohagi, bob) } let!(:empty_list) { list_with_account(ohagi, bob) }
@ -328,6 +395,15 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(antenna)).to include status.id
end end
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
context 'with LTL antenna' do context 'with LTL antenna' do
@ -346,6 +422,15 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(antenna)).to include status.id
end end
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
end end
@ -384,6 +469,15 @@ RSpec.describe FanOutOnWriteService, type: :service do
end end
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 context 'with list' do
let!(:list) { list_with_account(bob, alice) } let!(:list) { list_with_account(bob, alice) }
let!(:empty_list) { list_with_account(ohagi, bob) } let!(:empty_list) { list_with_account(ohagi, bob) }
@ -412,6 +506,15 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(antenna)).to include status.id
expect(antenna_feed_of(empty_antenna)).to_not include status.id expect(antenna_feed_of(empty_antenna)).to_not 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
context 'with LTL antenna' do context 'with LTL antenna' do
@ -420,6 +523,14 @@ RSpec.describe FanOutOnWriteService, type: :service do
it 'is added to the antenna feed of antenna follower' do it 'is added to the antenna feed of antenna follower' do
expect(antenna_feed_of(empty_antenna)).to_not include status.id expect(antenna_feed_of(empty_antenna)).to_not 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(empty_antenna)).to_not include status.id
end
end
end end
context 'with non-public searchability' do context 'with non-public searchability' do