From 95ab1f729c47658c876e123ea1aba54469b77d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Tue, 26 Mar 2024 08:44:16 +0900 Subject: [PATCH] =?UTF-8?q?Change:=20#647=20NG=E3=83=AF=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=81=AE=E5=85=A5=E5=8A=9B=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0?= =?UTF-8?q?=20(#663)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change: #647 NGワードの入力フォーム * Wip: 画面改造 * テストコード、画面 * Fix: 複数の問題 --- .haml-lint_todo.yml | 1 + .../admin/ng_words/keywords_controller.rb | 30 +++++++ .../admin/ng_words/settings_controller.rb | 11 +++ .../admin/ng_words/white_list_controller.rb | 11 +++ app/controllers/admin/ng_words_controller.rb | 21 +++-- .../admin/sensitive_words_controller.rb | 9 +- app/javascript/packs/admin.tsx | 23 ++++- app/lib/activitypub/activity/create.rb | 3 +- app/models/admin/ng_word.rb | 37 ++++---- app/models/form/admin_settings.rb | 4 - app/models/ng_word.rb | 87 +++++++++++++++++++ app/models/sensitive_word.rb | 14 ++- .../process_status_update_service.rb | 3 +- .../ng_words/keywords/_ng_word.html.haml | 10 +++ .../admin/ng_words/keywords/show.html.haml | 43 +++++++++ .../ng_words/{ => settings}/show.html.haml | 29 ++----- .../admin/ng_words/shared/_links.html.haml | 6 ++ .../admin/ng_words/white_list/show.html.haml | 25 ++++++ .../admin/ngword_histories/index.html.haml | 2 +- config/locales/en.yml | 12 +-- config/locales/ja.yml | 16 ++-- config/navigation.rb | 2 +- config/routes/admin.rb | 6 +- db/migrate/20240320231633_create_ng_words.rb | 63 ++++++++++++++ db/schema.rb | 10 ++- lib/tasks/dangerous.rake | 2 + spec/fabricators/ng_word_fabricator.rb | 5 ++ spec/lib/activitypub/activity/create_spec.rb | 58 ++----------- spec/models/admin/ng_word_spec.rb | 77 ++++++++++++++++ .../process_account_service_spec.rb | 4 +- .../process_status_update_service_spec.rb | 24 ++--- spec/services/post_status_service_spec.rb | 25 ++++-- spec/services/update_status_service_spec.rb | 25 ++++-- 33 files changed, 526 insertions(+), 172 deletions(-) create mode 100644 app/controllers/admin/ng_words/keywords_controller.rb create mode 100644 app/controllers/admin/ng_words/settings_controller.rb create mode 100644 app/controllers/admin/ng_words/white_list_controller.rb create mode 100644 app/models/ng_word.rb create mode 100644 app/views/admin/ng_words/keywords/_ng_word.html.haml create mode 100644 app/views/admin/ng_words/keywords/show.html.haml rename app/views/admin/ng_words/{ => settings}/show.html.haml (53%) create mode 100644 app/views/admin/ng_words/shared/_links.html.haml create mode 100644 app/views/admin/ng_words/white_list/show.html.haml create mode 100644 db/migrate/20240320231633_create_ng_words.rb create mode 100644 spec/fabricators/ng_word_fabricator.rb create mode 100644 spec/models/admin/ng_word_spec.rb diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 69c4b88db4..61445344e0 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -31,4 +31,5 @@ linters: exclude: - 'app/views/application/_sidebar.html.haml' - 'app/views/admin/ng_rules/_ng_rule_fields.html.haml' + - 'app/views/admin/ng_words/keywords/_ng_word.html.haml' - 'app/views/admin/sensitive_words/_sensitive_word.html.haml' diff --git a/app/controllers/admin/ng_words/keywords_controller.rb b/app/controllers/admin/ng_words/keywords_controller.rb new file mode 100644 index 0000000000..9af38fab7b --- /dev/null +++ b/app/controllers/admin/ng_words/keywords_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Admin + class NgWords::KeywordsController < NgWordsController + def show + super + @ng_words = ::NgWord.caches.presence || [::NgWord.new] + end + + protected + + def validate + begin + ::NgWord.save_from_raws(settings_params_test) + return true + rescue + flash[:alert] = I18n.t('admin.ng_words.test_error') + redirect_to after_update_redirect_path + end + + false + end + + private + + def after_update_redirect_path + admin_ng_words_keywords_path + end + end +end diff --git a/app/controllers/admin/ng_words/settings_controller.rb b/app/controllers/admin/ng_words/settings_controller.rb new file mode 100644 index 0000000000..63edadfce5 --- /dev/null +++ b/app/controllers/admin/ng_words/settings_controller.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Admin + class NgWords::SettingsController < NgWordsController + protected + + def after_update_redirect_path + admin_ng_words_settings_path + end + end +end diff --git a/app/controllers/admin/ng_words/white_list_controller.rb b/app/controllers/admin/ng_words/white_list_controller.rb new file mode 100644 index 0000000000..343af9ebdb --- /dev/null +++ b/app/controllers/admin/ng_words/white_list_controller.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Admin + class NgWords::WhiteListController < NgWordsController + protected + + def after_update_redirect_path + admin_ng_words_white_list_path + end + end +end diff --git a/app/controllers/admin/ng_words_controller.rb b/app/controllers/admin/ng_words_controller.rb index 17a6306d1a..a70a435fa4 100644 --- a/app/controllers/admin/ng_words_controller.rb +++ b/app/controllers/admin/ng_words_controller.rb @@ -11,13 +11,7 @@ module Admin def create authorize :ng_words, :create? - begin - test_words - rescue - flash[:alert] = I18n.t('admin.ng_words.test_error') - redirect_to after_update_redirect_path - return - end + return unless validate @admin_settings = Form::AdminSettings.new(settings_params) @@ -29,19 +23,24 @@ module Admin end end - private + protected - def test_words - ng_words = "#{settings_params['ng_words']}\n#{settings_params['ng_words_for_stranger_mention']}".split(/\r\n|\r|\n/).filter(&:present?) - Admin::NgWord.reject_with_custom_words?('Sample text', ng_words) + def validate + true end def after_update_redirect_path admin_ng_words_path end + private + def settings_params params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS) end + + def settings_params_test + params.require(:form_admin_settings)[:ng_words_test] + end end end diff --git a/app/controllers/admin/sensitive_words_controller.rb b/app/controllers/admin/sensitive_words_controller.rb index 5fff9394e9..24cdd4efcb 100644 --- a/app/controllers/admin/sensitive_words_controller.rb +++ b/app/controllers/admin/sensitive_words_controller.rb @@ -13,7 +13,7 @@ module Admin authorize :sensitive_words, :create? begin - test_words + ::SensitiveWord.save_from_raws(settings_params_test) rescue flash[:alert] = I18n.t('admin.ng_words.test_error') redirect_to after_update_redirect_path @@ -22,7 +22,7 @@ module Admin @admin_settings = Form::AdminSettings.new(settings_params) - if @admin_settings.save && ::SensitiveWord.save_from_raws(settings_params_test) + if @admin_settings.save flash[:notice] = I18n.t('generic.changes_saved_msg') redirect_to after_update_redirect_path else @@ -32,11 +32,6 @@ module Admin private - def test_words - sensitive_words = settings_params_test['keywords'].compact.uniq - Admin::NgWord.reject_with_custom_words?('Sample text', sensitive_words) - end - def after_update_redirect_path admin_sensitive_words_path end diff --git a/app/javascript/packs/admin.tsx b/app/javascript/packs/admin.tsx index 206c872b4a..18d9eef840 100644 --- a/app/javascript/packs/admin.tsx +++ b/app/javascript/packs/admin.tsx @@ -320,7 +320,8 @@ Rails.delegate( document, '#sensitive-words-table .add-row-button', 'click', - () => { + (ev) => { + ev.preventDefault(); addTableRow('sensitive-words-table'); }, ); @@ -329,8 +330,24 @@ Rails.delegate( document, '#sensitive-words-table .delete-row-button', 'click', - ({ target }) => { - removeTableRow(target, 'sensitive-words-table'); + (ev) => { + ev.preventDefault(); + removeTableRow(ev.target, 'sensitive-words-table'); + }, +); + +Rails.delegate(document, '#ng-words-table .add-row-button', 'click', (ev) => { + ev.preventDefault(); + addTableRow('ng-words-table'); +}); + +Rails.delegate( + document, + '#ng-words-table .delete-row-button', + 'click', + (ev) => { + ev.preventDefault(); + removeTableRow(ev.target, 'ng-words-table'); }, ); diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index b390fc5d24..986d5e46df 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -160,11 +160,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def valid_status? valid = true valid = false if valid && !valid_status_for_ng_rule? - valid = !Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}", uri: @params[:uri], target_type: :status, public: @status_parser.distributable_visibility?) if valid + valid = !Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}", uri: @params[:uri], target_type: :status, public: @status_parser.distributable_visibility?, stranger: mention_to_local_stranger? || reference_to_local_stranger?) if valid 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?(@raw_mention_uris.size, 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?(@raw_mention_uris.size, 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 = false if valid && Setting.block_unfollow_account_mention && (mention_to_local_stranger? || reference_to_local_stranger?) && !local_following_sender? valid diff --git a/app/models/admin/ng_word.rb b/app/models/admin/ng_word.rb index 871909e316..db3df319b0 100644 --- a/app/models/admin/ng_word.rb +++ b/app/models/admin/ng_word.rb @@ -4,20 +4,25 @@ 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? + + if options.delete(:stranger) + ::NgWord.caches.detect { |word| include?(text, word) ? word : nil }&.keyword.tap do |hit_word| + record!(:ng_words_for_stranger_mention, text, hit_word, options) if hit_word.present? + end.present? + else + ::NgWord.caches.filter { |w| !w.stranger }.detect { |word| include?(text, word) ? word : nil }&.keyword.tap do |hit_word| + record!(:ng_words, text, hit_word, options) if hit_word.present? + end.present? + end 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? + opts = options.merge({ stranger: true }) + reject?(text, **opts) end - def reject_with_custom_words?(text, custom_ng_words) - custom_ng_words.any? { |word| include?(text, word) } + def reject_with_custom_word?(text, word) + include_with_regexp?(text, word) end def hashtag_reject?(hashtag_count, **options) @@ -53,19 +58,15 @@ class Admin::NgWord private def include?(text, word) - if word.start_with?('?') && word.size >= 2 - text =~ /#{word[1..]}/i + if word.regexp + text =~ /#{word.keyword}/ else - text.include?(word) + text.include?(word.keyword) end end - def ng_words - Setting.ng_words || [] - end - - def ng_words_for_stranger_mention - Setting.ng_words_for_stranger_mention || [] + def include_with_regexp?(text, word) + text =~ /#{word}/i end def post_hash_tags_max diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 15c29b759d..326434a843 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -44,8 +44,6 @@ class Form::AdminSettings delete_content_cache_without_reaction status_page_url captcha_enabled - ng_words - ng_words_for_stranger_mention stranger_mention_from_local_ng hide_local_users_for_anonymous post_hash_tags_max @@ -122,8 +120,6 @@ class Form::AdminSettings }.freeze STRING_ARRAY_KEYS = %i( - ng_words - ng_words_for_stranger_mention emoji_reaction_disallow_domains permit_new_account_domains ).freeze diff --git a/app/models/ng_word.rb b/app/models/ng_word.rb new file mode 100644 index 0000000000..995d454ca4 --- /dev/null +++ b/app/models/ng_word.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: ng_words +# +# id :bigint(8) not null, primary key +# keyword :string not null +# regexp :boolean default(FALSE), not null +# stranger :boolean default(TRUE), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class NgWord < ApplicationRecord + attr_accessor :keywords, :regexps, :strangers + + validate :check_regexp + + class << self + def caches + Rails.cache.fetch('ng_words') { NgWord.where.not(id: 0).order(:keyword).to_a } + end + + def save_from_hashes(rows) + unmatched = caches + matched = [] + + NgWord.transaction do + rows.filter { |item| item[:keyword].present? }.each do |item| + exists = unmatched.find { |i| i.keyword == item[:keyword] } + + if exists.present? + unmatched.delete(exists) + matched << exists + + next if exists.regexp == item[:regexp] && exists.stranger == item[:stranger] + + exists.update!(regexp: item[:regexp], stranger: item[:stranger]) + elsif matched.none? { |i| i.keyword == item[:keyword] } + NgWord.create!( + keyword: item[:keyword], + regexp: item[:regexp], + stranger: item[:stranger] + ) + end + end + + NgWord.destroy(unmatched.map(&:id)) + end + + true + end + + def save_from_raws(rows) + regexps = rows['regexps'] || [] + strangers = rows['strangers'] || [] + + hashes = (rows['keywords'] || []).zip(rows['temporary_ids'] || []).map do |item| + temp_id = item[1] + { + keyword: item[0], + regexp: regexps.include?(temp_id), + stranger: strangers.include?(temp_id), + } + end + + save_from_hashes(hashes) + end + end + + private + + def invalidate_cache! + Rails.cache.delete('ng_words') + end + + def check_regexp + return if keyword.blank? || !regexp + + begin + Admin::NgWord.reject_with_custom_word?('Sample text', keyword) + rescue + raise Mastodon::ValidationError, I18n.t('admin.ng_words.test_error') + end + end +end diff --git a/app/models/sensitive_word.rb b/app/models/sensitive_word.rb index f88680aaae..baefc8bc86 100644 --- a/app/models/sensitive_word.rb +++ b/app/models/sensitive_word.rb @@ -16,6 +16,8 @@ class SensitiveWord < ApplicationRecord attr_accessor :keywords, :regexps, :remotes, :spoilers + validate :check_regexp + class << self def caches Rails.cache.fetch('sensitive_words') { SensitiveWord.where.not(id: 0).order(:keyword).to_a } @@ -50,8 +52,6 @@ class SensitiveWord < ApplicationRecord end true - # rescue - # false end def save_from_raws(rows) @@ -78,4 +78,14 @@ class SensitiveWord < ApplicationRecord def invalidate_cache! Rails.cache.delete('sensitive_words') end + + def check_regexp + return if keyword.blank? || !regexp + + begin + Admin::NgWord.reject_with_custom_word?('Sample text', keyword) + rescue + raise Mastodon::ValidationError, I18n.t('admin.ng_words.test_error') + end + end end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index d3f220cf57..d44ea02749 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -162,10 +162,9 @@ class ActivityPub::ProcessStatusUpdateService < BaseService end def valid_status? - valid = !Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}", uri: @status.uri, target_type: :status) + valid = !Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}", uri: @status.uri, target_type: :status, stranger: mention_to_local_stranger? || reference_to_local_stranger?) 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? diff --git a/app/views/admin/ng_words/keywords/_ng_word.html.haml b/app/views/admin/ng_words/keywords/_ng_word.html.haml new file mode 100644 index 0000000000..bf81193a97 --- /dev/null +++ b/app/views/admin/ng_words/keywords/_ng_word.html.haml @@ -0,0 +1,10 @@ +- temporary_id = defined?(@temp_id) ? @temp_id += 1 : @temp_id = 1 +%tr{ class: template ? 'template-row' : nil } + %td= f.input :keywords, as: :string, input_html: { multiple: true, value: ng_word.keyword } + %td + .label_input__wrapper= f.check_box :regexps, { multiple: true, checked: ng_word.regexp }, temporary_id, nil + %td + .label_input__wrapper= f.check_box :strangers, { multiple: true, checked: ng_word.stranger }, temporary_id, nil + %td + = hidden_field_tag :'form_admin_settings[ng_words_test][temporary_ids][]', temporary_id, class: 'temporary_id' + = link_to safe_join([fa_icon('times'), t('filters.index.delete')]), '#', class: 'table-action-link delete-row-button' diff --git a/app/views/admin/ng_words/keywords/show.html.haml b/app/views/admin/ng_words/keywords/show.html.haml new file mode 100644 index 0000000000..d53676b7bf --- /dev/null +++ b/app/views/admin/ng_words/keywords/show.html.haml @@ -0,0 +1,43 @@ +- content_for :page_title do + = t('admin.ng_words.title') + +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +- content_for :heading do + %h2= t('admin.ng_words.title') + = render partial: 'admin/ng_words/shared/links' + += simple_form_for @admin_settings, url: admin_ng_words_keywords_path, html: { method: :post } do |f| + = render 'shared/error_messages', object: @admin_settings + + %p.lead + = t('admin.ng_words.preamble') + = link_to t('admin.ngword_histories.title'), admin_ngword_histories_path + + %p= t 'admin.ng_words.phrases.regexp_html' + %p= t 'admin.ng_words.phrases.stranger_html' + + %hr/ + + .table-wrapper + %table.table.keywords-table#ng-words-table + %thead + %tr + %th= t('simple_form.labels.defaults.phrase') + %th= t('admin.ng_words.phrases.regexp_short') + %th= t('admin.ng_words.phrases.stranger_short') + %th + %tbody + = f.simple_fields_for :ng_words_test, @ng_words do |keyword| + = render partial: 'ng_word', collection: @ng_words, locals: { f: keyword, template: false } + + = f.simple_fields_for :ng_words_test, @ng_words do |keyword| + = render partial: 'ng_word', collection: [NgWord.new], locals: { f: keyword, template: true } + %tfoot + %tr + %td{ colspan: 4 } + = link_to safe_join([fa_icon('plus'), t('filters.edit.add_keyword')]), '#', class: 'table-action-link add-row-button' + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/ng_words/show.html.haml b/app/views/admin/ng_words/settings/show.html.haml similarity index 53% rename from app/views/admin/ng_words/show.html.haml rename to app/views/admin/ng_words/settings/show.html.haml index 319b8c4477..53d2ad3214 100644 --- a/app/views/admin/ng_words/show.html.haml +++ b/app/views/admin/ng_words/settings/show.html.haml @@ -4,21 +4,13 @@ - content_for :header_tags do = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' -= simple_form_for @admin_settings, url: admin_ng_words_path, html: { method: :post } do |f| +- content_for :heading do + %h2= t('admin.ng_words.title') + = render partial: 'admin/ng_words/shared/links' + += simple_form_for @admin_settings, url: admin_ng_words_settings_path, html: { method: :post } do |f| = render 'shared/error_messages', object: @admin_settings - %p.lead= t('admin.ng_words.preamble') - - %p.hint - = t 'admin.ng_words.history_hint' - = link_to t('admin.ngword_histories.title'), admin_ngword_histories_path - - .fields-group - = f.input :ng_words_for_stranger_mention, wrapper: :with_label, as: :text, input_html: { rows: 10 }, label: t('admin.ng_words.keywords_for_stranger_mention'), hint: t('admin.ng_words.keywords_for_stranger_mention_hint') - - .fields-group - = f.input :ng_words, wrapper: :with_label, as: :text, input_html: { rows: 10 }, label: t('admin.ng_words.keywords'), hint: t('admin.ng_words.keywords_hint') - .fields-group = f.input :post_hash_tags_max, wrapper: :with_label, as: :integer, label: t('admin.ng_words.post_hash_tags_max') @@ -28,17 +20,6 @@ .fields-group = f.input :post_mentions_max, wrapper: :with_label, as: :integer, label: t('admin.ng_words.post_mentions_max') - %h4= t('admin.ng_words.white_list') - %p.lead - = t('admin.ng_words.white_list_hint') - = link_to t('admin.ng_words.remote_approval_list'), admin_accounts_path(status: 'remote_pending', origin: 'remote') - - .fields-group - = f.input :hold_remote_new_accounts, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.hold_remote_new_accounts'), hint: t('admin.ng_words.remote_approval_hint') - - .fields-group - = f.input :permit_new_account_domains, wrapper: :with_label, as: :text, kmyblue: true, input_html: { rows: 6 }, label: t('admin.ng_words.permit_new_account_domains') - %h4= t('admin.ng_words.deprecated') %p.hint= t('admin.ng_words.deprecated_hint') diff --git a/app/views/admin/ng_words/shared/_links.html.haml b/app/views/admin/ng_words/shared/_links.html.haml new file mode 100644 index 0000000000..aac891c381 --- /dev/null +++ b/app/views/admin/ng_words/shared/_links.html.haml @@ -0,0 +1,6 @@ +.content__heading__tabs + = render_navigation renderer: :links do |primary| + :ruby + primary.item :keywords, safe_join([fa_icon('pencil fw'), t('admin.ng_words.keywords')]), admin_ng_words_keywords_path + primary.item :white_list, safe_join([fa_icon('list fw'), t('admin.ng_words.white_list')]), admin_ng_words_white_list_path + primary.item :settings, safe_join([fa_icon('cog fw'), t('admin.ng_words.settings')]), admin_ng_words_settings_path diff --git a/app/views/admin/ng_words/white_list/show.html.haml b/app/views/admin/ng_words/white_list/show.html.haml new file mode 100644 index 0000000000..a91540be23 --- /dev/null +++ b/app/views/admin/ng_words/white_list/show.html.haml @@ -0,0 +1,25 @@ +- content_for :page_title do + = t('admin.ng_words.title') + +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +- content_for :heading do + %h2= t('admin.ng_words.title') + = render partial: 'admin/ng_words/shared/links' + += simple_form_for @admin_settings, url: admin_ng_words_white_list_path, html: { method: :post } do |f| + = render 'shared/error_messages', object: @admin_settings + + %p.lead + = t('admin.ng_words.white_list_hint') + = link_to t('admin.ng_words.remote_approval_list'), admin_accounts_path(status: 'remote_pending', origin: 'remote') + + .fields-group + = f.input :hold_remote_new_accounts, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.hold_remote_new_accounts'), hint: t('admin.ng_words.remote_approval_hint') + + .fields-group + = f.input :permit_new_account_domains, wrapper: :with_label, as: :text, kmyblue: true, input_html: { rows: 6 }, label: t('admin.ng_words.permit_new_account_domains') + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/ngword_histories/index.html.haml b/app/views/admin/ngword_histories/index.html.haml index 27442441f4..517ccc3559 100644 --- a/app/views/admin/ngword_histories/index.html.haml +++ b/app/views/admin/ngword_histories/index.html.haml @@ -3,7 +3,7 @@ .filters .back-link - = link_to admin_ng_words_path do + = link_to admin_ng_words_keywords_path do = fa_icon 'chevron-left fw' = t('admin.ngword_histories.back_to_ng_words') diff --git a/config/locales/en.yml b/config/locales/en.yml index d54b7a82e6..e1e897e706 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -751,19 +751,21 @@ en: deprecated_hint: These settings will be removed in the next LTS or kmyblue version 14.0, whichever comes first. Please refer to the description of each setting and replace them with the new settings if possible. hide_local_users_for_anonymous: Hide timeline local user posts from anonymous hide_local_users_for_anonymous_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。サーバー設定の「見つける」にある「公開タイムラインへの未認証のアクセスを許可する」で、完全ではありませんが代替可能です。 - history_hint: We recommend that you regularly check your NG words to make sure that you have not specified the NG words incorrectly. hold_remote_new_accounts: Hold new remote accounts keywords: Reject keywords - keywords_for_stranger_mention: Reject keywords when mention/reply/reference/quote from strangers - keywords_for_stranger_mention_hint: This words are checked posts from other servers only. - keywords_hint: The first character of the line is "?". to use regular expressions permit_new_account_domains: Domain list to automatically approve new users - preamble: This setting is useful for solving problems related to spam that are difficult to address with domain blocking. You can reject posts that contain specific keywords or too many mentions. Please consider your settings carefully to avoid deleting problematic posts. More detailed settings can be made by using "NG Rules". + preamble: This setting is useful for solving problems related to spam that are difficult to address with domain blocking. You can reject posts that meet certain criteria, such as the inclusion of specific keywords. Please consider your settings carefully and check your history regularly to ensure that problem-free submissions are not deleted. 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) + phrases: + regexp_html: 正規 表現 にチェックの入っている項目は、正規表現を用いての比較となります。 + regexp_short: 正規 + stranger_html: 無関 係のフォロワーからのメンション にチェックの入っている項目は、フォロー関係にないアカウントからのメンション、返信、参照などのみに適用されます。 + stranger_short: 無関 remote_approval_list: List of remote accounts awaiting approval remote_approval_hint: Newly recognized accounts with unspecified domains will be placed in Suspended status. You can review that list and approve them if necessary. If this setting is not enabled, all remote accounts will be approved immediately. + settings: Settings stranger_mention_from_local_ng: フォローしていないアカウントへのメンションのNGワードを、ローカルユーザーによる投稿にも適用する stranger_mention_from_local_ng_hint: この設定は削除予定です。設定削除後は、常にチェックをつけている場合と同じ挙動になります。この動作を希望しない場合は、NGルールで代替してください。 test_error: Testing is returned any errors diff --git a/config/locales/ja.yml b/config/locales/ja.yml index d813f6d93d..fdfa6fc03e 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -684,7 +684,7 @@ ja: empty: NGルールが空です empty_title: 空のタイトル hit_count: ここ一週間で %{count} 件の検出 - preamble: スパムの中にはどうしても緩すぎる条件を指定しなければならず、他の正常な投稿が規制に巻き込まれやすくなる場合があります。NGルールではアカウントや投稿の特徴などを詳細に指定して、巻き込まれる投稿を少しでも減らすことができます。設定は慎重に検討し、正常な投稿の巻き込みが最小限になるよう定期的に履歴を確認してください。 + preamble: 「NGワードとスパム」機能においてどうしても緩すぎる条件を指定しなければならず、他の正常な投稿が規制に巻き込まれやすくなる場合があります。NGルールではアカウントや投稿の特徴などを詳細に指定して、巻き込まれる投稿を少しでも減らすことができます。設定は慎重に検討し、正常な投稿の巻き込みが最小限になるよう定期的に履歴を確認してください。 title: NGルール new: save: 新規NGルールを保存 @@ -750,19 +750,21 @@ ja: deprecated_hint: これらの設定は、次回のLTS、またはkmyblueバージョン14.0のどちらか早い方で削除する予定です。それぞれの設定の説明を参照して、可能であれば新しい設定に置き換えてください。 hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする hide_local_users_for_anonymous_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。サーバー設定の「見つける」にある「公開タイムラインへの未認証のアクセスを許可する」で、完全ではありませんが代替可能です。 - history_hint: 設定されたNGワードによって実際に拒否された投稿などは、履歴より確認できます。NGワードの指定に誤りがないか定期的に確認することをおすすめします。 hold_remote_new_accounts: リモートの新規アカウントを保留する - keywords: 投稿できないキーワード - keywords_for_stranger_mention: フォローしていないアカウントへのメンションや参照で利用できないキーワード - keywords_for_stranger_mention_hint: フォローしていないアカウントへのメンション、参照、引用にのみ適用されます - keywords_hint: 行を「?」で始めると、正規表現が使えます + keywords: 拒否するキーワード permit_new_account_domains: 新規ユーザーを自動承認するドメイン - preamble: ドメインブロックでは対処の難しいスパムに関する問題の解決に、この設定が役に立ちます。特定キーワードが含まれていたり、メンション数が多すぎたりする投稿を拒否することができます。問題のない投稿が削除されないよう、設定は慎重に検討してください。「NGルール」を用いることで、さらに細かい設定が可能です。 + phrases: + regexp_html: 正規 表現 にチェックの入っている項目は、正規表現を用いての比較となります。 + regexp_short: 正規 + stranger_html: 無関 係のフォロワーからのメンション にチェックの入っている項目は、フォロー関係にないアカウントからのメンション、返信、参照などのみに適用されます。 + stranger_short: 無関 + preamble: ドメインブロックでは対処の難しいスパムに関する問題の解決に、この設定が役に立ちます。特定キーワードが含まれているなどの条件を満たした投稿を拒否することができます。問題のない投稿が削除されないよう設定は慎重に検討し、定期的に履歴を確認してください。 post_hash_tags_max: 投稿に設定可能なハッシュタグの最大数 post_mentions_max: 投稿に設定可能なメンションの最大数 post_stranger_mentions_max: 投稿に設定可能なメンションの最大数 (メンション先にフォロワー以外を1人でも含む場合) remote_approval_list: 承認待ちのリモートアカウント一覧 remote_approval_hint: 指定されていないドメインで新しく認識されたアカウントはサスペンド状態になります。その一覧を確認し、必要であれば承認を行うことができます。この設定が有効でない場合、全てのリモートアカウントが即座に承認されます。 + settings: 詳細設定 stranger_mention_from_local_ng: フォローしていないアカウントへのメンションのNGワードを、ローカルユーザーによる投稿にも適用する stranger_mention_from_local_ng_hint: この設定は削除予定です。設定削除後は、常にチェックをつけている場合と同じ挙動になります。この動作を希望しない場合は、NGルールで代替してください。 test_error: NGワードのテストに失敗しました。正規表現のミスが含まれているかもしれません diff --git a/config/navigation.rb b/config/navigation.rb index 0f1c8818db..0bb19ae090 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -49,7 +49,7 @@ SimpleNavigation::Configuration.run do |navigation| n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), nil, if: -> { current_user.can?(:manage_reports, :view_audit_log, :manage_users, :manage_invites, :manage_taxonomies, :manage_federation, :manage_blocks, :manage_ng_words, :manage_sensitive_words) && !self_destruct } do |s| s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_path, highlights_on: %r{/admin/reports}, if: -> { current_user.can?(:manage_reports) } s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_path(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts|/admin/disputes|/admin/users}, if: -> { current_user.can?(:manage_users) } - s.item :ng_words, safe_join([fa_icon('list fw'), t('admin.ng_words.title')]), admin_ng_words_path, highlights_on: %r{/admin/(ng_words|ngword_histories)}, if: -> { current_user.can?(:manage_ng_words) } + s.item :ng_words, safe_join([fa_icon('list fw'), t('admin.ng_words.title')]), admin_ng_words_keywords_path, highlights_on: %r{/admin/(ng_words|ngword_histories)}, if: -> { current_user.can?(:manage_ng_words) } s.item :ng_rules, safe_join([fa_icon('rub fw'), t('admin.ng_rules.title')]), admin_ng_rules_path, highlights_on: %r{/admin/(ng_rules|ng_rule_histories)}, if: -> { current_user.can?(:manage_ng_words) } s.item :sensitive_words, safe_join([fa_icon('list fw'), t('admin.sensitive_words.title')]), admin_sensitive_words_path, highlights_on: %r{/admin/sensitive_words}, if: -> { current_user.can?(:manage_sensitive_words) } s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.can?(:manage_invites) } diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 64636957b1..6622ffcafc 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -32,7 +32,11 @@ namespace :admin do resources :action_logs, only: [:index] resources :warning_presets, except: [:new, :show] - resource :ng_words, only: [:show, :create] + namespace :ng_words do + resource :keywords, only: [:show, :create], controller: 'keywords' + resource :white_list, only: [:show, :create], controller: 'white_list' + resource :settings, only: [:show, :create], controller: 'settings' + end resources :ngword_histories, only: [:index] resources :ng_rules, except: [:show] do member do diff --git a/db/migrate/20240320231633_create_ng_words.rb b/db/migrate/20240320231633_create_ng_words.rb new file mode 100644 index 0000000000..faa8964710 --- /dev/null +++ b/db/migrate/20240320231633_create_ng_words.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +class CreateNgWords < ActiveRecord::Migration[7.1] + class Setting < ApplicationRecord + def value + YAML.safe_load(self[:value], permitted_classes: [ActiveSupport::HashWithIndifferentAccess, Symbol]) if self[:value].present? + end + + def value=(new_value) + self[:value] = new_value.to_yaml + end + end + + class NgWord < ApplicationRecord; end + + def normalized_keyword(keyword) + if regexp?(keyword) + keyword[1..] + else + keyword + end + end + + def regexp?(keyword) + keyword.start_with?('?') && keyword.size >= 2 + end + + def up + create_table :ng_words do |t| + t.string :keyword, null: false + t.boolean :regexp, null: false, default: false + t.boolean :stranger, null: false, default: true + + t.timestamps + end + + settings = Setting.where(var: %i(ng_words ng_words_for_stranger_mention)) + ng_words = settings.find { |s| s.var == 'ng_words' }&.value&.compact_blank&.uniq || [] + ng_words_for_stranger_mention = settings.find { |s| s.var == 'ng_words_for_stranger_mention' }&.value&.compact_blank&.uniq || [] + + (ng_words + ng_words_for_stranger_mention).compact.uniq.each do |word| + NgWord.create!( + keyword: normalized_keyword(word), + regexp: regexp?(word), + stranger: ng_words_for_stranger_mention.include?(word) + ) + end + + settings.destroy_all + end + + def down + ng_words = NgWord.where(stranger: false).map { |s| s.regexp ? "?#{s.keyword}" : s.keyword } + ng_words_for_stranger_mention = NgWord.where(stranger: true).map { |s| s.regexp ? "?#{s.keyword}" : s.keyword } + + Setting.where(var: %i(ng_words ng_words_for_stranger_mention)).destroy_all + + Setting.new(var: :ng_words).tap { |s| s.value = ng_words }.save! + Setting.new(var: :ng_words_for_stranger_mention).tap { |s| s.value = ng_words_for_stranger_mention }.save! + + drop_table :ng_words + end +end diff --git a/db/schema.rb b/db/schema.rb index e90f1af244..808a7386cd 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_03_12_230204) do +ActiveRecord::Schema[7.1].define(version: 2024_03_20_231633) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -926,6 +926,14 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_12_230204) do t.datetime "updated_at", null: false end + create_table "ng_words", force: :cascade do |t| + t.string "keyword", null: false + t.boolean "regexp", default: false, null: false + t.boolean "stranger", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "ngword_histories", force: :cascade do |t| t.string "uri", null: false t.integer "target_type", null: false diff --git a/lib/tasks/dangerous.rake b/lib/tasks/dangerous.rake index 41ef622188..ae5e414171 100644 --- a/lib/tasks/dangerous.rake +++ b/lib/tasks/dangerous.rake @@ -94,6 +94,7 @@ namespace :dangerous do 20240227225017 20240229233617 20240312230204 + 20240320231633 ) # Removed: account_groups target_tables = %w( @@ -113,6 +114,7 @@ namespace :dangerous do ng_rules ng_rule_histories ngword_histories + ng_words pending_follow_requests pending_statuses scheduled_expiration_statuses diff --git a/spec/fabricators/ng_word_fabricator.rb b/spec/fabricators/ng_word_fabricator.rb new file mode 100644 index 0000000000..b044f70528 --- /dev/null +++ b/spec/fabricators/ng_word_fabricator.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Fabricator(:ng_word) do + keyword { sequence(:keyword) { |i| "keyword_#{i}" } } +end diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 414b967380..cda7d3f033 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -1755,8 +1755,8 @@ RSpec.describe ActivityPub::Activity::Create do let(:custom_before) { true } let(:custom_before_sub) { false } let(:content) { 'Lorem ipsum' } - let(:ng_words) { 'hello' } - let(:ng_words_for_stranger_mention) { 'ohagi' } + let(:ng_word) { 'hello' } + let(:ng_word_for_stranger_mention) { 'ohagi' } let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -1767,7 +1767,8 @@ RSpec.describe ActivityPub::Activity::Create do end before do - Form::AdminSettings.new(ng_words: ng_words, ng_words_for_stranger_mention: ng_words_for_stranger_mention).save + Fabricate(:ng_word, keyword: ng_word, stranger: false) + Fabricate(:ng_word, keyword: ng_word_for_stranger_mention, stranger: true) subject.perform unless custom_before_sub end @@ -1777,11 +1778,6 @@ RSpec.describe ActivityPub::Activity::Create do it 'creates status' do expect(sender.statuses.first).to_not be_nil end - - it 'does not record history' do - history = NgwordHistory.find_by(uri: object_json[:id]) - expect(history).to be_nil - end end context 'when hit ng words' do @@ -1796,7 +1792,7 @@ RSpec.describe ActivityPub::Activity::Create do expect(history).to_not be_nil expect(history.status_blocked?).to be true expect(history.within_ng_words?).to be true - expect(history.keyword).to eq ng_words + expect(history.keyword).to eq ng_word end end @@ -1813,11 +1809,6 @@ RSpec.describe ActivityPub::Activity::Create do it 'creates status' do expect(sender.statuses.first).to be_nil end - - it 'records history' do - history = NgwordHistory.find_by(uri: object_json[:id]) - expect(history).to be_nil - end end context 'when mention from tags' do @@ -1844,11 +1835,6 @@ RSpec.describe ActivityPub::Activity::Create do it 'creates status' do expect(sender.statuses.first).to_not be_nil end - - it 'does not record history' do - history = NgwordHistory.find_by(uri: object_json[:id]) - expect(history).to be_nil - end end context 'with using ng words for stranger' do @@ -1857,14 +1843,6 @@ RSpec.describe ActivityPub::Activity::Create do it 'creates status' do expect(sender.statuses.first).to be_nil end - - it 'records history' do - history = NgwordHistory.find_by(uri: object_json[:id]) - expect(history).to_not be_nil - expect(history.status_blocked?).to be true - expect(history.within_ng_words_for_stranger_mention?).to be true - expect(history.keyword).to eq ng_words_for_stranger_mention - end end context 'with using ng words for stranger but receiver is following him' do @@ -1879,11 +1857,6 @@ RSpec.describe ActivityPub::Activity::Create do it 'creates status' do expect(sender.statuses.first).to_not be_nil end - - it 'does not record history' do - history = NgwordHistory.find_by(uri: object_json[:id]) - expect(history).to be_nil - end end context 'with using ng words for stranger but multiple receivers are partically following him' do @@ -1917,14 +1890,6 @@ RSpec.describe ActivityPub::Activity::Create do it 'creates status' do expect(sender.statuses.first).to be_nil end - - it 'records history' do - history = NgwordHistory.find_by(uri: object_json[:id]) - expect(history).to_not be_nil - expect(history.status_blocked?).to be true - expect(history.within_ng_words_for_stranger_mention?).to be true - expect(history.keyword).to eq ng_words_for_stranger_mention - end end end @@ -1946,14 +1911,6 @@ RSpec.describe ActivityPub::Activity::Create do it 'creates status' do expect(sender.statuses.first).to be_nil end - - it 'records history' do - history = NgwordHistory.find_by(uri: object_json[:id]) - expect(history).to_not be_nil - expect(history.status_blocked?).to be true - expect(history.within_ng_words_for_stranger_mention?).to be true - expect(history.keyword).to eq ng_words_for_stranger_mention - end end context 'with following' do @@ -1967,11 +1924,6 @@ RSpec.describe ActivityPub::Activity::Create do it 'creates status' do expect(sender.statuses.first).to_not be_nil end - - it 'does not record history' do - history = NgwordHistory.find_by(uri: object_json[:id]) - expect(history).to be_nil - end end end diff --git a/spec/models/admin/ng_word_spec.rb b/spec/models/admin/ng_word_spec.rb new file mode 100644 index 0000000000..15dac24775 --- /dev/null +++ b/spec/models/admin/ng_word_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Admin::NgWord do + describe '#reject?' do + subject { described_class.reject?(text, stranger: stranger, uri: uri, target_type: :status) } + + let(:text) { 'This is a ohagi.' } + let(:stranger) { false } + let(:uri) { nil } + + context 'when general post' do + it 'ng word hits' do + Fabricate(:ng_word, keyword: 'ohagi', stranger: false) + expect(subject).to be true + end + + it 'else ng word does not hit' do + Fabricate(:ng_word, keyword: 'angry', stranger: false) + expect(subject).to be false + end + + it 'stranger word does not hit' do + Fabricate(:ng_word, keyword: 'ohagi', stranger: true) + expect(subject).to be false + end + end + + context 'when mention to stranger' do + let(:stranger) { true } + + it 'ng word hits' do + Fabricate(:ng_word, keyword: 'ohagi', stranger: true) + expect(subject).to be true + end + + it 'else ng word does not hit' do + Fabricate(:ng_word, keyword: 'angry', stranger: true) + expect(subject).to be false + end + + it 'general word hits' do + Fabricate(:ng_word, keyword: 'ohagi', stranger: false) + expect(subject).to be true + end + end + + context 'when remote post' do + let(:uri) { 'https://example.com/note' } + + it 'ng word hits' do + Fabricate(:ng_word, keyword: 'ohagi', stranger: false) + expect(subject).to be true + expect(NgwordHistory.find_by(uri: uri)).to_not be_nil + end + + it 'else ng word does not hit' do + Fabricate(:ng_word, keyword: 'angry', stranger: false) + expect(subject).to be false + expect(NgwordHistory.find_by(uri: uri)).to be_nil + end + end + + context 'when using regexp' do + it 'regexp hits with enable' do + Fabricate(:ng_word, keyword: 'oha[ghi]i', regexp: true, stranger: false) + expect(subject).to be true + end + + it 'regexp does not hit without enable' do + Fabricate(:ng_word, keyword: 'oha[ghi]i', regexp: false, stranger: false) + expect(subject).to be false + end + end + end +end diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index bb14e18460..796bcb425e 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -425,7 +425,7 @@ RSpec.describe ActivityPub::ProcessAccountService do end it 'creates account when ng word is not set' do - Setting.ng_words = ['Amazon'] + Fabricate(:ng_word, keyword: 'Amazon', stranger: false) subject expect(account.reload.display_name).to eq 'Ohagi' @@ -434,7 +434,7 @@ RSpec.describe ActivityPub::ProcessAccountService do end it 'does not create account when ng word is set' do - Setting.ng_words = ['Ohagi'] + Fabricate(:ng_word, keyword: 'Ohagi', stranger: false) subject expect(account.reload.display_name).to_not eq 'Ohagi' diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index 2653b35b44..969e0d3db3 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -515,7 +515,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do let(:content) { 'ng word test' } it 'update status' do - Form::AdminSettings.new(ng_words: 'test').save + Fabricate(:ng_word, keyword: 'test', stranger: false) subject.call(status, json, json) expect(status.reload.text).to_not eq content @@ -526,7 +526,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do let(:content) { 'ng word aiueo' } it 'update status' do - Form::AdminSettings.new(ng_words: 'test').save + Fabricate(:ng_word, keyword: 'test', stranger: false) subject.call(status, json, json) expect(status.reload.text).to eq content @@ -542,7 +542,8 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do 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 + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test') subject.call(status, json, json) expect(status.reload.text).to_not eq content @@ -550,7 +551,8 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do end it 'update status when following' do - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test') alice.follow!(status.account) subject.call(status, json, json) @@ -568,7 +570,8 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do 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 + Form::AdminSettings.new(stranger_mention_from_local_ng: '0').save + Fabricate(:ng_word, keyword: 'test') subject.call(status, json, json) expect(status.reload.text).to_not eq content @@ -589,7 +592,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do end it 'update status' do - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save + Fabricate(:ng_word, keyword: 'test') subject.call(status, json, json) expect(status.reload.text).to eq content @@ -607,7 +610,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do let(:thread) { Fabricate(:status, account: alice) } it 'update status' do - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save + Fabricate(:ng_word, keyword: 'test') subject.call(status, json, json) expect(status.reload.text).to_not eq content @@ -629,7 +632,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do end it 'update status' do - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save + Fabricate(:ng_word, keyword: 'test') subject.call(status, json, json) expect(status.reload.text).to eq content @@ -658,7 +661,8 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do 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 + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test') subject.call(status, json, json) expect(status.reload.text).to_not eq content @@ -671,7 +675,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do end it 'update status' do - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save + Fabricate(:ng_word, keyword: 'test') subject.call(status, json, json) expect(status.reload.text).to eq content diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index 4111687af3..9ecec791c4 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -613,7 +613,7 @@ RSpec.describe PostStatusService do it 'hit ng words' do account = Fabricate(:account) text = 'ng word test' - Form::AdminSettings.new(ng_words: 'test').save + Fabricate(:ng_word, keyword: 'test', stranger: false) expect { subject.call(account, text: text) }.to raise_error(Mastodon::ValidationError) end @@ -621,7 +621,7 @@ RSpec.describe PostStatusService do it 'not hit ng words' do account = Fabricate(:account) text = 'ng word aiueo' - Form::AdminSettings.new(ng_words: 'test').save + Fabricate(:ng_word, keyword: 'test', stranger: false) status = subject.call(account, text: text) @@ -633,7 +633,8 @@ RSpec.describe PostStatusService do account = Fabricate(:account) Fabricate(:account, username: 'ohagi', domain: nil) text = 'ng word test @ohagi' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test', stranger: true) expect { subject.call(account, text: text) }.to raise_error(Mastodon::ValidationError) end @@ -642,7 +643,8 @@ RSpec.describe PostStatusService do account = Fabricate(:account) Fabricate(:account, username: 'ohagi', domain: nil) text = 'ng word test @ohagi' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '0').save + Fabricate(:ng_word, keyword: 'test', stranger: true) status = subject.call(account, text: text) @@ -655,7 +657,8 @@ RSpec.describe PostStatusService do mentioned = Fabricate(:account, username: 'ohagi', domain: nil) mentioned.follow!(account) text = 'ng word test @ohagi' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test', stranger: true) status = subject.call(account, text: text) @@ -666,7 +669,8 @@ RSpec.describe PostStatusService do it 'hit ng words for reply' do account = Fabricate(:account) text = 'ng word test' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test', stranger: true) expect { subject.call(account, text: text, thread: Fabricate(:status)) }.to raise_error(Mastodon::ValidationError) end @@ -676,7 +680,8 @@ RSpec.describe PostStatusService do mentioned = Fabricate(:account, username: 'ohagi', domain: nil) mentioned.follow!(account) text = 'ng word test' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test', stranger: true) status = subject.call(account, text: text) @@ -702,7 +707,8 @@ RSpec.describe PostStatusService do account = Fabricate(:account) Fabricate(:account, username: 'ohagi', domain: nil) text = "ng word test BT: #{ActivityPub::TagManager.instance.uri_for(target_status)}" - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test', stranger: true) expect { subject.call(account, text: text) }.to raise_error(Mastodon::ValidationError) end @@ -713,7 +719,8 @@ RSpec.describe PostStatusService do target_status.account.follow!(account) Fabricate(:account, username: 'ohagi', domain: nil) text = "ng word test BT: #{ActivityPub::TagManager.instance.uri_for(target_status)}" - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test', stranger: true) status = subject.call(account, text: text) diff --git a/spec/services/update_status_service_spec.rb b/spec/services/update_status_service_spec.rb index 2d57c0aedd..211e80e65b 100644 --- a/spec/services/update_status_service_spec.rb +++ b/spec/services/update_status_service_spec.rb @@ -272,14 +272,14 @@ RSpec.describe UpdateStatusService do it 'hit ng words' do text = 'ng word test' - Form::AdminSettings.new(ng_words: 'test').save + Fabricate(:ng_word, keyword: 'test', stranger: false) expect { subject.call(status, status.account_id, text: text) }.to raise_error(Mastodon::ValidationError) end it 'not hit ng words' do text = 'ng word aiueo' - Form::AdminSettings.new(ng_words: 'test').save + Fabricate(:ng_word, keyword: 'test', stranger: false) status2 = subject.call(status, status.account_id, text: text) @@ -290,7 +290,8 @@ RSpec.describe UpdateStatusService do it 'hit ng words for mention' do Fabricate(:account, username: 'ohagi', domain: nil) text = 'ng word test @ohagi' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test') expect { subject.call(status, status.account_id, text: text) }.to raise_error(Mastodon::ValidationError) expect(status.reload.text).to_not eq text @@ -300,7 +301,8 @@ RSpec.describe UpdateStatusService do it 'hit ng words for mention but local posts are not checked' do Fabricate(:account, username: 'ohagi', domain: nil) text = 'ng word test @ohagi' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '0').save + Fabricate(:ng_word, keyword: 'test') status2 = subject.call(status, status.account_id, text: text) @@ -312,7 +314,8 @@ RSpec.describe UpdateStatusService do mentioned = Fabricate(:account, username: 'ohagi', domain: nil) mentioned.follow!(account) text = 'ng word test @ohagi' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test') status2 = subject.call(status, status.account_id, text: text) @@ -322,7 +325,8 @@ RSpec.describe UpdateStatusService do it 'hit ng words for reply' do text = 'ng word test' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test') status = PostStatusService.new.call(account, text: 'hello', thread: Fabricate(:status)) @@ -334,7 +338,8 @@ RSpec.describe UpdateStatusService do mentioned = Fabricate(:account, username: 'ohagi', domain: nil) mentioned.follow!(account) text = 'ng word test' - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test') status = PostStatusService.new.call(account, text: 'hello', thread: Fabricate(:status, account: mentioned)) @@ -360,7 +365,8 @@ RSpec.describe UpdateStatusService do it 'hit ng words for reference' do target_status = Fabricate(:status) text = "ng word test BT: #{ActivityPub::TagManager.instance.uri_for(target_status)}" - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test') status = PostStatusService.new.call(account, text: 'hello') @@ -371,7 +377,8 @@ RSpec.describe UpdateStatusService do target_status = Fabricate(:status) target_status.account.follow!(status.account) text = "ng word test BT: #{ActivityPub::TagManager.instance.uri_for(target_status)}" - Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save + Form::AdminSettings.new(stranger_mention_from_local_ng: '1').save + Fabricate(:ng_word, keyword: 'test') status = PostStatusService.new.call(account, text: 'hello')