Add: 検索許可「ローカルとフォロワー」 (#60)

This commit is contained in:
KMY(雪あすか) 2023-10-05 16:37:27 +09:00 committed by GitHub
parent deb8642e95
commit 235bef36d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 210 additions and 25 deletions

View file

@ -15,6 +15,8 @@ import { IconButton } from '../../../components/icon_button';
const messages = defineMessages({ const messages = defineMessages({
public_short: { id: 'searchability.public.short', defaultMessage: 'Public' }, public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
public_long: { id: 'searchability.public.long', defaultMessage: 'Anyone can find' }, 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_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' },
private_long: { id: 'searchability.unlisted.long', defaultMessage: 'Your followers can find' }, private_long: { id: 'searchability.unlisted.long', defaultMessage: 'Your followers can find' },
direct_short: { id: 'searchability.private.short', defaultMessage: 'Reactionners' }, direct_short: { id: 'searchability.private.short', defaultMessage: 'Reactionners' },
@ -223,6 +225,7 @@ class SearchabilityDropdown extends PureComponent {
this.options = [ this.options = [
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) }, { 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: '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: '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) }, { icon: 'at', value: 'limited', text: formatMessage(messages.limited_short), meta: formatMessage(messages.limited_long) },

View file

@ -38,6 +38,7 @@ const messages = defineMessages({
personal_short: { id: 'privacy.personal.short', defaultMessage: 'Yourself only' }, personal_short: { id: 'privacy.personal.short', defaultMessage: 'Yourself only' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' }, 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_private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' },
searchability_direct_short: { id: 'searchability.private.short', defaultMessage: 'Reactionners' }, searchability_direct_short: { id: 'searchability.private.short', defaultMessage: 'Reactionners' },
searchability_limited_short: { id: 'searchability.direct.short', defaultMessage: 'Self only' }, searchability_limited_short: { id: 'searchability.direct.short', defaultMessage: 'Self only' },
@ -270,6 +271,7 @@ class DetailedStatus extends ImmutablePureComponent {
const searchabilityIconInfo = { const searchabilityIconInfo = {
'public': { icon: 'globe', text: intl.formatMessage(messages.searchability_public_short) }, '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) }, 'private': { icon: 'unlock', text: intl.formatMessage(messages.searchability_private_short) },
'direct': { icon: 'lock', text: intl.formatMessage(messages.searchability_direct_short) }, 'direct': { icon: 'lock', text: intl.formatMessage(messages.searchability_direct_short) },
'limited': { icon: 'at', text: intl.formatMessage(messages.searchability_limited_short) }, 'limited': { icon: 'at', text: intl.formatMessage(messages.searchability_limited_short) },

View file

@ -629,6 +629,8 @@
"searchability.private.short": "Reactionners", "searchability.private.short": "Reactionners",
"searchability.public.long": "Anyone can find", "searchability.public.long": "Anyone can find",
"searchability.public.short": "Everyone", "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.long": "Your followers and reactionners can find",
"searchability.unlisted.short": "Followers and reactionners", "searchability.unlisted.short": "Followers and reactionners",
"search_popout.domain": "domain", "search_popout.domain": "domain",

View file

@ -714,6 +714,8 @@
"searchability.private.short": "反応者のみ", "searchability.private.short": "反応者のみ",
"searchability.public.long": "この投稿は誰でも検索できます", "searchability.public.long": "この投稿は誰でも検索できます",
"searchability.public.short": "誰でも", "searchability.public.short": "誰でも",
"searchability.public_unlisted.long": "ローカルユーザーとフォロワーが検索できます",
"searchability.public_unlisted.short": "ローカルとフォロワー",
"searchability.unlisted.long": "この投稿はあなたのフォロワーと反応者だけが検索できます", "searchability.unlisted.long": "この投稿はあなたのフォロワーと反応者だけが検索できます",
"searchability.unlisted.short": "フォロワーと反応者", "searchability.unlisted.short": "フォロワーと反応者",
"search_popout.domain": "ドメイン", "search_popout.domain": "ドメイン",

View file

@ -26,7 +26,7 @@ class AccountStatusesFilter
scope.merge!(no_reblogs_scope) if exclude_reblogs? scope.merge!(no_reblogs_scope) if exclude_reblogs?
scope.merge!(hashtag_scope) if tagged? 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_visibilities = [:public, :public_unlisted, :login, :unlisted, :private, :direct, :limited]
available_searchabilities = [:public] if domain_block&.reject_send_not_public_searchability available_searchabilities = [:public] if domain_block&.reject_send_not_public_searchability

View file

@ -23,7 +23,7 @@ class Importer::StatusesIndexImporter < Importer::BaseImporter
to_index.map do |object| to_index.map do |object|
# This is unlikely to happen, but the post may have been # This is unlikely to happen, but the post may have been
# un-interacted with since it was queued for indexing # 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 deleted += 1
{ delete: { _id: object.id } } { delete: { _id: object.id } }
else else

View file

@ -89,10 +89,10 @@ class SearchQueryTransformer < Parslet::Transform
public_index, public_index,
searchability_limited, searchability_limited,
] ]
definition_should << searchability_public if %i(public).include?(@searchability) definition_should << searchability_public if %i(public public_unlisted).include?(@searchability)
definition_should << searchability_private if %i(public unlisted private).include?(@searchability) definition_should << searchability_private if %i(public public_unlisted unlisted private).include?(@searchability)
definition_should << searchable_by_me if %i(public unlisted private direct).include?(@searchability) definition_should << searchable_by_me if %i(public public_unlisted unlisted private direct).include?(@searchability)
definition_should << self_posts if %i(public unlisted private direct).exclude?(@searchability) definition_should << self_posts if %i(public public_unlisted unlisted private direct).exclude?(@searchability)
{ {
bool: { bool: {
@ -199,8 +199,8 @@ class SearchQueryTransformer < Parslet::Transform
def following_account_ids def following_account_ids
return @following_account_ids if defined?(@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 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 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_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) @following_account_ids = following_accounts.pluck(:target_account_id)
end end

View file

@ -5,7 +5,7 @@ module StatusSearchConcern
included do included do
scope :indexable, -> { without_reblogs.where(visibility: [:public, :login], searchability: nil).joins(:account).where(account: { indexable: true }) } 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 end
def searchable_by def searchable_by

View file

@ -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_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 :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) }
scope :with_public_visibility, -> { where(visibility: [:public, :public_unlisted, :login]) } 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 :with_global_timeline_visibility, -> { where(visibility: [:public, :login]) }
scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) } 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 }) } scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) }
@ -442,19 +442,26 @@ class Status < ApplicationRecord
def compute_searchability def compute_searchability
local = account.local? 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 'direct' if unsupported_searchability?
return searchability if local && !searchability.nil? return check_searchability if local && !check_searchability.nil?
return 'direct' if local || [:public, :private, :direct, :limited].exclude?(account.searchability.to_sym) return 'direct' if local || %i(public private direct limited).exclude?(account.searchability.to_sym)
account_searchability = Status.searchabilities[account.searchability] 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' Status.searchabilities.invert.fetch([account_searchability, status_searchability].max) || 'direct'
end end
def compute_searchability_activitypub 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 compute_searchability
end end
@ -477,6 +484,10 @@ class Status < ApplicationRecord
end end
def selectable_searchabilities def selectable_searchabilities
searchabilities.keys - %w(unsupported)
end
def selectable_searchabilities_for_search
searchabilities.keys - %w(public_unlisted unsupported) searchabilities.keys - %w(public_unlisted unsupported)
end end
@ -620,7 +631,7 @@ class Status < ApplicationRecord
elsif visibility == 'limited' elsif visibility == 'limited'
:limited :limited
elsif visibility == 'private' elsif visibility == 'private'
searchability == 'public' ? :private : searchability searchability == 'public' || searchability == 'public_unlisted' ? :private : searchability
elsif visibility == 'direct' elsif visibility == 'direct'
searchability == 'limited' ? :limited : :direct searchability == 'limited' ? :limited : :direct
else else

View file

@ -106,7 +106,8 @@ class Trends::Statuses < Trends::Base
private private
def eligible?(status) 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.account.discoverable? && !status.account.silenced? && status.spoiler_text.blank? && (!status.sensitive? || status.media_attachments.none?) &&
!status.reply? && valid_locale?(status.language) !status.reply? && valid_locale?(status.language)
end end

View file

@ -93,7 +93,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
end end
def searchability def searchability
object.compute_searchability object.compute_searchability_local
end end
def sensitive def sensitive

View file

@ -64,8 +64,10 @@ class DeliveryAntennaService
next if antenna.exclude_accounts&.include?(@status.account_id) next if antenna.exclude_accounts&.include?(@status.account_id)
next if antenna.exclude_domains&.include?(domain) next if antenna.exclude_domains&.include?(domain)
next if antenna.exclude_tags&.any? { |tag_id| tag_ids.include?(tag_id) } 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) collection.push(antenna)
end end
@ -121,7 +123,7 @@ class DeliveryAntennaService
when :public, :public_unlisted, :login, :limited when :public, :public_unlisted, :login, :limited
false false
when :unlisted when :unlisted
!@status.public_searchability? @status.compute_searchability != 'public'
else else
true true
end end

View file

@ -218,6 +218,6 @@ class FanOutOnWriteService < BaseService
end end
def broadcastable_unlisted2? 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
end end

View file

@ -81,7 +81,7 @@ class PostStatusService < BaseService
@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 @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 @limited_scope = @options[:visibility]&.to_sym if @visibility == :limited
@searchability = searchability @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 @markdown = @options[:markdown] || false
@scheduled_at = @options[:scheduled_at]&.to_datetime @scheduled_at = @options[:scheduled_at]&.to_datetime
@scheduled_at = nil if scheduled_in_the_past? @scheduled_at = nil if scheduled_in_the_past?
@ -129,6 +129,8 @@ class PostStatusService < BaseService
case @options[:searchability]&.to_sym case @options[:searchability]&.to_sym
when :public when :public
case @visibility&.to_sym when :public, :public_unlisted, :login, :unlisted then :public when :private then :private else :direct end 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 when :private
case @visibility&.to_sym when :public, :public_unlisted, :login, :unlisted, :private then :private else :direct end case @visibility&.to_sym when :public, :public_unlisted, :login, :unlisted, :private then :private else :direct end
when :direct when :direct

View file

@ -41,7 +41,7 @@
.fields-row .fields-row
.fields-group.fields-row__column.fields-row__column-12 .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 .fields-group
= ff.input :use_public_index, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_use_public_index') = ff.input :use_public_index, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_use_public_index')

View file

@ -1836,6 +1836,8 @@ en:
public: Public public: Public
public_long: Anyone can find public_long: Anyone can find
public_search_long: You can search all posts permitted to search 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_more: Show more
show_newer: Show newer show_newer: Show newer
show_older: Show older show_older: Show older

View file

@ -1814,6 +1814,8 @@ ja:
public: 誰でも public: 誰でも
public_long: この投稿は誰でも検索できます public_long: この投稿は誰でも検索できます
public_search_long: 検索が許可された全ての投稿が検索できます public_search_long: 検索が許可された全ての投稿が検索できます
public_unlisted: ローカルとフォロワー
public_unlisted_long: ローカル・フォロワー・反応者のみが検索できます
show_more: もっと見る show_more: もっと見る
show_newer: 新しいものを表示 show_newer: 新しいものを表示
show_older: 古いものを表示 show_older: 古いものを表示

View file

@ -67,6 +67,22 @@ describe StatusReachFinder do
end end
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 context 'when misskey with public searchability' do
let(:sender_software) { 'misskey' } let(:sender_software) { 'misskey' }

View file

@ -137,6 +137,15 @@ RSpec.describe Status do
end end
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 context 'when public-private' do
let(:status_searchability) { :private } let(:status_searchability) { :private }
@ -215,6 +224,24 @@ RSpec.describe Status do
expect(subject.compute_searchability).to eq 'public' expect(subject.compute_searchability).to eq 'public'
end end
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 end
describe '#quote' do describe '#quote' do

View file

@ -91,18 +91,36 @@ describe TagFeed, type: :service do
expect(results).to include status_tagged_with_cats expect(results).to include status_tagged_with_cats
end 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 it 'unlisted/public_searchability post returns with account' do
status_tagged_with_cats.update(visibility: :unlisted, searchability: :public) status_tagged_with_cats.update(visibility: :unlisted, searchability: :public)
results = described_class.new(tag_cats, account).get(20) results = described_class.new(tag_cats, account).get(20)
expect(results).to include status_tagged_with_cats expect(results).to include status_tagged_with_cats
end 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 it 'private post not returns' do
status_tagged_with_cats.update(visibility: :private, searchability: :public) status_tagged_with_cats.update(visibility: :private, searchability: :public)
results = described_class.new(tag_cats, nil).get(20) results = described_class.new(tag_cats, nil).get(20)
expect(results).to_not include status_tagged_with_cats expect(results).to_not include status_tagged_with_cats
end 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 it 'private post not returns with account' do
status_tagged_with_cats.update(visibility: :private, searchability: :public) status_tagged_with_cats.update(visibility: :private, searchability: :public)
results = described_class.new(tag_cats, account).get(20) results = described_class.new(tag_cats, account).get(20)

View file

@ -63,6 +63,45 @@ describe StatusesSearchService do
end end
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 context 'when private searchability' do
let(:searchability) { :private } let(:searchability) { :private }
let(:account) { other } let(:account) { other }

View file

@ -5,9 +5,11 @@ require 'rails_helper'
describe ActivityPub::NoteSerializer do describe ActivityPub::NoteSerializer do
subject { JSON.parse(@serialization.to_json) } subject { JSON.parse(@serialization.to_json) }
let(:visibility) { :public }
let(:searchability) { :public }
let!(:account) { Fabricate(:account) } let!(:account) { Fabricate(:account) }
let!(:other) { 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_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_account_next) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
let!(:reply_by_other_first) { Fabricate(:status, account: other, thread: parent, visibility: :public) } let!(:reply_by_other_first) { Fabricate(:status, account: other, thread: parent, visibility: :public) }
@ -46,6 +48,30 @@ describe ActivityPub::NoteSerializer do
expect(subject['replies']['first']['items']).to_not include(reply_by_account_visibility_direct.uri) expect(subject['replies']['first']['items']).to_not include(reply_by_account_visibility_direct.uri)
end 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 context 'when has quote but no_convert setting' do
let(:referred) { Fabricate(:status) } let(:referred) { Fabricate(:status) }

View file

@ -363,6 +363,15 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(redis).to_not have_received(:publish).with('timeline:public', anything) expect(redis).to_not have_received(:publish).with('timeline:public', anything)
end 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 context 'with searchability private' do
let(:searchability) { 'private' } let(:searchability) { 'private' }

View file

@ -127,6 +127,13 @@ RSpec.describe PostStatusService, type: :service do
expect(status.searchability).to eq 'private' expect(status.searchability).to eq 'private'
end 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 it 'creates a status with the given searchability=public / visibility=unlisted' do
status = create_status_with_options(searchability: :public, visibility: :unlisted) status = create_status_with_options(searchability: :public, visibility: :unlisted)
@ -134,6 +141,13 @@ RSpec.describe PostStatusService, type: :service do
expect(status.searchability).to eq 'public' expect(status.searchability).to eq 'public'
end 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 it 'creates a status with the given searchability=public / visibility=private' do
status = create_status_with_options(searchability: :public, visibility: :private) status = create_status_with_options(searchability: :public, visibility: :private)
@ -141,6 +155,13 @@ RSpec.describe PostStatusService, type: :service do
expect(status.searchability).to eq 'private' expect(status.searchability).to eq 'private'
end 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 it 'creates a status for the given application' do
application = Fabricate(:application) application = Fabricate(:application)