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 +++++++++++++- config/locales/en-MP.yml | 3 +++ config/locales/simple_form.en-MP.yml | 5 ++++- db/migrate/20200719024610_add_show_replies_to_accounts.rb | 7 +++++++ db/migrate/20200719033609_add_show_unlisted_to_accounts.rb | 7 +++++++ db/schema.rb | 4 +++- 20 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 db/migrate/20200719024610_add_show_replies_to_accounts.rb create mode 100644 db/migrate/20200719033609_add_show_unlisted_to_accounts.rb 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') diff --git a/config/locales/en-MP.yml b/config/locales/en-MP.yml index 534552c73..eb5b82067 100644 --- a/config/locales/en-MP.yml +++ b/config/locales/en-MP.yml @@ -111,7 +111,10 @@ en-MP: settings: monsterfork: Monsterfork profiles: + privacy: Privacy + privacy_html: These options allow you to adjust how much information is visible on your public profile on Monsterpit. Be aware that other servers you send your roars to have their own profile systems and may not honor these options. You will need to use followers-only or direct privacy for roars you do not want displayed in other servers' public profiles. advanced_privacy: Advanced privacy + advanced_privacy_html: These options can increase your privacy at the expense of compatability with other servers. They can potentially cause roars to not be delivered to some of your followers. Only enable them if you're fully aware of their side effects. user_mailer: warning: explanation: diff --git a/config/locales/simple_form.en-MP.yml b/config/locales/simple_form.en-MP.yml index 55097c519..376ea7f9d 100644 --- a/config/locales/simple_form.en-MP.yml +++ b/config/locales/simple_form.en-MP.yml @@ -26,7 +26,8 @@ en-MP: setting_default_language: The language of your roars can be detected automatically, but it's not always accurate setting_show_application: The application you use to toot will be displayed in the detailed view of your roars setting_skin: Reskins the selected UI flavour - invite_request: + show_replies: Disable if you'd prefer your replies not be a part of your public profile + show_unlisted: Disable if you'd prefer to only show unlisted roars on your profile page to visitors who are logged-in or are your followers. text: This helps us determine if registrations are made in sincerity and prevent spam. It is only visible to admins. user: chosen_languages: When checked, only roars in selected languages will be displayed in public timelines @@ -47,6 +48,8 @@ en-MP: setting_favourite_modal: Show confirmation dialog before admiring (applies to Glitch flavour only) setting_show_application: Disclose application used to send roars setting_use_pending_items: Relax mode + show_replies: Show replies on profile + show_unlisted: Show unlisted roars to anonymous visitors invite_request: text: "Introduce yourself and let the admins know what brings you to Monsterpit." notification_emails: diff --git a/db/migrate/20200719024610_add_show_replies_to_accounts.rb b/db/migrate/20200719024610_add_show_replies_to_accounts.rb new file mode 100644 index 000000000..ac6c5906b --- /dev/null +++ b/db/migrate/20200719024610_add_show_replies_to_accounts.rb @@ -0,0 +1,7 @@ +class AddShowRepliesToAccounts < ActiveRecord::Migration[5.2] + def change + safety_assured do + add_column :accounts, :show_replies, :boolean, null: false, default: true + end + end +end diff --git a/db/migrate/20200719033609_add_show_unlisted_to_accounts.rb b/db/migrate/20200719033609_add_show_unlisted_to_accounts.rb new file mode 100644 index 000000000..a9bb16720 --- /dev/null +++ b/db/migrate/20200719033609_add_show_unlisted_to_accounts.rb @@ -0,0 +1,7 @@ +class AddShowUnlistedToAccounts < ActiveRecord::Migration[5.2] + def change + safety_assured do + add_column :accounts, :show_unlisted, :boolean, null: false, default: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index beec07767..9618dc7d4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_07_18_011317) do +ActiveRecord::Schema.define(version: 2020_07_19_033609) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -183,6 +183,8 @@ ActiveRecord::Schema.define(version: 2020_07_18_011317) do t.integer "header_storage_schema_version" t.string "devices_url" t.boolean "require_dereference", default: false, null: false + t.boolean "show_replies", default: true, null: false + t.boolean "show_unlisted", default: true, null: false t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id" -- cgit