diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index b73c2cddd1..e5d431a528 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -146,6 +146,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity valid = !Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}", uri: @params[:uri], target_type: :status, public: @status_parser.distributable_visibility?) valid = !Admin::NgWord.hashtag_reject?(@tags.size) if valid valid = !Admin::NgWord.mention_reject?(@mentions.count { |m| !m.silent }) if valid + valid = !Admin::NgWord.stranger_mention_reject_with_count?(@mentions.count { |m| !m.silent }) if valid && (mention_to_local_stranger? || reference_to_local_stranger?) valid = !Admin::NgWord.stranger_mention_reject?("#{@params[:spoiler_text]}\n#{@params[:text]}", uri: @params[:uri], target_type: :status, public: @status_parser.distributable_visibility?) if valid && (mention_to_local_stranger? || reference_to_local_stranger?) valid diff --git a/app/models/admin/ng_word.rb b/app/models/admin/ng_word.rb index c3d8c020a3..89230efc92 100644 --- a/app/models/admin/ng_word.rb +++ b/app/models/admin/ng_word.rb @@ -34,6 +34,14 @@ class Admin::NgWord mention_reject?(text.gsub(Account::MENTION_RE)&.count || 0) end + def stranger_mention_reject_with_count?(mention_count) + post_stranger_mentions_max.positive? && post_stranger_mentions_max < mention_count + end + + def stranger_mention_reject_with_extractor?(text) + stranger_mention_reject_with_count?(text.gsub(Account::MENTION_RE)&.count || 0) + end + private def include?(text, word) @@ -62,6 +70,11 @@ class Admin::NgWord value.is_a?(Integer) && value.positive? ? value : 0 end + def post_stranger_mentions_max + value = Setting.post_stranger_mentions_max + value.is_a?(Integer) && value.positive? ? value : 0 + end + def record!(type, text, keyword, options) return unless options[:uri] && options[:target_type] return if options.key?(:public) && !options.delete(:public) diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 144bb1a2ec..2513023b6e 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -50,6 +50,7 @@ class Form::AdminSettings hide_local_users_for_anonymous post_hash_tags_max post_mentions_max + post_stranger_mentions_max sensitive_words sensitive_words_for_full authorized_fetch @@ -71,6 +72,7 @@ class Form::AdminSettings backups_retention_period post_hash_tags_max post_mentions_max + post_stranger_mentions_max registrations_limit registrations_limit_per_day registrations_start_hour diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 2549c44ed6..cc549b97e4 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -161,11 +161,16 @@ class ActivityPub::ProcessStatusUpdateService < BaseService end def valid_status? - !Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}", uri: @status.uri, target_type: :status) && !Admin::NgWord.hashtag_reject?(@raw_tags.size) && !Admin::NgWord.mention_reject?(@raw_mentions.size) + 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 end def validate_status_mentions! raise AbortError if (mention_to_stranger? || reference_to_stranger?) && Admin::NgWord.stranger_mention_reject?("#{@status.spoiler_text}\n#{@status.text}", uri: @status.uri, target_type: :status) + raise AbortError if Admin::NgWord.mention_reject?(@raw_mentions.size) + raise AbortError if (mention_to_stranger? || reference_to_stranger?) && Admin::NgWord.stranger_mention_reject_with_count?(@raw_mentions.size) end def mention_to_stranger? diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index d0341f65b5..3c4eb9c12f 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -210,10 +210,11 @@ class PostStatusService < BaseService def validate_status! raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if Admin::NgWord.reject?("#{@options[:spoiler_text]}\n#{@options[:text]}") raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@text) - raise Mastodon::ValidationError, I18n.t('statuses.too_many_mentions') if Admin::NgWord.mention_reject_with_extractor?(@text) end def validate_status_mentions! + raise Mastodon::ValidationError, I18n.t('statuses.too_many_mentions') if Admin::NgWord.mention_reject_with_extractor?(@text) + raise Mastodon::ValidationError, I18n.t('statuses.too_many_mentions') if (mention_to_stranger? || reference_to_stranger?) && Admin::NgWord.stranger_mention_reject_with_extractor?(@text) raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if (mention_to_stranger? || reference_to_stranger?) && Setting.stranger_mention_from_local_ng && Admin::NgWord.stranger_mention_reject?("#{@options[:spoiler_text]}\n#{@options[:text]}") end diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index 59caf4c6bb..1393e7eefa 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -84,6 +84,7 @@ class UpdateStatusService < BaseService raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if Admin::NgWord.reject?("#{@options[:spoiler_text]}\n#{@options[:text]}") raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text] || '') raise Mastodon::ValidationError, I18n.t('statuses.too_many_mentions') if Admin::NgWord.mention_reject_with_extractor?(@options[:text] || '') + raise Mastodon::ValidationError, I18n.t('statuses.too_many_mentions') if (mention_to_stranger? || reference_to_stranger?) && Admin::NgWord.stranger_mention_reject_with_extractor?(@options[:text] || '') end def validate_status_mentions! diff --git a/app/views/admin/ng_words/show.html.haml b/app/views/admin/ng_words/show.html.haml index 5af34cb80a..d188a179c0 100644 --- a/app/views/admin/ng_words/show.html.haml +++ b/app/views/admin/ng_words/show.html.haml @@ -23,6 +23,9 @@ .fields-group = f.input :post_hash_tags_max, wrapper: :with_label, as: :integer, label: t('admin.ng_words.post_hash_tags_max') + .fields-group + = f.input :post_stranger_mentions_max, wrapper: :with_label, as: :integer, label: t('admin.ng_words.post_stranger_mentions_max') + .fields-group = f.input :post_mentions_max, wrapper: :with_label, as: :integer, label: t('admin.ng_words.post_mentions_max') diff --git a/config/locales/en.yml b/config/locales/en.yml index da88271252..ddb7b03869 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -649,6 +649,7 @@ en: keywords_hint: The first character of the line is "?". to use regular expressions post_hash_tags_max: Hash tags max for posts post_mentions_max: Mentions max for posts + post_stranger_mentions_max: 投稿に設定可能なメンションの最大数 (If the mentions include at least one person who is not a follower of yours) stranger_mention_from_local_ng: フォローしていないアカウントへのメンションのNGワードを、ローカルユーザーによる投稿にも適用する stranger_mention_from_local_ng_hint: サーバーの登録が承認制でない場合、あなたのサーバーにもスパムが入り込む可能性があります test_error: Testing is returned any errors diff --git a/config/locales/ja.yml b/config/locales/ja.yml index d5eccff6d5..2b87a13798 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -642,6 +642,7 @@ ja: keywords_hint: 行を「?」で始めると、正規表現が使えます post_hash_tags_max: 投稿に設定可能なハッシュタグの最大数 post_mentions_max: 投稿に設定可能なメンションの最大数 + post_stranger_mentions_max: 投稿に設定可能なメンションの最大数 (メンション先にフォロワー以外を1人でも含む場合) stranger_mention_from_local_ng: フォローしていないアカウントへのメンションのNGワードを、ローカルユーザーによる投稿にも適用する stranger_mention_from_local_ng_hint: サーバーの登録が承認制でない場合、あなたのサーバーにもスパムが入り込む可能性があります test_error: NGワードのテストに失敗しました。正規表現のミスが含まれているかもしれません diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 53662d0bf4..d9a90d8b0b 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -2068,7 +2068,79 @@ RSpec.describe ActivityPub::Activity::Create do end context 'when mentions limit is set' do - let(:post_mentions_max) { 2 } + let(:post_mentions_max) { 3 } + let(:post_stranger_mentions_max) { 0 } + let(:custom_before) { true } + let(:mention_recipient_alice) { Fabricate(:account) } + let(:mention_recipient_bob) { Fabricate(:account) } + let(:mention_recipient_ohagi) { Fabricate(:account) } + let(:mention_recipient_ohagi_follow) { true } + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + tag: [ + { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(mention_recipient_alice), + }, + { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(mention_recipient_bob), + }, + { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(mention_recipient_ohagi), + }, + ], + } + end + + before do + Form::AdminSettings.new(post_mentions_max: post_mentions_max, post_stranger_mentions_max: post_stranger_mentions_max).save + + mention_recipient_alice.follow!(sender) + mention_recipient_bob.follow!(sender) + mention_recipient_ohagi.follow!(sender) if mention_recipient_ohagi_follow + + subject.perform + end + + context 'when limit is enough' do + it 'creates status' do + expect(sender.statuses.first).to_not be_nil + end + end + + context 'when limit is over' do + let(:post_mentions_max) { 1 } + + it 'creates status' do + expect(sender.statuses.first).to be_nil + end + end + + context 'when limit for stranger is over but normal limit is not reach' do + let(:post_stranger_mentions_max) { 1 } + + it 'creates status' do + expect(sender.statuses.first).to_not be_nil + end + end + + context 'when limit for stranger is over and following partically' do + let(:post_stranger_mentions_max) { 1 } + let(:mention_recipient_ohagi_follow) { false } + + it 'creates status' do + expect(sender.statuses.first).to be_nil + end + end + end + + context 'when mentions limit for stranger is set' do + let(:post_stranger_mentions_max) { 2 } let(:custom_before) { true } let(:object_json) do { @@ -2089,7 +2161,7 @@ RSpec.describe ActivityPub::Activity::Create do end before do - Form::AdminSettings.new(post_mentions_max: post_mentions_max).save + Form::AdminSettings.new(post_stranger_mentions_max: post_stranger_mentions_max).save subject.perform end @@ -2100,7 +2172,7 @@ RSpec.describe ActivityPub::Activity::Create do end context 'when limit is over' do - let(:post_mentions_max) { 1 } + let(:post_stranger_mentions_max) { 1 } it 'creates status' do expect(sender.statuses.first).to be_nil diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index e7fa1a8ddf..d39d52ab9a 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -742,9 +742,11 @@ RSpec.describe PostStatusService, type: :service do end it 'using mentions under limit' do - Fabricate(:account, username: 'a') - Fabricate(:account, username: 'b') + a = Fabricate(:account, username: 'a') + b = Fabricate(:account, username: 'b') account = Fabricate(:account) + a.follow!(account) + b.follow!(account) text = '@a @b' Form::AdminSettings.new(post_mentions_max: 2).save @@ -755,11 +757,64 @@ RSpec.describe PostStatusService, type: :service do end it 'using mentions over limit' do + a = Fabricate(:account, username: 'a') + b = Fabricate(:account, username: 'b') + account = Fabricate(:account) + a.follow!(account) + b.follow!(account) + text = '@a @b' + Form::AdminSettings.new(post_mentions_max: 1).save + + expect { subject.call(account, text: text) }.to raise_error Mastodon::ValidationError + end + + it 'using mentions for stranger under limit' do Fabricate(:account, username: 'a') Fabricate(:account, username: 'b') account = Fabricate(:account) text = '@a @b' - Form::AdminSettings.new(post_mentions_max: 1).save + Form::AdminSettings.new(post_stranger_mentions_max: 2).save + + status = subject.call(account, text: text) + + expect(status).to be_persisted + expect(status.text).to eq text + end + + it 'using mentions for stranger over limit' do + Fabricate(:account, username: 'a') + Fabricate(:account, username: 'b') + account = Fabricate(:account) + text = '@a @b' + Form::AdminSettings.new(post_stranger_mentions_max: 1).save + + expect { subject.call(account, text: text) }.to raise_error Mastodon::ValidationError + end + + it 'using mentions for stranger over limit but normal setting is under limit' do + a = Fabricate(:account, username: 'a') + b = Fabricate(:account, username: 'b') + account = Fabricate(:account) + a.follow!(account) + b.follow!(account) + text = '@a @b' + Form::AdminSettings.new(post_mentions_max: 2, post_stranger_mentions_max: 1).save + + status = subject.call(account, text: text) + + expect(status).to be_persisted + expect(status.text).to eq text + end + + it 'using mentions for stranger over limit but normal setting is under limit when following one only' do + a = Fabricate(:account, username: 'a') + b = Fabricate(:account, username: 'b') + Fabricate(:account, username: 'c') + account = Fabricate(:account) + a.follow!(account) + b.follow!(account) + text = '@a @b @c' + Form::AdminSettings.new(post_mentions_max: 3, post_stranger_mentions_max: 2).save expect { subject.call(account, text: text) }.to raise_error Mastodon::ValidationError end