From cd00d7f533d5fca1cb2f4410a07f4989aa570145 Mon Sep 17 00:00:00 2001 From: KMY Date: Thu, 27 Jul 2023 12:47:33 +0900 Subject: [PATCH] Add sensitive words setting --- .../admin/sensitive_words_controller.rb | 34 +++++++++++++++++++ app/models/admin/sensitive_word.rb | 25 ++++++++++++++ app/models/form/admin_settings.rb | 4 +++ app/models/user_role.rb | 2 ++ app/policies/sensitive_words_policy.rb | 11 ++++++ app/services/post_status_service.rb | 8 +++++ app/services/update_status_service.rb | 8 +++++ .../admin/sensitive_words/show.html.haml | 19 +++++++++++ config/locales/en.yml | 6 ++++ config/locales/ja.yml | 8 +++++ config/navigation.rb | 3 +- config/routes/admin.rb | 1 + 12 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 app/controllers/admin/sensitive_words_controller.rb create mode 100644 app/models/admin/sensitive_word.rb create mode 100644 app/policies/sensitive_words_policy.rb create mode 100644 app/views/admin/sensitive_words/show.html.haml diff --git a/app/controllers/admin/sensitive_words_controller.rb b/app/controllers/admin/sensitive_words_controller.rb new file mode 100644 index 0000000000..d97da2a927 --- /dev/null +++ b/app/controllers/admin/sensitive_words_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Admin + class SensitiveWordsController < BaseController + def show + authorize :sensitive_words, :show? + + @admin_settings = Form::AdminSettings.new + end + + def create + authorize :sensitive_words, :create? + + @admin_settings = Form::AdminSettings.new(settings_params) + + if @admin_settings.save + flash[:notice] = I18n.t('generic.changes_saved_msg') + redirect_to after_update_redirect_path + else + render :index + end + end + + private + + def after_update_redirect_path + admin_sensitive_words_path + end + + def settings_params + params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS) + end + end +end diff --git a/app/models/admin/sensitive_word.rb b/app/models/admin/sensitive_word.rb new file mode 100644 index 0000000000..0e4f3e6a16 --- /dev/null +++ b/app/models/admin/sensitive_word.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Admin::SensitiveWord + class << self + def sensitive?(text, spoiler_text) + exposure_text = (spoiler_text.presence || text) + (spoiler_text.blank? && sensitive_words.any? { |word| text.include?(word) }) || + sensitive_words_for_full.any? { |word| exposure_text.include?(word) } + end + + def modified_text(text, spoiler_text) + spoiler_text.present? ? "#{spoiler_text}\n\n#{text}" : text + end + + private + + def sensitive_words + Setting.sensitive_words || [] + end + + def sensitive_words_for_full + Setting.sensitive_words_for_full || [] + end + end +end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index ff7ef50c12..e11fe50961 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -38,6 +38,8 @@ class Form::AdminSettings enable_block_emoji_reaction_settings hide_local_users_for_anonymous post_hash_tags_max + sensitive_words + sensitive_words_for_full ).freeze INTEGER_KEYS = %i( @@ -70,6 +72,8 @@ class Form::AdminSettings STRING_ARRAY_KEYS = %i( ng_words + sensitive_words + sensitive_words_for_full ).freeze attr_accessor(*KEYS) diff --git a/app/models/user_role.rb b/app/models/user_role.rb index 0fd9b6e498..5368ae5ed2 100644 --- a/app/models/user_role.rb +++ b/app/models/user_role.rb @@ -36,6 +36,7 @@ class UserRole < ApplicationRecord manage_roles: (1 << 17), manage_user_access: (1 << 18), delete_user_data: (1 << 19), + manage_sensitive_words: (1 << 29), manage_ng_words: (1 << 30), }.freeze @@ -63,6 +64,7 @@ class UserRole < ApplicationRecord manage_taxonomies manage_invites manage_ng_words + manage_sensitive_words ).freeze, administration: %w( diff --git a/app/policies/sensitive_words_policy.rb b/app/policies/sensitive_words_policy.rb new file mode 100644 index 0000000000..0812aa9751 --- /dev/null +++ b/app/policies/sensitive_words_policy.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class SensitiveWordsPolicy < ApplicationPolicy + def show? + role.can?(:manage_sensitive_words) + end + + def create? + role.can?(:manage_sensitive_words) + end +end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 08d3f1998a..eb5038b332 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -82,10 +82,18 @@ class PostStatusService < BaseService @scheduled_at = @options[:scheduled_at]&.to_datetime @scheduled_at = nil if scheduled_in_the_past? @reference_ids = (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?) + process_sensitive_words rescue ArgumentError raise ActiveRecord::RecordInvalid end + def process_sensitive_words + if [:public, :public_unlisted, :login].include?(@visibility&.to_sym) && Admin::SensitiveWord.sensitive?(@text, @options[:spoiler_text] || '') + @text = Admin::SensitiveWord.modified_text(@text, @options[:spoiler_text]) + @options[:spoiler_text] = I18n.t('admin.sensitive_words.alert') + end + end + def searchability return :private if @options[:searchability]&.to_sym == :public && @visibility&.to_sym == :unlisted && @account.user&.setting_disallow_unlisted_public_searchability diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index be0bf526bc..9a8d23cb7e 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -127,6 +127,7 @@ class UpdateStatusService < BaseService @status.markdown = @options[:markdown] || false @status.sensitive = @options[:sensitive] || @options[:spoiler_text].present? if @options.key?(:sensitive) || @options.key?(:spoiler_text) @status.language = valid_locale_cascade(@options[:language], @status.language, @status.account.user&.preferred_posting_language, I18n.default_locale) + process_sensitive_words # We raise here to rollback the entire transaction raise NoChangesSubmittedError unless significant_changes? @@ -137,6 +138,13 @@ class UpdateStatusService < BaseService @status.save! end + def process_sensitive_words + return unless [:public, :public_unlisted, :login].include?(@status.visibility&.to_sym) && Admin::SensitiveWord.sensitive?(@status.text, @status.spoiler_text || '') + + @status.text = Admin::SensitiveWord.modified_text(@status.text, @status.spoiler_text) + @status.spoiler_text = I18n.t('admin.sensitive_words.alert') + end + def update_expiration! UpdateStatusExpirationService.new.call(@status) end diff --git a/app/views/admin/sensitive_words/show.html.haml b/app/views/admin/sensitive_words/show.html.haml new file mode 100644 index 0000000000..bb5447d3e1 --- /dev/null +++ b/app/views/admin/sensitive_words/show.html.haml @@ -0,0 +1,19 @@ +- content_for :page_title do + = t('admin.sensitive_words.title') + +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +%p.hint= t 'admin.sensitive_words.hint' + += simple_form_for @admin_settings, url: admin_sensitive_words_path, html: { method: :post } do |f| + = render 'shared/error_messages', object: @admin_settings + + .fields-group + = f.input :sensitive_words_for_full, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.sensitive_words.keywords_for_all'), hint: t('admin.sensitive_words.keywords_for_all_hint') + + .fields-group + = f.input :sensitive_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.sensitive_words.keywords'), hint: t('admin.sensitive_words.keywords_hint') + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/config/locales/en.yml b/config/locales/en.yml index 93593e2569..5ee09dadbf 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -773,6 +773,12 @@ en: edit: Edit rule empty: No server rules have been defined yet. title: Server rules + sensitive_words: + alert: This post contains sensitive words, so alert added + hint: This keywords is applied to public posts only.. + keywords: Sensitive keywords + keywords_for_all: Sensitive keywords (Contains CW alert) + title: Sensitive words and moderation options settings: about: manage_rules: Manage server rules diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 5a2a37d0e0..1690c7f3dd 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -769,6 +769,14 @@ ja: edit: ルールを編集 empty: サーバーのルールが定義されていません。 title: サーバーのルール + sensitive_words: + alert: この投稿にはセンシティブなキーワードが含まれるため、警告文が追加されました + hint: センシティブなキーワードの設定は、当サーバーのローカルユーザーによる公開範囲「公開」「ローカル公開」「ログインユーザーのみ」に対して適用されます。 + keywords: センシティブなキーワード(警告文は除外) + keywords_for_all: センシティブなキーワード(警告文にも適用) + keywords_for_all_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。警告文にも含まれていればCWになります + keywords_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。ただし警告文に使用していた場合は無視されます + title: センシティブ単語と設定 settings: about: manage_rules: サーバーのルールを管理 diff --git a/config/navigation.rb b/config/navigation.rb index 8b7b6b7df2..d64565e369 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -40,7 +40,8 @@ SimpleNavigation::Configuration.run do |navigation| 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 :media_attachments, safe_join([fa_icon('picture-o fw'), t('admin.media_attachments.title')]), admin_media_attachments_path, highlights_on: %r{/admin/media_attachments}, if: -> { current_user.can?(:manage_users) } - s.item :ng_words, safe_join([fa_icon('picture-o fw'), t('admin.ng_words.title')]), admin_ng_words_path, highlights_on: %r{/admin/ng_words}, 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_path, highlights_on: %r{/admin/ng_words}, 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) } s.item :follow_recommendations, safe_join([fa_icon('user-plus fw'), t('admin.follow_recommendations.title')]), admin_follow_recommendations_path, highlights_on: %r{/admin/follow_recommendations}, if: -> { current_user.can?(:manage_taxonomies) } s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_path(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.can?(:manage_federation) } diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 6c2a37f3c2..aeca09acdc 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -34,6 +34,7 @@ namespace :admin do resources :warning_presets, except: [:new, :show] resources :media_attachments, only: [:index] resource :ng_words, only: [:show, :create] + resource :sensitive_words, only: [:show, :create] resources :announcements, except: [:show] do member do