From dc5526f4ae8c9d3a6f132b2bc72914b95e5286cc Mon Sep 17 00:00:00 2001 From: Fire Demon Date: Sat, 18 Jul 2020 23:59:04 -0500 Subject: [Privacy, Federation, UI] Add options to allow Fediverse users to decide whether to include replies and unlisted posts on their profiles --- app/controllers/accounts_controller.rb | 4 ++-- app/controllers/activitypub/outboxes_controller.rb | 6 +++++- app/controllers/api/v1/accounts/credentials_controller.rb | 4 +++- app/controllers/api/v1/accounts/statuses_controller.rb | 4 ++-- app/controllers/settings/profiles_controller.rb | 4 +++- app/javascript/flavours/glitch/components/status.js | 1 + .../glitch/features/account_timeline/components/header.js | 3 ++- app/lib/activitypub/adapter.rb | 2 ++ app/models/account.rb | 2 ++ app/models/status.rb | 6 +++--- app/serializers/activitypub/actor_serializer.rb | 4 ++-- app/serializers/rest/account_serializer.rb | 2 +- app/services/activitypub/process_account_service.rb | 2 ++ app/views/accounts/show.html.haml | 2 ++ app/views/settings/profiles/show.html.haml | 14 +++++++++++++- 15 files changed, 45 insertions(+), 15 deletions(-) (limited to 'app') diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 21209cf12..81b8f8985 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -69,12 +69,12 @@ class AccountsController < ApplicationController default_statuses.tap do |statuses| statuses.merge!(hashtag_scope) if tag_requested? statuses.merge!(only_media_scope) if media_requested? - statuses.merge!(no_replies_scope) unless (current_account&.id == @account.id) && replies_requested? + statuses.merge!(no_replies_scope) unless (current_account&.id == @account.id || @account.show_replies?) && replies_requested? end end def default_statuses - visibility_scopes = user_signed_in? ? [:public, :unlisted] : :public + visibility_scopes = user_signed_in? || @account.show_unlisted? ? [:public, :unlisted] : :public @account.statuses.not_local_only.where(visibility: visibility_scopes) end diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index e25a4bc07..4d4f5e364 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -49,7 +49,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController def set_statuses return unless page_requested? - @statuses = @account.statuses.permitted_for(@account, signed_request_account) + @statuses = @account.statuses.permitted_for(@account, signed_request_account, user_signed_in: known_visitor?) @statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id)) @statuses = cache_collection(@statuses, Status) end @@ -61,4 +61,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController def page_params { page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact end + + def known_visitor? + user_signed_in? || (signed_request_account.present? && signed_request_account.following?(@account)) + end end diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index dbafc3cc2..3c8187a99 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -21,7 +21,9 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController private def account_params - params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, :require_dereference, fields_attributes: [:name, :value]) + params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, + :require_dereference, :show_replies, :show_unlisted, + fields_attributes: [:name, :value]) end def user_settings_params diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 8a7a3a04d..4735fea8c 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -29,7 +29,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses statuses.merge!(only_media_scope) if truthy_param?(:only_media) - statuses.merge!(no_replies_scope) if (current_account&.id != @account.id) || truthy_param?(:exclude_replies) + statuses.merge!(no_replies_scope) if (current_account&.id != @account.id && !@account.show_replies?) || truthy_param?(:exclude_replies) statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs) statuses.merge!(hashtag_scope) if params[:tagged].present? @@ -37,7 +37,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def permitted_account_statuses - @account.statuses.permitted_for(@account, current_account) + @account.statuses.permitted_for(@account, current_account, user_signed_in: user_signed_in?) end def only_media_scope diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 33d93a233..d6e3c9863 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -23,7 +23,9 @@ class Settings::ProfilesController < Settings::BaseController private def account_params - params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, :require_dereference, fields_attributes: [:name, :value]) + params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, + :require_dereference, :show_replies, :show_unlisted, + fields_attributes: [:name, :value]) end def set_account diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 021c75c76..3a6029b96 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -674,6 +674,7 @@ class Status extends ImmutablePureComponent { const selectorAttribs = { 'data-status-by': `@${status.getIn(['account', 'acct'])}`, 'data-nest-level': status.get('nest_level'), + 'data-nest-deep': status.get('nest_level') >= 15, }; if (prepend && account) { diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js index d7edd43ab..527352497 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js @@ -125,7 +125,8 @@ export default class Header extends ImmutablePureComponent { {!hideTabs && (
- { account.get('id') === me && () } + { (account.get('id') === me || account.get('show_replies')) && + () }
)} diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb index 6ecce7fe9..ef46ae4ae 100644 --- a/app/lib/activitypub/adapter.rb +++ b/app/lib/activitypub/adapter.rb @@ -10,6 +10,8 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base direct_message: { 'litepub': 'http://litepub.social/ns#', 'directMessage': 'litepub:directMessage' }, edited: { 'mp' => 'http://the.monsterpit.net/ns#', 'edited' => 'mp:edited' }, require_dereference: { 'mp' => 'http://the.monsterpit.net/ns#', 'requireDereference' => 'mp:requireDereference' }, + show_replies: { 'mp' => 'http://the.monsterpit.net/ns#', 'showReplies' => 'mp:showReplies' }, + show_unlisted: { 'mp' => 'http://the.monsterpit.net/ns#', 'showUnlisted' => 'mp:showUnlisted' }, manually_approves_followers: { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' }, sensitive: { 'sensitive' => 'as:sensitive' }, hashtag: { 'Hashtag' => 'as:Hashtag' }, diff --git a/app/models/account.rb b/app/models/account.rb index 48e6e8532..8b384f212 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -51,6 +51,8 @@ # header_storage_schema_version :integer # devices_url :string # require_dereference :boolean default(FALSE), not null +# show_replies :boolean default(TRUE), not null +# show_unlisted :boolean default(TRUE), not null # class Account < ApplicationRecord diff --git a/app/models/status.rb b/app/models/status.rb index bee7d1e67..54023c24c 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -390,8 +390,8 @@ class Status < ApplicationRecord end end - def permitted_for(target_account, account) - visibility = [:public, :unlisted] + def permitted_for(target_account, account, user_signed_in: false) + visibility = user_signed_in || target_account.show_unlisted? ? [:public, :unlisted] : :public if account.nil? where(visibility: visibility).not_local_only @@ -402,7 +402,7 @@ class Status < ApplicationRecord else # followers can see followers-only stuff, but also things they are mentioned in. # non-followers can see everything that isn't private/direct, but can see stuff they are mentioned in. - visibility.push(:private) if account.following?(target_account) + visibility.push(:private) if account.following?(target_account) && (user_signed_in || target_account.show_unlisted?) scope = left_outer_joins(:reblog) diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index f3ed70490..5a63d15ea 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -24,8 +24,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer attribute :moved_to, if: :moved? attribute :also_known_as, if: :also_known_as? - context_extensions :require_dereference - attribute :require_dereference + context_extensions :require_dereference, :show_replies + attributes :require_dereference, :show_replies, :show_unlisted class EndpointsSerializer < ActivityPub::Serializer include RoutingHelper diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index bc941d3e7..e425c34a0 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -7,7 +7,7 @@ class REST::AccountSerializer < ActiveModel::Serializer :note, :url, :avatar, :avatar_static, :header, :header_static, :followers_count, :following_count, :statuses_count, :last_status_at - attribute :require_dereference + attributes :require_dereference, :show_replies, :show_unlisted has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested? has_many :emojis, serializer: REST::CustomEmojiSerializer diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 56c70cfa0..2a5980c79 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -87,6 +87,8 @@ class ActivityPub::ProcessAccountService < BaseService @account.actor_type = actor_type @account.discoverable = @json['discoverable'] || false @account.require_dereference = @json['requireDereference'] || false + @account.show_replies = @json['showReplies'] || true + @account.show_unlisted = @json['showUnlisted'] || true end def set_fetchable_attributes! diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index ef84f7304..b1f237618 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -26,6 +26,8 @@ .account__section-headline = active_link_to t('accounts.posts_tab_heading'), short_account_url(@account) + - if @account.show_replies? + = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account) = active_link_to t('accounts.media'), short_account_media_url(@account) - if user_signed_in? && @account.blocking?(current_account) diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index d2f6dc9ba..828b3ee4c 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -28,13 +28,25 @@ .fields-group = f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot') + + %h4= t 'settings.profiles.privacy' + + %p.hint= t 'settings.profiles.privacy_html' - if Setting.profile_directory .fields-group - = f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable'), recommended: true + = f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable') + + .fields-group + = f.input :show_replies, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.show_replies') + .fields-group + = f.input :show_unlisted, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.show_unlisted') + %h4= t 'settings.profiles.advanced_privacy' + %p.hint= t 'settings.profiles.advanced_privacy_html' + .fields-group = f.input :require_dereference, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.require_dereference_html') -- cgit