From ccafeaf32fac2279d1f6ad891309a7c52ccb7edf Mon Sep 17 00:00:00 2001 From: noellabo Date: Sun, 11 Sep 2022 23:03:39 +0900 Subject: [PATCH] Add the ability to make status reference notifications followee-only --- .../settings/preferences_controller.rb | 2 +- app/javascript/mastodon/reducers/settings.js | 4 +- app/policies/status_policy.rb | 6 +- app/services/notify_service.rb | 9 +++ .../preferences/notifications/show.html.haml | 1 + config/locales/simple_form.en.yml | 1 + config/locales/simple_form.ja.yml | 1 + config/settings.yml | 1 + .../status_reference_fabricator.rb | 4 ++ spec/policies/status_policy_spec.rb | 31 ++++++++++ spec/services/notify_service_spec.rb | 60 +++++++++++++++++++ 11 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 spec/fabricators/status_reference_fabricator.rb diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 3ec51656a..3b69e31ce 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -96,7 +96,7 @@ class Settings::PreferencesController < Settings::BaseController :setting_default_search_searchability, :setting_show_reload_button, notification_emails: %i(follow follow_request reblog favourite emoji_reaction status_reference mention digest report pending_account trending_tag), - interactions: %i(must_be_follower must_be_following must_be_following_dm must_be_dm_to_send_email) + interactions: %i(must_be_follower must_be_following must_be_following_dm must_be_dm_to_send_email must_be_following_reference) ) end diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 343afb015..8007f9454 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -75,7 +75,7 @@ const initialState = ImmutableMap({ poll: true, status: true, emoji_reaction: true, - status_reference: false, + status_reference: true, }), sounds: ImmutableMap({ @@ -87,7 +87,7 @@ const initialState = ImmutableMap({ poll: true, status: true, emoji_reaction: true, - status_reference: false, + status_reference: true, }), }), diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 720d187e4..344a6721b 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -56,7 +56,7 @@ class StatusPolicy < ApplicationPolicy def subscribe? return false unless show? - !unlisted? || owned? || following_author? || mention_exists? + public? || owned? || following_author? || mention_exists? end private @@ -69,6 +69,10 @@ class StatusPolicy < ApplicationPolicy author.id == current_account&.id end + def public? + record.public_visibility? + end + def unlisted? record.unlisted_visibility? end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index fc9b361a0..8741f24b3 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -67,6 +67,10 @@ class NotifyService < BaseService @recipient.user.settings.interactions['must_be_following'] && !following_sender? end + def optional_non_following_and_reference? + status_reference? && @recipient.user.settings.interactions['must_be_following_reference'] && !following_sender? + end + def optional_non_direct_message? message? && @recipient.user.settings.interactions['must_be_dm_to_send_email'] && !@notification.target_status.direct_visibility? end @@ -75,6 +79,10 @@ class NotifyService < BaseService @notification.type == :mention end + def status_reference? + @notification.type == :status_reference + end + def direct_message? message? && @notification.target_status.direct_visibility? end @@ -162,6 +170,7 @@ class NotifyService < BaseService blocked ||= hellbanned? # Hellban blocked ||= optional_non_follower? # Options blocked ||= optional_non_following? # Options + blocked ||= optional_non_following_and_reference? # Options blocked ||= optional_non_following_and_direct? # Options blocked ||= conversation_muted? blocked ||= send("blocked_#{@notification.type}?") # Type-dependent filters diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml index 5b5ced555..071fe03b1 100644 --- a/app/views/settings/preferences/notifications/show.html.haml +++ b/app/views/settings/preferences/notifications/show.html.haml @@ -38,3 +38,4 @@ = ff.input :must_be_following, as: :boolean, wrapper: :with_label = ff.input :must_be_following_dm, as: :boolean, wrapper: :with_label = ff.input :must_be_dm_to_send_email, as: :boolean, wrapper: :with_label, fedibird_features: true + = ff.input :must_be_following_reference, as: :boolean, wrapper: :with_label, fedibird_features: true diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index ce7ff4ccb..32ad9c90b 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -307,6 +307,7 @@ en: must_be_follower: Block notifications from non-followers must_be_following: Block notifications from people you don't follow must_be_following_dm: Block direct messages from people you don't follow + must_be_following_reference: Block reference notifications from people you don't follow invite: comment: Comment invite_request: diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index c4f5dc69e..ac5e44f6c 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -311,6 +311,7 @@ ja: must_be_follower: フォロワー以外からの通知をブロック must_be_following: フォローしていないユーザーからの通知をブロック must_be_following_dm: フォローしていないユーザーからのダイレクトメッセージをブロック + must_be_following_reference: フォローしていないユーザーからの参照通知をブロック invite: comment: コメント invite_request: diff --git a/config/settings.yml b/config/settings.yml index 6d9759589..521a214ea 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -82,6 +82,7 @@ defaults: &defaults must_be_follower: false must_be_following: false must_be_following_dm: false + must_be_following_reference: true reserved_usernames: - admin - support diff --git a/spec/fabricators/status_reference_fabricator.rb b/spec/fabricators/status_reference_fabricator.rb new file mode 100644 index 000000000..7d7ba4c16 --- /dev/null +++ b/spec/fabricators/status_reference_fabricator.rb @@ -0,0 +1,4 @@ +Fabricator(:status_reference) do + status_id nil + target_status_id nil +end diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index 1cddf4abd..02bce9e1f 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -25,6 +25,37 @@ RSpec.describe StatusPolicy, type: :model do end end + permissions :subscribe? do + it 'grants access when public and account is viewer' do + viewer = Fabricate(:account) + status.visibility = :public + + expect(subject).to permit(viewer, status) + end + + it 'grants access when direct and viewer is mentioned' do + status.visibility = :unlisted + status.mentions = [Fabricate(:mention, account: alice)] + + expect(subject).to permit(alice, status) + end + + it 'grants access when unlisted and account is following viewer' do + follow = Fabricate(:follow) + status.visibility = :unlisted + status.account = follow.target_account + + expect(subject).to permit(follow.account, status) + end + + it 'denies access when unlisted and account is not mentioned or following viewer' do + viewer = Fabricate(:account) + status.visibility = :unlisted + + expect(subject).to_not permit(viewer, status) + end + end + permissions :show? do it 'grants access when direct and account is viewer' do status.visibility = :direct diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index ecb9ddbad..84071ae27 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -126,6 +126,66 @@ RSpec.describe NotifyService, type: :service do end end + describe 'status references' do + let(:target_status) { Fabricate(:status, account: recipient, visibility: :public) } + let(:activity) { Fabricate(:status_reference, status: status, target_status: target_status) } + let(:type) { :status_reference } + + before do + user.settings.interactions = user.settings.interactions.merge('must_be_following_reference' => enabled) + end + + context 'if must_be_following_reference is true' do + let(:enabled) { true } + + describe 'with public' do + let(:status) { Fabricate(:status, account: sender, visibility: :public) } + + it 'does notify' do + is_expected.to_not change(Notification, :count) + end + end + + describe 'with unlisted' do + let(:status) { Fabricate(:status, account: sender, visibility: :unlisted) } + + it 'does notify when sender is followed' do + recipient.follow!(sender) + is_expected.to change(Notification, :count) + end + + it 'does not notify when sender is not followed' do + is_expected.to_not change(Notification, :count) + end + end + end + + context 'if must_be_following_reference is false' do + let(:enabled) { false } + + describe 'with public' do + let(:status) { Fabricate(:status, account: sender, visibility: :public) } + + it 'does notify' do + is_expected.to change(Notification, :count) + end + end + + describe 'with unlisted' do + let(:status) { Fabricate(:status, account: sender, visibility: :unlisted) } + + it 'does notify when sender is followed' do + recipient.follow!(sender) + is_expected.to change(Notification, :count) + end + + it 'does not notify when sender is not followed' do + is_expected.to_not change(Notification, :count) + end + end + end + end + context do let(:asshole) { Fabricate(:account, username: 'asshole') } let(:reply_to) { Fabricate(:status, account: asshole) }