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