diff --git a/app/models/admin/ng_rule.rb b/app/models/admin/ng_rule.rb index 8746457b808725..54ca39b4c9d0cd 100644 --- a/app/models/admin/ng_rule.rb +++ b/app/models/admin/ng_rule.rb @@ -176,7 +176,7 @@ def value_over_threshold?(_reason, value, expected) end def detect_keyword?(text, arr) - Admin::NgRule.detect_keyword?(text, arr) + Admin::NgRule.detect_keyword?(text, arr, local: @account.local?) end class << self @@ -184,14 +184,15 @@ def string_to_array(text) text.delete("\r").split("\n") end - def detect_keyword(text, arr) + def detect_keyword(text, arr, local: true) arr = string_to_array(arr) if arr.is_a?(String) + text = PlainTextFormatter.new(text, false).to_s unless local arr.detect { |word| include?(text, word) ? word : nil } end - def detect_keyword?(text, arr) - detect_keyword(text, arr).present? + def detect_keyword?(text, arr, local: true) + detect_keyword(text, arr, local: local).present? end def include?(text, word) diff --git a/app/models/admin/ng_word.rb b/app/models/admin/ng_word.rb index 2a961d290349be..871909e316b0bf 100644 --- a/app/models/admin/ng_word.rb +++ b/app/models/admin/ng_word.rb @@ -3,12 +3,14 @@ class Admin::NgWord class << self def reject?(text, **options) + text = PlainTextFormatter.new(text, false).to_s if options[:uri].present? hit_word = ng_words.detect { |word| include?(text, word) ? word : nil } record!(:ng_words, text, hit_word, options) if hit_word.present? hit_word.present? end def stranger_mention_reject?(text, **options) + text = PlainTextFormatter.new(text, false).to_s if options[:uri].present? hit_word = ng_words_for_stranger_mention.detect { |word| include?(text, word) ? word : nil } record!(:ng_words_for_stranger_mention, text, hit_word, options) if hit_word.present? hit_word.present? diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index f174ae1575172e..d3f220cf57fc72 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -164,15 +164,16 @@ def update_poll!(allow_significant_changes: true) def valid_status? valid = !Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}", uri: @status.uri, target_type: :status) valid = !Admin::NgWord.hashtag_reject?(@raw_tags.size) if valid + valid = false if valid && Admin::NgWord.mention_reject?(@raw_mentions.size, uri: @status.uri, target_type: :status, text: "#{@status_parser.spoiler_text}\n#{@status_parser.text}") + valid = false if valid && (mention_to_local_stranger? || reference_to_local_stranger?) && Admin::NgWord.stranger_mention_reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}", uri: @status.uri, target_type: :status) + valid = false if valid && (mention_to_local_stranger? || reference_to_local_stranger?) && Admin::NgWord.stranger_mention_reject_with_count?(@raw_mentions.size, uri: @status.uri, target_type: :status, text: "#{@status_parser.spoiler_text}\n#{@status_parser.text}") + valid = false if valid && (mention_to_local_stranger? || reference_to_local_stranger?) && reject_reply_exclude_followers? valid end def validate_status_mentions! raise AbortError unless valid_status_for_ng_rule? - raise AbortError if (mention_to_local_stranger? || reference_to_local_stranger?) && Admin::NgWord.stranger_mention_reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}", uri: @status.uri, target_type: :status) - raise AbortError if Admin::NgWord.mention_reject?(@raw_mentions.size, uri: @status.uri, target_type: :status, text: "#{@status_parser.spoiler_text}\n#{@status_parser.text}") - raise AbortError if (mention_to_local_stranger? || reference_to_local_stranger?) && Admin::NgWord.stranger_mention_reject_with_count?(@raw_mentions.size, uri: @status.uri, target_type: :status, text: "#{@status_parser.spoiler_text}\n#{@status_parser.text}") end def valid_status_for_ng_rule? @@ -196,8 +197,11 @@ def valid_status_for_ng_rule? end def mention_to_local_stranger? - @status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @status.account.id && mentioned_account.local? && !mentioned_account.following?(@status.account) } || - (@status.thread.present? && @status.thread.account.id != @status.account.id && @status.thread.account.local? && !@status.thread.account.following?(@status.account)) + return @mention_to_local_stranger if defined?(@mention_to_local_stranger) + + @mention_to_local_stranger = @raw_mentions.filter_map { |uri| ActivityPub::TagManager.instance.local_uri?(uri) && ActivityPub::TagManager.instance.uri_to_resource(uri, Account) }.any? { |mentioned_account| !mentioned_account.following?(@status.account) } + @mention_to_local_stranger ||= @status.thread.present? && @status.thread.account_id != @status.account_id && @status.thread.account.local? && !@status.thread.account.following?(@status.account) + @mention_to_local_stranger end def reference_to_local_stranger? @@ -365,6 +369,12 @@ def ignore_hashtags? @ignore_hashtags ||= DomainBlock.reject_hashtag?(@account.domain) end + def reject_reply_exclude_followers? + return @reject_reply_exclude_followers if defined?(@reject_reply_exclude_followers) + + @reject_reply_exclude_followers ||= DomainBlock.reject_reply_exclude_followers?(@account.domain) + end + def unsupported_media_type?(mime_type) mime_type.present? && !MediaAttachment.supported_mime_types.include?(mime_type) end diff --git a/config/locales/en.yml b/config/locales/en.yml index cdfdddda6242c6..c1d4d1f2eea429 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -457,7 +457,7 @@ en: reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions reject_new_follow: Reject follows reject_new_follow_hint: Reject follows in the future - reject_reply_exclude_followers: Reject replies exclude followers + reject_reply_exclude_followers: Reject mentions/references exclude followers reject_reply_hint: Reject replies in the future reject_reply_exclude_followers_hint: Reject replies exclude followers in the future reject_reports: Reject reports @@ -573,7 +573,7 @@ en: reject_media: Reject media reject_new_follow: Reject follows reject_straight_follow: Reject straight follow - reject_reply_exclude_followers: Reject reply exclude followers + reject_reply_exclude_followers: Reject reply/reference exclude followers reject_reports: Reject reports silence: Limit suspend: Suspend diff --git a/config/locales/ja.yml b/config/locales/ja.yml index af2fb2890fbd0f..e495d13e4dd1ba 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -452,7 +452,7 @@ ja: reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です reject_new_follow: 新規フォローを拒否 reject_new_follow_hint: 今後の新規フォローを拒否します。停止とは無関係です - reject_reply_exclude_followers: フォロー相手以外からのリプライを拒否 + reject_reply_exclude_followers: フォロー相手以外からのメンションと参照を拒否 reject_reply_hint: 今後のリプライを拒否します。停止とは無関係です reject_reply_exclude_followers_hint: 今後のリプライを拒否します。停止とは無関係です reject_reports: 通報を拒否 @@ -565,7 +565,7 @@ ja: reject_hashtag: ハッシュタグを拒否 reject_media: メディアを拒否する reject_new_follow: 新規フォローを拒否 - reject_reply_exclude_followers: フォロー相手以外からのリプライを拒否 + reject_reply_exclude_followers: フォロー相手以外からのメンション・参照を拒否 reject_reports: 通報を拒否 reject_send_sensitive: センシティブ投稿配送なし reject_straight_follow: フォローを制限 diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 6065b001256798..7c91f55203eb11 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -1772,7 +1772,7 @@ def activity_for_object(json) end context 'when not contains ng words' do - let(:content) { 'ohagi, world!' } + let(:content) { 'ohagi, world! OH GOOD' } it 'creates status' do expect(sender.statuses.first).to_not be_nil @@ -2022,7 +2022,7 @@ def activity_for_object(json) context 'when ng rule is set' do let(:custom_before) { true } - let(:content) { 'Lorem ipsum' } + let(:content) { 'Lorem ipsum GOOD LINK' } let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index 9a02b500d2d62a..13b93a8acfaff6 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -325,6 +325,31 @@ def poll_option_json(name, votes) end end + context 'when reject mentions to stranger by domain-block' do + let(:json_tags) do + [ + { type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) }, + ] + end + + before do + Fabricate(:domain_block, domain: 'example.com', reject_reply_exclude_followers: true, severity: :noop) + end + + it 'updates mentions' do + subject.call(status, json, json) + + expect(status.mentions.reload.map(&:account_id)).to eq [] + end + + it 'updates mentions when follower' do + alice.follow!(status.account) + subject.call(status, json, json) + + expect(status.mentions.reload.map(&:account_id)).to eq [alice.id] + end + end + context 'when originally without mentions' do before do subject.call(status, json, json) @@ -508,7 +533,7 @@ def poll_option_json(name, votes) end end - context 'when hit ng words for mention' do + context 'when hit ng words for mention to local stranger' do let(:json_tags) do [ { type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) }, @@ -523,6 +548,15 @@ def poll_option_json(name, votes) expect(status.reload.text).to_not eq content expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id end + + it 'update status when following' do + Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + alice.follow!(status.account) + + 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 mention but local posts are not checked' do