diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index b306e8e8cce2f51c37e0db80bea5e5ca8e14a373..c12e1c12e11e24ea1a8caa4618cc3ea71f0ece8d 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -33,7 +33,7 @@ class Api::V1::AccountsController < Api::BaseController def follow FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs)) - options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } } + options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } } render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options) end diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index f6d90580b6cce03c54b706c0458d6840fdabfd7a..67ec7665b670c1fc36d64ce5999d75918d6f8245 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -77,16 +77,14 @@ class GettingStarted extends ImmutablePureComponent { }; componentDidMount () { - const { myAccount, fetchFollowRequests, multiColumn } = this.props; + const { fetchFollowRequests, multiColumn } = this.props; if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { this.context.router.history.replace('/timelines/home'); return; } - if (myAccount.get('locked')) { - fetchFollowRequests(); - } + fetchFollowRequests(); } render () { @@ -134,7 +132,7 @@ class GettingStarted extends ImmutablePureComponent { height += 48*3; - if (myAccount.get('locked')) { + if (myAccount.get('locked') || unreadFollowRequests > 0) { navItems.push(<ColumnLink key={i++} icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />); height += 48; } diff --git a/app/lib/activitypub/activity/follow.rb b/app/lib/activitypub/activity/follow.rb index 28f1da19f89072cb1feb82bbae09bd19268b1864..ec92f4255f6b0295acc9bb457f90fcc22ce23dd6 100644 --- a/app/lib/activitypub/activity/follow.rb +++ b/app/lib/activitypub/activity/follow.rb @@ -21,7 +21,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity follow_request = FollowRequest.create!(account: @account, target_account: target_account, uri: @json['id']) - if target_account.locked? + if target_account.locked? || @account.silenced? NotifyService.new.call(target_account, follow_request) else AuthorizeFollowService.new.call(@account, target_account) diff --git a/app/serializers/rest/credential_account_serializer.rb b/app/serializers/rest/credential_account_serializer.rb index fb195eb07d8bf24f684436dfbc752dd75a55c6d4..be0d763dc122991b0fdc957d321cd0d7c2a7a9df 100644 --- a/app/serializers/rest/credential_account_serializer.rb +++ b/app/serializers/rest/credential_account_serializer.rb @@ -12,6 +12,7 @@ class REST::CredentialAccountSerializer < REST::AccountSerializer language: user.setting_default_language, note: object.note, fields: object.fields.map(&:to_h), + follow_requests_count: FollowRequest.where(target_account: object).limit(40).count, } end end diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 101acdaf96365aa85dccef86c6ff4bfa1d8efe0a..1941c2e2d8864a352e2de04239391ea2ee5bdee2 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -30,7 +30,7 @@ class FollowService < BaseService ActivityTracker.increment('activity:interactions') - if target_account.locked? || target_account.activitypub? + if target_account.locked? || source_account.silenced? || target_account.activitypub? request_follow(source_account, target_account, reblogs: reblogs) elsif target_account.local? direct_follow(source_account, target_account, reblogs: reblogs) diff --git a/app/services/update_account_service.rb b/app/services/update_account_service.rb index 01756a73d4bbeafdc8cef1af40849c59b916a575..ebf24be37698be220593bc7ba40dccee741abbce 100644 --- a/app/services/update_account_service.rb +++ b/app/services/update_account_service.rb @@ -20,7 +20,9 @@ class UpdateAccountService < BaseService private def authorize_all_follow_requests(account) - AuthorizeFollowWorker.push_bulk(FollowRequest.where(target_account: account).select(:account_id, :target_account_id)) do |req| + follow_requests = FollowRequest.where(target_account: account) + follow_requests = follow_requests.select { |req| !req.account.silenced? } + AuthorizeFollowWorker.push_bulk(follow_requests) do |req| [req.account_id, req.target_account_id] end end diff --git a/spec/lib/activitypub/activity/follow_spec.rb b/spec/lib/activitypub/activity/follow_spec.rb index 6bbacdbe644d2c2f2085651e7cfbe2b6a760373c..05112cc184f2a65aa2437b2aa7276b251f48f711 100644 --- a/spec/lib/activitypub/activity/follow_spec.rb +++ b/spec/lib/activitypub/activity/follow_spec.rb @@ -31,6 +31,36 @@ RSpec.describe ActivityPub::Activity::Follow do end end + context 'silenced account following an unlocked account' do + before do + sender.touch(:silenced_at) + subject.perform + end + + it 'does not create a follow from sender to recipient' do + expect(sender.following?(recipient)).to be false + end + + it 'creates a follow request' do + expect(sender.requested?(recipient)).to be true + end + end + + context 'unlocked account muting the sender' do + before do + recipient.mute!(sender) + subject.perform + end + + it 'creates a follow from sender to recipient' do + expect(sender.following?(recipient)).to be true + end + + it 'does not create a follow request' do + expect(sender.requested?(recipient)).to be false + end + end + context 'locked account' do before do recipient.update(locked: true) diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb index 86c85293e888694a37967416c17a6a7f50611108..ae863a9f0e98451ce3dc04e683fd6e9162ad67fb 100644 --- a/spec/services/follow_service_spec.rb +++ b/spec/services/follow_service_spec.rb @@ -30,6 +30,33 @@ RSpec.describe FollowService, type: :service do end end + describe 'unlocked account, from silenced account' do + let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account } + + before do + sender.touch(:silenced_at) + subject.call(sender, bob.acct) + end + + it 'creates a follow request with reblogs' do + expect(FollowRequest.find_by(account: sender, target_account: bob, show_reblogs: true)).to_not be_nil + end + end + + describe 'unlocked account, from a muted account' do + let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account } + + before do + bob.mute!(sender) + subject.call(sender, bob.acct) + end + + it 'creates a following relation with reblogs' do + expect(sender.following?(bob)).to be true + expect(sender.muting_reblogs?(bob)).to be false + end + end + describe 'unlocked account' do let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account } diff --git a/spec/services/update_account_service_spec.rb b/spec/services/update_account_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..960b26891f78780f18f10c3cd8de59dc5738827d --- /dev/null +++ b/spec/services/update_account_service_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +RSpec.describe UpdateAccountService, type: :service do + subject { UpdateAccountService.new } + + describe 'switching form locked to unlocked accounts' do + let(:account) { Fabricate(:account, locked: true) } + let(:alice) { Fabricate(:user, email: 'alice@example.com', account: Fabricate(:account, username: 'alice')).account } + let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account } + let(:eve) { Fabricate(:user, email: 'eve@example.com', account: Fabricate(:account, username: 'eve')).account } + + before do + bob.touch(:silenced_at) + account.mute!(eve) + + FollowService.new.call(alice, account) + FollowService.new.call(bob, account) + FollowService.new.call(eve, account) + + subject.call(account, { locked: false }) + end + + it 'auto-accepts pending follow requests' do + expect(alice.following?(account)).to be true + expect(alice.requested?(account)).to be false + end + + it 'does not auto-accept pending follow requests from silenced users' do + expect(bob.following?(account)).to be false + expect(bob.requested?(account)).to be true + end + + it 'auto-accepts pending follow requests from muted users so as to not leak mute' do + expect(eve.following?(account)).to be true + expect(eve.requested?(account)).to be false + end + end +end