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({
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) },

View file

@ -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) },

View file

@ -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",

View file

@ -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": "ドメイン",

View file

@ -26,7 +26,7 @@ class AccountStatusesFilter
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

View file

@ -23,7 +23,7 @@ class Importer::StatusesIndexImporter < Importer::BaseImporter
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

View file

@ -89,10 +89,10 @@ class SearchQueryTransformer < Parslet::Transform
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 @@ class SearchQueryTransformer < Parslet::Transform
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

View file

@ -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

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_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 @@ class Status < ApplicationRecord
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 @@ class Status < ApplicationRecord
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 @@ class Status < ApplicationRecord
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

View file

@ -106,7 +106,8 @@ class Trends::Statuses < Trends::Base
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

View file

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

View file

@ -64,8 +64,10 @@ class DeliveryAntennaService
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 @@ class DeliveryAntennaService
when :public, :public_unlisted, :login, :limited
false
when :unlisted
!@status.public_searchability?
@status.compute_searchability != 'public'
else
true
end

View file

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

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
@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 @@ class PostStatusService < BaseService
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

View file

@ -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')