diff --git a/app/javascript/mastodon/features/compose/components/searchability_dropdown.jsx b/app/javascript/mastodon/features/compose/components/searchability_dropdown.jsx index e7a002b9d88678..3b597e7735c99a 100644 --- a/app/javascript/mastodon/features/compose/components/searchability_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/searchability_dropdown.jsx @@ -15,6 +15,8 @@ import { IconButton } from '../../../components/icon_button'; const messages = defineMessages({ public_short: { id: 'searchability.public.short', defaultMessage: 'Public' }, public_long: { id: 'searchability.public.long', defaultMessage: 'Anyone can find' }, + public_unlisted_short: { id: 'searchability.public_unlisted.short', defaultMessage: 'Public unlisted' }, + public_unlisted_long: { id: 'searchability.public_unlisted.long', defaultMessage: 'Local users and followers can find' }, private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' }, private_long: { id: 'searchability.unlisted.long', defaultMessage: 'Your followers can find' }, direct_short: { id: 'searchability.private.short', defaultMessage: 'Reactionners' }, @@ -223,6 +225,7 @@ class SearchabilityDropdown extends PureComponent { this.options = [ { icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) }, + { icon: 'cloud', value: 'public_unlisted', text: formatMessage(messages.public_unlisted_short), meta: formatMessage(messages.public_unlisted_long) }, { icon: 'unlock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) }, { icon: 'lock', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) }, { icon: 'at', value: 'limited', text: formatMessage(messages.limited_short), meta: formatMessage(messages.limited_long) }, diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index e9568bf9d0ccf5..ce60f2459c143b 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -38,6 +38,7 @@ const messages = defineMessages({ personal_short: { id: 'privacy.personal.short', defaultMessage: 'Yourself only' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' }, + searchability_public_unlisted_short: { id: 'searchability.public_unlisted.short', defaultMessage: 'Public unlisted' }, searchability_private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' }, searchability_direct_short: { id: 'searchability.private.short', defaultMessage: 'Reactionners' }, searchability_limited_short: { id: 'searchability.direct.short', defaultMessage: 'Self only' }, @@ -270,6 +271,7 @@ class DetailedStatus extends ImmutablePureComponent { const searchabilityIconInfo = { 'public': { icon: 'globe', text: intl.formatMessage(messages.searchability_public_short) }, + 'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.searchability_public_unlisted_short) }, 'private': { icon: 'unlock', text: intl.formatMessage(messages.searchability_private_short) }, 'direct': { icon: 'lock', text: intl.formatMessage(messages.searchability_direct_short) }, 'limited': { icon: 'at', text: intl.formatMessage(messages.searchability_limited_short) }, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 780a5df5c8e5bc..8fbc847d1a90f3 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -629,6 +629,8 @@ "searchability.private.short": "Reactionners", "searchability.public.long": "Anyone can find", "searchability.public.short": "Everyone", + "searchability.public_unlisted.long": "Local users and followers can find", + "searchability.public_unlisted.short": "Local and followers", "searchability.unlisted.long": "Your followers and reactionners can find", "searchability.unlisted.short": "Followers and reactionners", "search_popout.domain": "domain", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index fdd4e0cdc19e3f..4d5f0cbc9a7ac4 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -714,6 +714,8 @@ "searchability.private.short": "反応者のみ", "searchability.public.long": "この投稿は誰でも検索できます", "searchability.public.short": "誰でも", + "searchability.public_unlisted.long": "ローカルユーザーとフォロワーが検索できます", + "searchability.public_unlisted.short": "ローカルとフォロワー", "searchability.unlisted.long": "この投稿はあなたのフォロワーと反応者だけが検索できます", "searchability.unlisted.short": "フォロワーと反応者", "search_popout.domain": "ドメイン", diff --git a/app/lib/account_statuses_filter.rb b/app/lib/account_statuses_filter.rb index 9a4a2b5d6ea4fc..4ff59756102896 100644 --- a/app/lib/account_statuses_filter.rb +++ b/app/lib/account_statuses_filter.rb @@ -26,7 +26,7 @@ def results scope.merge!(no_reblogs_scope) if exclude_reblogs? scope.merge!(hashtag_scope) if tagged? - available_searchabilities = [:public, :unlisted, :private, :direct, :limited, nil] + available_searchabilities = [:public, :public_unlisted, :unlisted, :private, :direct, :limited, nil] available_visibilities = [:public, :public_unlisted, :login, :unlisted, :private, :direct, :limited] available_searchabilities = [:public] if domain_block&.reject_send_not_public_searchability diff --git a/app/lib/importer/statuses_index_importer.rb b/app/lib/importer/statuses_index_importer.rb index 9fbce14478f581..5a8fa29c869b17 100644 --- a/app/lib/importer/statuses_index_importer.rb +++ b/app/lib/importer/statuses_index_importer.rb @@ -23,7 +23,7 @@ def import! to_index.map do |object| # This is unlikely to happen, but the post may have been # un-interacted with since it was queued for indexing - if object.searchable_by.empty? && %w(public private).exclude?(object.searchability) + if object.searchable_by.empty? && %w(public public_unlisted private).exclude?(object.searchability) deleted += 1 { delete: { _id: object.id } } else diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index ba4383f6741bf5..567b10ade2b107 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -89,10 +89,10 @@ def default_filter public_index, searchability_limited, ] - definition_should << searchability_public if %i(public).include?(@searchability) - definition_should << searchability_private if %i(public unlisted private).include?(@searchability) - definition_should << searchable_by_me if %i(public unlisted private direct).include?(@searchability) - definition_should << self_posts if %i(public unlisted private direct).exclude?(@searchability) + definition_should << searchability_public if %i(public public_unlisted).include?(@searchability) + definition_should << searchability_private if %i(public public_unlisted unlisted private).include?(@searchability) + definition_should << searchable_by_me if %i(public public_unlisted unlisted private direct).include?(@searchability) + definition_should << self_posts if %i(public public_unlisted unlisted private direct).exclude?(@searchability) { bool: { @@ -199,8 +199,8 @@ def searchability_limited def following_account_ids return @following_account_ids if defined?(@following_account_ids) - account_exists_sql = Account.where('accounts.id = follows.target_account_id').where(searchability: %w(public private)).reorder(nil).select(1).to_sql - status_exists_sql = Status.where('statuses.account_id = follows.target_account_id').where(reblog_of_id: nil).where(searchability: %w(public private)).reorder(nil).select(1).to_sql + account_exists_sql = Account.where('accounts.id = follows.target_account_id').where(searchability: %w(public public_unlisted private)).reorder(nil).select(1).to_sql + status_exists_sql = Status.where('statuses.account_id = follows.target_account_id').where(reblog_of_id: nil).where(searchability: %w(public public_unlisted private)).reorder(nil).select(1).to_sql following_accounts = Follow.where(account_id: @options[:current_account].id).merge(Account.where("EXISTS (#{account_exists_sql})").or(Account.where("EXISTS (#{status_exists_sql})"))) @following_account_ids = following_accounts.pluck(:target_account_id) end diff --git a/app/models/concerns/status_search_concern.rb b/app/models/concerns/status_search_concern.rb index 376f82e509fbe2..e73a12d5115210 100644 --- a/app/models/concerns/status_search_concern.rb +++ b/app/models/concerns/status_search_concern.rb @@ -5,7 +5,7 @@ module StatusSearchConcern included do scope :indexable, -> { without_reblogs.where(visibility: [:public, :login], searchability: nil).joins(:account).where(account: { indexable: true }) } - scope :remote_dynamic_searchability, -> { remote.where(searchability: [:public, :private]) } + scope :remote_dynamic_searchability, -> { remote.where(searchability: [:public, :public_unlisted, :private]) } end def searchable_by diff --git a/app/models/status.rb b/app/models/status.rb index 4c076ca5d7b745..5a33411aba0f85 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -129,7 +129,7 @@ class Status < ApplicationRecord scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) } scope :with_public_visibility, -> { where(visibility: [:public, :public_unlisted, :login]) } - scope :with_public_search_visibility, -> { merge(where(visibility: [:public, :public_unlisted, :login]).or(Status.where(searchability: :public))) } + scope :with_public_search_visibility, -> { merge(where(visibility: [:public, :public_unlisted, :login]).or(Status.where(searchability: [:public, :public_unlisted]))) } scope :with_global_timeline_visibility, -> { where(visibility: [:public, :login]) } scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) } scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) } @@ -442,19 +442,26 @@ def requires_review_notification? def compute_searchability local = account.local? + check_searchability = public_unlisted_searchability? ? 'public' : searchability - return 'private' if public_searchability? && account.silenced? + return 'private' if %w(public public_unlisted).include?(check_searchability) && account.silenced? return 'direct' if unsupported_searchability? - return searchability if local && !searchability.nil? - return 'direct' if local || [:public, :private, :direct, :limited].exclude?(account.searchability.to_sym) + return check_searchability if local && !check_searchability.nil? + return 'direct' if local || %i(public private direct limited).exclude?(account.searchability.to_sym) account_searchability = Status.searchabilities[account.searchability] - status_searchability = Status.searchabilities[searchability.nil? ? 'direct' : searchability] + status_searchability = Status.searchabilities[check_searchability.nil? ? 'direct' : check_searchability] Status.searchabilities.invert.fetch([account_searchability, status_searchability].max) || 'direct' end def compute_searchability_activitypub - return 'private' if public_unlisted_visibility? && public_searchability? + return 'private' if public_unlisted_searchability? + + compute_searchability + end + + def compute_searchability_local + return 'public_unlisted' if public_unlisted_searchability? compute_searchability end @@ -477,6 +484,10 @@ def selectable_reblog_visibilities end def selectable_searchabilities + searchabilities.keys - %w(unsupported) + end + + def selectable_searchabilities_for_search searchabilities.keys - %w(public_unlisted unsupported) end @@ -620,7 +631,7 @@ def set_searchability elsif visibility == 'limited' :limited elsif visibility == 'private' - searchability == 'public' ? :private : searchability + searchability == 'public' || searchability == 'public_unlisted' ? :private : searchability elsif visibility == 'direct' searchability == 'limited' ? :limited : :direct else diff --git a/app/models/trends/statuses.rb b/app/models/trends/statuses.rb index f2c04f220ddb22..c7fe097904b040 100644 --- a/app/models/trends/statuses.rb +++ b/app/models/trends/statuses.rb @@ -106,7 +106,8 @@ def klass private def eligible?(status) - (status.searchability.nil? || status.public_searchability?) && (status.public_visibility? || status.public_unlisted_visibility?) && + (status.searchability.nil? || status.compute_searchability == 'public') && + (status.public_visibility? || status.public_unlisted_visibility?) && status.account.discoverable? && !status.account.silenced? && status.spoiler_text.blank? && (!status.sensitive? || status.media_attachments.none?) && !status.reply? && valid_locale?(status.language) end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index e9ac924f7ee897..1fcc939cdbd0e9 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -93,7 +93,7 @@ def limited_scope end def searchability - object.compute_searchability + object.compute_searchability_local end def sensitive diff --git a/app/services/delivery_antenna_service.rb b/app/services/delivery_antenna_service.rb index 3d263219954ac4..fb60473c302643 100644 --- a/app/services/delivery_antenna_service.rb +++ b/app/services/delivery_antenna_service.rb @@ -64,8 +64,10 @@ def delivery! next if antenna.exclude_accounts&.include?(@status.account_id) next if antenna.exclude_domains&.include?(domain) next if antenna.exclude_tags&.any? { |tag_id| tag_ids.include?(tag_id) } - next if @status.unlisted_visibility? && !@status.public_searchability? && follower_ids.exclude?(antenna.account_id) - next if @status.unlisted_visibility? && @status.public_searchability? && follower_ids.exclude?(antenna.account_id) && antenna.any_keywords && antenna.any_tags + + searchability = @status.compute_searchability + next if @status.unlisted_visibility? && searchability != 'public' && follower_ids.exclude?(antenna.account_id) + next if @status.unlisted_visibility? && searchability == 'public' && follower_ids.exclude?(antenna.account_id) && antenna.any_keywords && antenna.any_tags collection.push(antenna) end @@ -121,7 +123,7 @@ def followers_only? when :public, :public_unlisted, :login, :limited false when :unlisted - !@status.public_searchability? + @status.compute_searchability != 'public' else true end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index a2e2653813ab4d..b56474ddf61a9e 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -218,6 +218,6 @@ def broadcastable_unlisted? end def broadcastable_unlisted2? - @status.unlisted_visibility? && @status.public_searchability? && !@status.reblog? && !@account.silenced? + @status.unlisted_visibility? && @status.compute_searchability == 'public' && !@status.reblog? && !@account.silenced? end end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index bf2ba2cdbf9ea5..cdd1aa86747e89 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -81,7 +81,7 @@ def preprocess_attributes! @visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted && Setting.enable_public_unlisted_visibility @limited_scope = @options[:visibility]&.to_sym if @visibility == :limited @searchability = searchability - @searchability = :private if @account.silenced? && @searchability&.to_sym == :public + @searchability = :private if @account.silenced? && %i(public public_unlisted).include?(@searchability&.to_sym) @markdown = @options[:markdown] || false @scheduled_at = @options[:scheduled_at]&.to_datetime @scheduled_at = nil if scheduled_in_the_past? @@ -129,6 +129,8 @@ def searchability case @options[:searchability]&.to_sym when :public case @visibility&.to_sym when :public, :public_unlisted, :login, :unlisted then :public when :private then :private else :direct end + when :public_unlisted + case @visibility&.to_sym when :public, :public_unlisted, :login, :unlisted then :public_unlisted when :private then :private else :direct end when :private case @visibility&.to_sym when :public, :public_unlisted, :login, :unlisted, :private then :private else :direct end when :direct diff --git a/app/views/settings/preferences/reaching/show.html.haml b/app/views/settings/preferences/reaching/show.html.haml index 03077758aa32cb..3e330ad590bb17 100644 --- a/app/views/settings/preferences/reaching/show.html.haml +++ b/app/views/settings/preferences/reaching/show.html.haml @@ -41,7 +41,7 @@ .fields-row .fields-group.fields-row__column.fields-row__column-12 - = ff.input :default_searchability_of_search, collection: Status.selectable_searchabilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |searchability| safe_join([I18n.t("statuses.searchabilities.#{searchability}"), I18n.t("statuses.searchabilities.#{searchability}_search_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_searchability_of_search') + = ff.input :default_searchability_of_search, collection: Status.selectable_searchabilities_for_search, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |searchability| safe_join([I18n.t("statuses.searchabilities.#{searchability}"), I18n.t("statuses.searchabilities.#{searchability}_search_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_searchability_of_search') .fields-group = ff.input :use_public_index, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_use_public_index') diff --git a/config/locales/en.yml b/config/locales/en.yml index d56dd1d3fbade6..8633f4dfc03097 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1836,6 +1836,8 @@ en: public: Public public_long: Anyone can find public_search_long: You can search all posts permitted to search + public_unlisted: Local and followers + public_unlisted_long: Local users and followers can find show_more: Show more show_newer: Show newer show_older: Show older diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 2447395e88c65c..87de99e51528f8 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1814,6 +1814,8 @@ ja: public: 誰でも public_long: この投稿は誰でも検索できます public_search_long: 検索が許可された全ての投稿が検索できます + public_unlisted: ローカルとフォロワー + public_unlisted_long: ローカル・フォロワー・反応者のみが検索できます show_more: もっと見る show_newer: 新しいものを表示 show_older: 古いものを表示 diff --git a/spec/lib/status_reach_finder_spec.rb b/spec/lib/status_reach_finder_spec.rb index ba319b5423418d..3d51fedb1458af 100644 --- a/spec/lib/status_reach_finder_spec.rb +++ b/spec/lib/status_reach_finder_spec.rb @@ -67,6 +67,22 @@ end end + context 'when misskey with public_unlisted searchability' do + let(:sender_software) { 'misskey' } + let(:searchability) { :public_unlisted } + + it 'send status without setting' do + expect(subject.inboxes).to include 'https://foo.bar/inbox' + expect(subject.inboxes_for_misskey).to_not include 'https://foo.bar/inbox' + end + + it 'send status with setting' do + alice.user.settings.update(reject_unlisted_subscription: 'true') + expect(subject.inboxes).to_not include 'https://foo.bar/inbox' + expect(subject.inboxes_for_misskey).to include 'https://foo.bar/inbox' + end + end + context 'when misskey with public searchability' do let(:sender_software) { 'misskey' } diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 36f4fddec9528c..96f6bb56c9db95 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -137,6 +137,15 @@ end end + context 'when public-public_unlisted but silenced' do + let(:silenced_at) { Time.now.utc } + let(:status_searchability) { :public_unlisted } + + it 'returns private' do + expect(subject.compute_searchability).to eq 'private' + end + end + context 'when public-private' do let(:status_searchability) { :private } @@ -215,6 +224,24 @@ expect(subject.compute_searchability).to eq 'public' end end + + context 'when public-public_unlisted of local account' do + let(:account_searchability) { :public } + let(:account_domain) { nil } + let(:status_searchability) { :public_unlisted } + + it 'returns public' do + expect(subject.compute_searchability).to eq 'public' + end + + it 'returns public_unlisted for local' do + expect(subject.compute_searchability_local).to eq 'public_unlisted' + end + + it 'returns private for activitypub' do + expect(subject.compute_searchability_activitypub).to eq 'private' + end + end end describe '#quote' do diff --git a/spec/models/tag_feed_spec.rb b/spec/models/tag_feed_spec.rb index 270797ccd8cccb..5206e7cedec436 100644 --- a/spec/models/tag_feed_spec.rb +++ b/spec/models/tag_feed_spec.rb @@ -91,18 +91,36 @@ expect(results).to include status_tagged_with_cats end + it 'unlisted/public_unlisted_searchability post returns' do + status_tagged_with_cats.update(visibility: :unlisted, searchability: :public_unlisted) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to include status_tagged_with_cats + end + it 'unlisted/public_searchability post returns with account' do status_tagged_with_cats.update(visibility: :unlisted, searchability: :public) results = described_class.new(tag_cats, account).get(20) expect(results).to include status_tagged_with_cats end + it 'unlisted/public_unlisted_searchability post returns with account' do + status_tagged_with_cats.update(visibility: :unlisted, searchability: :public_unlisted) + results = described_class.new(tag_cats, account).get(20) + expect(results).to include status_tagged_with_cats + end + it 'private post not returns' do status_tagged_with_cats.update(visibility: :private, searchability: :public) results = described_class.new(tag_cats, nil).get(20) expect(results).to_not include status_tagged_with_cats end + it 'private, public_unlisted post not returns' do + status_tagged_with_cats.update(visibility: :private, searchability: :public_unlisted) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to_not include status_tagged_with_cats + end + it 'private post not returns with account' do status_tagged_with_cats.update(visibility: :private, searchability: :public) results = described_class.new(tag_cats, account).get(20) diff --git a/spec/search/services/statuses_search_service_spec.rb b/spec/search/services/statuses_search_service_spec.rb index c0d4116ae0378d..51245f73541d98 100644 --- a/spec/search/services/statuses_search_service_spec.rb +++ b/spec/search/services/statuses_search_service_spec.rb @@ -63,6 +63,45 @@ end end + context 'when public_unlisted searchability' do + let(:searchability) { :public_unlisted } + let(:account) { other } + + context 'with other account' do + it 'search status' do + expect(subject.count).to eq 1 + expect(subject).to include status.id + end + end + + context 'with follower' do + let(:account) { following } + + it 'search status' do + expect(subject.count).to eq 1 + expect(subject).to include status.id + end + end + + context 'with reacted user' do + let(:account) { reacted } + + it 'search status' do + expect(subject.count).to eq 1 + expect(subject).to include status.id + end + end + + context 'with self' do + let(:account) { alice } + + it 'search status' do + expect(subject.count).to eq 1 + expect(subject).to include status.id + end + end + end + context 'when private searchability' do let(:searchability) { :private } let(:account) { other } diff --git a/spec/serializers/activitypub/note_serializer_spec.rb b/spec/serializers/activitypub/note_serializer_spec.rb index 910b5010ecf39e..a9760c799c2876 100644 --- a/spec/serializers/activitypub/note_serializer_spec.rb +++ b/spec/serializers/activitypub/note_serializer_spec.rb @@ -5,9 +5,11 @@ describe ActivityPub::NoteSerializer do subject { JSON.parse(@serialization.to_json) } + let(:visibility) { :public } + let(:searchability) { :public } let!(:account) { Fabricate(:account) } let!(:other) { Fabricate(:account) } - let!(:parent) { Fabricate(:status, account: account, visibility: :public) } + let!(:parent) { Fabricate(:status, account: account, visibility: visibility, searchability: searchability) } let!(:reply_by_account_first) { Fabricate(:status, account: account, thread: parent, visibility: :public) } let!(:reply_by_account_next) { Fabricate(:status, account: account, thread: parent, visibility: :public) } let!(:reply_by_other_first) { Fabricate(:status, account: other, thread: parent, visibility: :public) } @@ -46,6 +48,30 @@ expect(subject['replies']['first']['items']).to_not include(reply_by_account_visibility_direct.uri) end + it 'send as public visibility' do + expect(subject['to']).to include 'https://www.w3.org/ns/activitystreams#Public' + end + + context 'when public_unlisted visibility' do + let(:visibility) { :public_unlisted } + + it 'send as unlisted visibility' do + expect(subject['to']).to_not include 'https://www.w3.org/ns/activitystreams#Public' + end + end + + it 'send as public searchability' do + expect(subject['searchableBy']).to include 'https://www.w3.org/ns/activitystreams#Public' + end + + context 'when public_unlisted searchability' do + let(:searchability) { :public_unlisted } + + it 'send as private searchability' do + expect(subject['searchableBy']).to_not include 'https://www.w3.org/ns/activitystreams#Public' + end + end + context 'when has quote but no_convert setting' do let(:referred) { Fabricate(:status) } diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb index 65db23214a4607..34169fcb714c79 100644 --- a/spec/services/fan_out_on_write_service_spec.rb +++ b/spec/services/fan_out_on_write_service_spec.rb @@ -363,6 +363,15 @@ def antenna_with_options(owner, **options) expect(redis).to_not have_received(:publish).with('timeline:public', anything) end + context 'with searchability public_unlisted' do + let(:searchability) { 'public_unlisted' } + + it 'is not broadcast to the hashtag stream' do + expect(redis).to have_received(:publish).with('timeline:hashtag:hoge', anything) + expect(redis).to have_received(:publish).with('timeline:hashtag:hoge:local', anything) + end + end + context 'with searchability private' do let(:searchability) { 'private' } diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index 5e2a54c264a30f..529ab09969bdd9 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -127,6 +127,13 @@ expect(status.searchability).to eq 'private' end + it 'creates a status with limited searchability for silenced users with public_unlisted searchability' do + status = subject.call(Fabricate(:account, silenced: true), text: 'test', searchability: :public_unlisted, visibility: :public) + + expect(status).to be_persisted + expect(status.searchability).to eq 'private' + end + it 'creates a status with the given searchability=public / visibility=unlisted' do status = create_status_with_options(searchability: :public, visibility: :unlisted) @@ -134,6 +141,13 @@ expect(status.searchability).to eq 'public' end + it 'creates a status with the given searchability=public_unlisted / visibility=unlisted' do + status = create_status_with_options(searchability: :public_unlisted, visibility: :unlisted) + + expect(status).to be_persisted + expect(status.searchability).to eq 'public_unlisted' + end + it 'creates a status with the given searchability=public / visibility=private' do status = create_status_with_options(searchability: :public, visibility: :private) @@ -141,6 +155,13 @@ expect(status.searchability).to eq 'private' end + it 'creates a status with the given searchability=public_unlisted / visibility=private' do + status = create_status_with_options(searchability: :public_unlisted, visibility: :private) + + expect(status).to be_persisted + expect(status.searchability).to eq 'private' + end + it 'creates a status for the given application' do application = Fabricate(:application)