Skip to content

Commit

Permalink
Fix: #162 編集/他のサーバーからの編集で、フォローしていない相手からのメンションに関するNGワードに対応 (#163)
Browse files Browse the repository at this point in the history
* Fix: #162 フォローしていないアカウントへのメンションのNGワードが編集で有効にならない問題

* Fix: 他のサーバーから来た編集についても適用
  • Loading branch information
kmycode authored Oct 22, 2023
1 parent 5497e2a commit 4f37ede
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 9 deletions.
15 changes: 15 additions & 0 deletions app/services/activitypub/process_status_update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
include Redisable
include Lockable

class AbortError < ::StandardError; end

def call(status, activity_json, object_json, request_id: nil)
raise ArgumentError, 'Status has unsaved changes' if status.changed?

Expand All @@ -30,6 +32,9 @@ def call(status, activity_json, object_json, request_id: nil)
handle_implicit_update!
end

@status
rescue AbortError
@status.reload
@status
end

Expand All @@ -46,6 +51,7 @@ def handle_explicit_update!
update_poll!
update_immediate_attributes!
update_metadata!
validate_status_mentions!
create_edits!
end

Expand Down Expand Up @@ -160,6 +166,15 @@ def valid_status?
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@raw_tags.size)
end

def validate_status_mentions!
raise AbortError if mention_to_stranger? && Admin::NgWord.stranger_mention_reject?("#{@status.spoiler_text}\n#{@status.text}")
end

def mention_to_stranger?
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @status.account.id && !mentioned_account.following?(@status.account) } ||
(@status.thread.present? && @status.thread.account.id != @status.account.id && !@status.thread.account.following?(@status.account))
end

def update_immediate_attributes!
@status.text = @status_parser.text || ''
@status.spoiler_text = @status_parser.spoiler_text || ''
Expand Down
2 changes: 1 addition & 1 deletion app/services/post_status_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def validate_status_mentions!
end

def mention_to_stranger?
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @account && !mentioned_account.following?(@account) } ||
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @account.id && !mentioned_account.following?(@account) } ||
(@in_reply_to && @in_reply_to.account.id != @account.id && !@in_reply_to.account.following?(@account))
end

Expand Down
15 changes: 13 additions & 2 deletions app/services/update_status_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ def call(status, account_id, options = {})
update_poll! if @options.key?(:poll)
update_immediate_attributes!
create_edit! unless @options[:no_history]

reset_preview_card!
process_mentions_service.call(@status)
validate_status_mentions!
end

queue_poll_notifications!
reset_preview_card!
update_metadata!
update_references!
broadcast_updates!
Expand Down Expand Up @@ -81,6 +84,15 @@ def validate_status!
raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text])
end

def validate_status_mentions!
raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if mention_to_stranger? && Setting.stranger_mention_from_local_ng && Admin::NgWord.stranger_mention_reject?("#{@options[:spoiler_text]}\n#{@options[:text]}")
end

def mention_to_stranger?
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @status.account.id && !mentioned_account.following?(@status.account) } ||
(@status.thread.present? && @status.thread.account.id != @status.account.id && !@status.thread.account.following?(@status.account))
end

def validate_media!
return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)

Expand Down Expand Up @@ -167,7 +179,6 @@ def update_references!

def update_metadata!
ProcessHashtagsService.new.call(@status)
process_mentions_service.call(@status)

@status.update(limited_scope: :circle) if process_mentions_service.mentions?
end
Expand Down
173 changes: 167 additions & 6 deletions spec/services/activitypub/process_status_update_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@ def poll_option_json(name, votes)
RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
subject { described_class.new }

let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com')) }
let(:thread) { nil }
let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com'), thread: thread) }
let(:json_tags) do
[
{ type: 'Hashtag', name: 'hoge' },
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
]
end
let(:content) { 'Hello universe' }
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Note',
summary: 'Show more',
content: 'Hello universe',
content: content,
updated: '2021-09-08T22:39:25Z',
tag: [
{ type: 'Hashtag', name: 'hoge' },
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
],
tag: json_tags,
}
end
let(:json) { Oj.load(Oj.dump(payload)) }
Expand Down Expand Up @@ -462,5 +467,161 @@ def poll_option_json(name, votes)
subject.call(status, json, json)
expect(status.reload.edited_at.to_s).to eq '2021-09-08 22:39:25 UTC'
end

describe 'ng word is set' do
let(:json_tags) { [] }

context 'when hit ng words' do
let(:content) { 'ng word test' }

it 'update status' do
Form::AdminSettings.new(ng_words: 'test').save

subject.call(status, json, json)
expect(status.reload.text).to_not eq content
end
end

context 'when not hit ng words' do
let(:content) { 'ng word aiueo' }

it 'update status' do
Form::AdminSettings.new(ng_words: 'test').save

subject.call(status, json, json)
expect(status.reload.text).to eq content
end
end

context 'when hit ng words for mention' do
let(:json_tags) do
[
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
]
end
let(:content) { 'ng word test' }

it 'update status' do
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save

subject.call(status, json, json)
expect(status.reload.text).to_not eq content
expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id
end
end

context 'when hit ng words for mention but local posts are not checked' do
let(:json_tags) do
[
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
]
end
let(:content) { 'ng word test' }

it 'update status' do
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save

subject.call(status, json, json)
expect(status.reload.text).to_not eq content
expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id
end
end

context 'when hit ng words for mention to follower' do
let(:json_tags) do
[
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
]
end
let(:content) { 'ng word test' }

before do
alice.follow!(status.account)
end

it 'update status' do
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save

subject.call(status, json, json)
expect(status.reload.text).to eq content
expect(status.mentioned_accounts.pluck(:id)).to include alice.id
end
end

context 'when hit ng words for reply' do
let(:json_tags) do
[
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
]
end
let(:content) { 'ng word test' }
let(:thread) { Fabricate(:status, account: alice) }

it 'update status' do
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save

subject.call(status, json, json)
expect(status.reload.text).to_not eq content
expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id
end
end

context 'when hit ng words for reply to follower' do
let(:json_tags) do
[
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
]
end
let(:content) { 'ng word test' }
let(:thread) { Fabricate(:status, account: alice) }

before do
alice.follow!(status.account)
end

it 'update status' do
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save

subject.call(status, json, json)
expect(status.reload.text).to eq content
expect(status.mentioned_accounts.pluck(:id)).to include alice.id
end
end

context 'when using hashtag under limit' do
let(:json_tags) do
[
{ type: 'Hashtag', name: 'a' },
{ type: 'Hashtag', name: 'b' },
]
end
let(:content) { 'ohagi is good' }

it 'update status' do
Form::AdminSettings.new(post_hash_tags_max: 2).save

subject.call(status, json, json)
expect(status.reload.text).to eq content
end
end

context 'when using hashtag over limit' do
let(:json_tags) do
[
{ type: 'Hashtag', name: 'a' },
{ type: 'Hashtag', name: 'b' },
{ type: 'Hashtag', name: 'c' },
]
end
let(:content) { 'ohagi is good' }

it 'update status' do
Form::AdminSettings.new(post_hash_tags_max: 2).save

subject.call(status, json, json)
expect(status.reload.text).to_not eq content
end
end
end
end
end
20 changes: 20 additions & 0 deletions spec/services/post_status_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,26 @@
expect(status).to be_persisted
expect(status.text).to eq text
end

it 'using hashtag under limit' do
account = Fabricate(:account)
text = '#a #b'
Form::AdminSettings.new(post_hash_tags_max: 2).save

status = subject.call(account, text: text)

expect(status).to be_persisted
expect(status.tags.count).to eq 2
expect(status.text).to eq text
end

it 'using hashtag over limit' do
account = Fabricate(:account)
text = '#a #b #c'
Form::AdminSettings.new(post_hash_tags_max: 2).save

expect { subject.call(account, text: text) }.to raise_error Mastodon::ValidationError
end
end

def create_status_with_options(**options)
Expand Down
Loading

0 comments on commit 4f37ede

Please sign in to comment.