Fix processing of mentions for post edits with an existing corresponding silent mention (#33227)

This commit is contained in:
Claire 2024-12-11 13:59:29 +01:00 committed by GitHub
parent da279df8ae
commit bcb3b627b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 33 additions and 21 deletions

View file

@ -190,40 +190,30 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
end end
def update_mentions! def update_mentions!
previous_mentions = @status.active_mentions.includes(:account).to_a
current_mentions = []
unresolved_mentions = [] unresolved_mentions = []
@raw_mentions.each do |href| currently_mentioned_account_ids = @raw_mentions.filter_map do |href|
next if href.blank? next if href.blank?
account = ActivityPub::TagManager.instance.uri_to_resource(href, Account) account = ActivityPub::TagManager.instance.uri_to_resource(href, Account)
account ||= ActivityPub::FetchRemoteAccountService.new.call(href, request_id: @request_id) account ||= ActivityPub::FetchRemoteAccountService.new.call(href, request_id: @request_id)
next if account.nil? account&.id
mention = previous_mentions.find { |x| x.account_id == account.id }
mention ||= account.mentions.new(status: @status)
current_mentions << mention
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
# Since previous mentions are about already-known accounts, # Since previous mentions are about already-known accounts,
# they don't try to resolve again and won't fall into this case. # they don't try to resolve again and won't fall into this case.
# In other words, this failure case is only for new mentions and won't # In other words, this failure case is only for new mentions and won't
# affect `removed_mentions` so they can safely be retried asynchronously # affect `removed_mentions` so they can safely be retried asynchronously
unresolved_mentions << href unresolved_mentions << href
nil
end end
current_mentions.each do |mention| @status.mentions.upsert_all(currently_mentioned_account_ids.map { |id| { account_id: id, silent: false } }, unique_by: %w(status_id account_id))
mention.save if mention.new_record?
end
# If previous mentions are no longer contained in the text, convert them # If previous mentions are no longer contained in the text, convert them
# to silent mentions, since withdrawing access from someone who already # to silent mentions, since withdrawing access from someone who already
# received a notification might be more confusing # received a notification might be more confusing
removed_mentions = previous_mentions - current_mentions @status.mentions.where.not(account_id: currently_mentioned_account_ids).update_all(silent: true)
Mention.where(id: removed_mentions.map(&:id)).update_all(silent: true) unless removed_mentions.empty?
# Queue unresolved mentions for later # Queue unresolved mentions for later
unresolved_mentions.uniq.each do |uri| unresolved_mentions.uniq.each do |uri|

View file

@ -13,7 +13,7 @@ class ProcessMentionsService < BaseService
return unless @status.local? return unless @status.local?
@previous_mentions = @status.active_mentions.includes(:account).to_a @previous_mentions = @status.mentions.includes(:account).to_a
@current_mentions = [] @current_mentions = []
Status.transaction do Status.transaction do
@ -57,6 +57,8 @@ class ProcessMentionsService < BaseService
mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id } mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id }
mention ||= @status.mentions.new(account: mentioned_account) mention ||= @status.mentions.new(account: mentioned_account)
mention.silent = false
@current_mentions << mention @current_mentions << mention
"@#{mentioned_account.acct}" "@#{mentioned_account.acct}"
@ -78,7 +80,7 @@ class ProcessMentionsService < BaseService
end end
@current_mentions.each do |mention| @current_mentions.each do |mention|
mention.save if mention.new_record? && @save_records mention.save if (mention.new_record? || mention.silent_changed?) && @save_records
end end
# If previous mentions are no longer contained in the text, convert them # If previous mentions are no longer contained in the text, convert them
@ -86,7 +88,7 @@ class ProcessMentionsService < BaseService
# received a notification might be more confusing # received a notification might be more confusing
removed_mentions = @previous_mentions - @current_mentions removed_mentions = @previous_mentions - @current_mentions
Mention.where(id: removed_mentions.map(&:id)).update_all(silent: true) unless removed_mentions.empty? Mention.where(id: removed_mentions.map(&:id), silent: false).update_all(silent: true) unless removed_mentions.empty?
end end
def mention_undeliverable?(mentioned_account) def mention_undeliverable?(mentioned_account)

View file

@ -16,7 +16,7 @@ class MentionResolveWorker
return if account.nil? return if account.nil?
status.mentions.create!(account: account, silent: false) status.mentions.upsert({ account_id: account.id, silent: false }, unique_by: %w(status_id account_id))
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
# Do nothing # Do nothing
rescue Mastodon::UnexpectedResponseError => e rescue Mastodon::UnexpectedResponseError => e

View file

@ -32,7 +32,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
let(:media_attachments) { [] } let(:media_attachments) { [] }
before do before do
mentions.each { |a| Fabricate(:mention, status: status, account: a) } mentions.each { |(account, silent)| Fabricate(:mention, status: status, account: account, silent: silent) }
tags.each { |t| status.tags << t } tags.each { |t| status.tags << t }
media_attachments.each { |m| status.media_attachments << m } media_attachments.each { |m| status.media_attachments << m }
stub_request(:get, bogus_mention).to_raise(HTTP::ConnectionError) stub_request(:get, bogus_mention).to_raise(HTTP::ConnectionError)
@ -280,7 +280,19 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end end
context 'when originally with mentions' do context 'when originally with mentions' do
let(:mentions) { [alice, bob] } let(:mentions) { [[alice, false], [bob, false]] }
before do
subject.call(status, json, json)
end
it 'updates mentions' do
expect(status.active_mentions.reload.map(&:account_id)).to eq [alice.id]
end
end
context 'when originally with silent mentions' do
let(:mentions) { [[alice, true], [bob, true]] }
before do before do
subject.call(status, json, json) subject.call(status, json, json)

View file

@ -150,6 +150,14 @@ RSpec.describe UpdateStatusService do
.to eq [bob.id] .to eq [bob.id]
expect(status.mentions.pluck(:account_id)) expect(status.mentions.pluck(:account_id))
.to contain_exactly(alice.id, bob.id) .to contain_exactly(alice.id, bob.id)
# Going back when a mention was switched to silence should still be possible
subject.call(status, status.account_id, text: 'Hello @alice')
expect(status.active_mentions.pluck(:account_id))
.to eq [alice.id]
expect(status.mentions.pluck(:account_id))
.to contain_exactly(alice.id, bob.id)
end end
end end