diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index e5d431a5284411..a1a62f049f11ac 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -144,9 +144,9 @@ def process_status_params def valid_status? 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.hashtag_reject?(@tags.size, uri: @params[:uri], target_type: :status, public: @status_parser.distributable_visibility?, text: "#{@params[:spoiler_text]}\n#{@params[:text]}") if valid + valid = !Admin::NgWord.mention_reject?(@mentions.count { |m| !m.silent }, uri: @params[:uri], target_type: :status, public: @status_parser.distributable_visibility?, text: "#{@params[:spoiler_text]}\n#{@params[:text]}") if valid + valid = !Admin::NgWord.stranger_mention_reject_with_count?(@mentions.count { |m| !m.silent }, uri: @params[:uri], target_type: :status, public: @status_parser.distributable_visibility?, text: "#{@params[:spoiler_text]}\n#{@params[:text]}") 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 89230efc92a677..2a961d290349be 100644 --- a/app/models/admin/ng_word.rb +++ b/app/models/admin/ng_word.rb @@ -18,24 +18,30 @@ def reject_with_custom_words?(text, custom_ng_words) custom_ng_words.any? { |word| include?(text, word) } end - def hashtag_reject?(hashtag_count) - post_hash_tags_max.positive? && post_hash_tags_max < hashtag_count + def hashtag_reject?(hashtag_count, **options) + hit = post_hash_tags_max.positive? && post_hash_tags_max < hashtag_count + record_count!(:hashtag_count, hashtag_count, options) if hit + hit end def hashtag_reject_with_extractor?(text) hashtag_reject?(Extractor.extract_hashtags(text)&.size || 0) end - def mention_reject?(mention_count) - post_mentions_max.positive? && post_mentions_max < mention_count + def mention_reject?(mention_count, **options) + hit = post_mentions_max.positive? && post_mentions_max < mention_count + record_count!(:mention_count, mention_count, options) if hit + hit end def mention_reject_with_extractor?(text) 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 + def stranger_mention_reject_with_count?(mention_count, **options) + hit = post_stranger_mentions_max.positive? && post_stranger_mentions_max < mention_count + record_count!(:stranger_mention_count, mention_count, options) if hit + hit end def stranger_mention_reject_with_extractor?(text) @@ -87,5 +93,19 @@ def record!(type, text, keyword, options) keyword: keyword, })) end + + def record_count!(type, count, options) + return unless options[:text] && options[:uri] && options[:target_type] + return if options.key?(:public) && !options.delete(:public) + + return if NgwordHistory.where('created_at > ?', 1.day.ago).exists?(uri: options[:uri], reason: type) + + NgwordHistory.create(options.merge({ + reason: type, + text: options[:text], + keyword: '', + count: count, + })) + end end end diff --git a/app/models/ngword_history.rb b/app/models/ngword_history.rb index 992d3c16fb7833..ed821891a9396b 100644 --- a/app/models/ngword_history.rb +++ b/app/models/ngword_history.rb @@ -12,10 +12,11 @@ # keyword :string not null # created_at :datetime not null # updated_at :datetime not null +# count :integer default(0), not null # class NgwordHistory < ApplicationRecord include Paginable enum target_type: { status: 0, account_note: 1, account_name: 2 }, _suffix: :blocked - enum reason: { ng_words: 0, ng_words_for_stranger_mention: 1 }, _prefix: :within + enum reason: { ng_words: 0, ng_words_for_stranger_mention: 1, hashtag_count: 2, mention_count: 3, stranger_mention_count: 4 }, _prefix: :within end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index cc549b97e4900d..97896287ea03e4 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -168,9 +168,9 @@ def valid_status? 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) + raise AbortError if (mention_to_stranger? || reference_to_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_stranger? || reference_to_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 mention_to_stranger? diff --git a/app/views/admin/ngword_histories/_history.html.haml b/app/views/admin/ngword_histories/_history.html.haml index f781924683b880..53666bb60cbb7e 100644 --- a/app/views/admin/ngword_histories/_history.html.haml +++ b/app/views/admin/ngword_histories/_history.html.haml @@ -6,25 +6,31 @@ = html_aware_format(history.text, false) .detailed-status__meta - %span.negative-hint= history.keyword + - if history.within_ng_words? || history.within_ng_words_for_stranger_mention? + %span.negative-hint= history.keyword + - else + %span.negative-hint= history.count.to_s · - if history.within_ng_words? = t('admin.ng_words.keywords') - elsif history.within_ng_words_for_stranger_mention? = t('admin.ng_words.keywords_for_stranger_mention') + - elsif history.within_hashtag_count? + = t('admin.ng_words.post_hash_tags_max') + - elsif history.within_mention_count? + = t('admin.ng_words.post_mentions_max') + - elsif history.within_stranger_mention_count? + = t('admin.ng_words.post_stranger_mentions_max') %br/ %time.formatted{ datetime: history.created_at.iso8601, title: l(history.created_at) }= l(history.created_at) · - if history.account_note_blocked? - = t('admin.ngword_history.target_types.account_note') + = t('admin.ngword_histories.target_types.account_note') - elsif history.account_name_blocked? - = t('admin.ngword_history.target_types.account_name') + = t('admin.ngword_histories.target_types.account_name') - elsif history.status_blocked? - = t('admin.ngword_history.target_types.status') + = t('admin.ngword_histories.target_types.status') · = history.uri - -# if history.application - = history.application.name - · diff --git a/db/migrate/20240217022038_add_count_to_ngword_histories.rb b/db/migrate/20240217022038_add_count_to_ngword_histories.rb new file mode 100644 index 00000000000000..ca3909bb45de05 --- /dev/null +++ b/db/migrate/20240217022038_add_count_to_ngword_histories.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddCountToNgwordHistories < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + def change + add_column :ngword_histories, :count, :integer, null: false, default: 0 + + add_index :ngword_histories, [:uri, :reason, :created_at], unique: false, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index 271b1f24be4128..2a1640a1432ff2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_02_16_042730) do +ActiveRecord::Schema[7.1].define(version: 2024_02_17_022038) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -876,7 +876,9 @@ t.string "keyword", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "count", default: 0, null: false t.index ["uri", "keyword", "created_at"], name: "index_ngword_histories_on_uri_and_keyword_and_created_at" + t.index ["uri", "reason", "created_at"], name: "index_ngword_histories_on_uri_and_reason_and_created_at" end create_table "notifications", force: :cascade do |t| diff --git a/lib/tasks/dangerous.rake b/lib/tasks/dangerous.rake index ac529381e204b2..917388fe0ee3c2 100644 --- a/lib/tasks/dangerous.rake +++ b/lib/tasks/dangerous.rake @@ -84,6 +84,8 @@ namespace :dangerous do 20240121231131 20240212224800 20240212230358 + 20240216042730 + 20240217022038 ) # Removed: account_groups target_tables = %w( @@ -100,6 +102,7 @@ namespace :dangerous do friend_domains instance_infos list_statuses + ngword_histories scheduled_expiration_statuses status_capability_tokens status_references diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index d9a90d8b0b15e3..25798df57052bd 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -2032,6 +2032,7 @@ def activity_for_object(json) id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', + to: 'https://www.w3.org/ns/activitystreams#Public', tag: [ { type: 'Hashtag', @@ -2055,6 +2056,9 @@ def activity_for_object(json) context 'when limit is enough' do it 'creates status' do expect(sender.statuses.first).to_not be_nil + + history = NgwordHistory.find_by(uri: object_json[:id]) + expect(history).to be_nil end end @@ -2063,6 +2067,13 @@ def activity_for_object(json) it 'creates status' do expect(sender.statuses.first).to be_nil + + history = NgwordHistory.find_by(uri: object_json[:id]) + expect(history).to_not be_nil + expect(history.status_blocked?).to be true + expect(history.within_hashtag_count?).to be true + expect(history.count).to eq 2 + expect(history.text).to eq "\nLorem ipsum" end end end @@ -2080,6 +2091,7 @@ def activity_for_object(json) id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', + to: 'https://www.w3.org/ns/activitystreams#Public', tag: [ { type: 'Mention', @@ -2110,6 +2122,9 @@ def activity_for_object(json) context 'when limit is enough' do it 'creates status' do expect(sender.statuses.first).to_not be_nil + + history = NgwordHistory.find_by(uri: object_json[:id]) + expect(history).to be_nil end end @@ -2118,6 +2133,12 @@ def activity_for_object(json) it 'creates status' do expect(sender.statuses.first).to be_nil + + history = NgwordHistory.find_by(uri: object_json[:id]) + expect(history).to_not be_nil + expect(history.status_blocked?).to be true + expect(history.within_mention_count?).to be true + expect(history.count).to eq 3 end end @@ -2126,6 +2147,9 @@ def activity_for_object(json) it 'creates status' do expect(sender.statuses.first).to_not be_nil + + history = NgwordHistory.find_by(uri: object_json[:id]) + expect(history).to be_nil end end @@ -2135,6 +2159,12 @@ def activity_for_object(json) it 'creates status' do expect(sender.statuses.first).to be_nil + + history = NgwordHistory.find_by(uri: object_json[:id]) + expect(history).to_not be_nil + expect(history.status_blocked?).to be true + expect(history.within_stranger_mention_count?).to be true + expect(history.count).to eq 3 end end end @@ -2147,6 +2177,7 @@ def activity_for_object(json) id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', + to: 'https://www.w3.org/ns/activitystreams#Public', tag: [ { type: 'Mention', @@ -2168,6 +2199,9 @@ def activity_for_object(json) context 'when limit is enough' do it 'creates status' do expect(sender.statuses.first).to_not be_nil + + history = NgwordHistory.find_by(uri: object_json[:id]) + expect(history).to be_nil end end @@ -2176,6 +2210,12 @@ def activity_for_object(json) it 'creates status' do expect(sender.statuses.first).to be_nil + + history = NgwordHistory.find_by(uri: object_json[:id]) + expect(history).to_not be_nil + expect(history.status_blocked?).to be true + expect(history.within_stranger_mention_count?).to be true + expect(history.count).to eq 2 end end end