* Change: #648 センシティブワードの入力フォーム * Wip: 行の追加削除 * Wip: 設定の保存、マイグレーション * 不要な処理を削除 * マイグレーションコード調整
This commit is contained in:
parent
f509bd4fc3
commit
ed246f0d03
17 changed files with 377 additions and 61 deletions
|
@ -29,3 +29,4 @@ linters:
|
||||||
exclude:
|
exclude:
|
||||||
- 'app/views/application/_sidebar.html.haml'
|
- 'app/views/application/_sidebar.html.haml'
|
||||||
- 'app/views/admin/ng_rules/_ng_rule_fields.html.haml'
|
- 'app/views/admin/ng_rules/_ng_rule_fields.html.haml'
|
||||||
|
- 'app/views/admin/sensitive_words/_sensitive_word.html.haml'
|
||||||
|
|
|
@ -6,6 +6,7 @@ module Admin
|
||||||
authorize :sensitive_words, :show?
|
authorize :sensitive_words, :show?
|
||||||
|
|
||||||
@admin_settings = Form::AdminSettings.new
|
@admin_settings = Form::AdminSettings.new
|
||||||
|
@sensitive_words = ::SensitiveWord.caches.presence || [::SensitiveWord.new]
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -21,7 +22,7 @@ module Admin
|
||||||
|
|
||||||
@admin_settings = Form::AdminSettings.new(settings_params)
|
@admin_settings = Form::AdminSettings.new(settings_params)
|
||||||
|
|
||||||
if @admin_settings.save
|
if @admin_settings.save && ::SensitiveWord.save_from_raws(settings_params_test)
|
||||||
flash[:notice] = I18n.t('generic.changes_saved_msg')
|
flash[:notice] = I18n.t('generic.changes_saved_msg')
|
||||||
redirect_to after_update_redirect_path
|
redirect_to after_update_redirect_path
|
||||||
else
|
else
|
||||||
|
@ -32,11 +33,8 @@ module Admin
|
||||||
private
|
private
|
||||||
|
|
||||||
def test_words
|
def test_words
|
||||||
sensitive_words = settings_params['sensitive_words'].split(/\r\n|\r|\n/)
|
sensitive_words = settings_params_test['keywords'].compact.uniq
|
||||||
sensitive_words_for_full = settings_params['sensitive_words_for_full'].split(/\r\n|\r|\n/)
|
Admin::NgWord.reject_with_custom_words?('Sample text', sensitive_words)
|
||||||
sensitive_words_all = settings_params['sensitive_words_all'].split(/\r\n|\r|\n/)
|
|
||||||
sensitive_words_all_for_full = settings_params['sensitive_words_all_for_full'].split(/\r\n|\r|\n/)
|
|
||||||
Admin::NgWord.reject_with_custom_words?('Sample text', sensitive_words + sensitive_words_for_full + sensitive_words_all + sensitive_words_all_for_full)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_update_redirect_path
|
def after_update_redirect_path
|
||||||
|
@ -46,5 +44,9 @@ module Admin
|
||||||
def settings_params
|
def settings_params
|
||||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def settings_params_test
|
||||||
|
params.require(:form_admin_settings)[:sensitive_words_test]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -272,6 +272,68 @@ Rails.delegate(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const addTableRow = (tableId: string) => {
|
||||||
|
const templateElement = document.querySelector(`#${tableId} .template-row`)!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const tableElement = document.querySelector(`#${tableId} tbody`)!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof templateElement === 'undefined' ||
|
||||||
|
typeof tableElement === 'undefined'
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let temporaryId = 0;
|
||||||
|
tableElement
|
||||||
|
.querySelectorAll<HTMLInputElement>('.temporary_id')
|
||||||
|
.forEach((input) => {
|
||||||
|
if (parseInt(input.value) + 1 > temporaryId) {
|
||||||
|
temporaryId = parseInt(input.value) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cloned = templateElement.cloneNode(true) as HTMLTableRowElement;
|
||||||
|
cloned.className = '';
|
||||||
|
cloned.querySelector<HTMLInputElement>('.temporary_id')!.value = // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
||||||
|
temporaryId.toString();
|
||||||
|
cloned
|
||||||
|
.querySelectorAll<HTMLInputElement>('input[type=checkbox]')
|
||||||
|
.forEach((input) => {
|
||||||
|
input.value = temporaryId.toString();
|
||||||
|
});
|
||||||
|
tableElement.appendChild(cloned);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeTableRow = (target: EventTarget | null, tableId: string) => {
|
||||||
|
const tableRowElement = (target as HTMLElement).closest('tr') as Node;
|
||||||
|
const tableElement = document.querySelector(`#${tableId} tbody`)!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof tableRowElement === 'undefined' ||
|
||||||
|
typeof tableElement === 'undefined'
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tableElement.removeChild(tableRowElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
Rails.delegate(
|
||||||
|
document,
|
||||||
|
'#sensitive-words-table .add-row-button',
|
||||||
|
'click',
|
||||||
|
() => {
|
||||||
|
addTableRow('sensitive-words-table');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Rails.delegate(
|
||||||
|
document,
|
||||||
|
'#sensitive-words-table .delete-row-button',
|
||||||
|
'click',
|
||||||
|
({ target }) => {
|
||||||
|
removeTableRow(target, 'sensitive-words-table');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
async function mountReactComponent(element: Element) {
|
async function mountReactComponent(element: Element) {
|
||||||
const componentName = element.getAttribute('data-admin-component');
|
const componentName = element.getAttribute('data-admin-component');
|
||||||
const stringProps = element.getAttribute('data-props');
|
const stringProps = element.getAttribute('data-props');
|
||||||
|
|
|
@ -1135,6 +1135,10 @@ code {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.template-row {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-tracker {
|
.progress-tracker {
|
||||||
|
|
|
@ -5,12 +5,12 @@ class Admin::SensitiveWord
|
||||||
def sensitive?(text, spoiler_text, local: true)
|
def sensitive?(text, spoiler_text, local: true)
|
||||||
exposure_text = spoiler_text.presence || text
|
exposure_text = spoiler_text.presence || text
|
||||||
|
|
||||||
sensitive = (spoiler_text.blank? && sensitive_words_all.any? { |word| include?(text, word) }) ||
|
sensitive_words = ::SensitiveWord.caches
|
||||||
sensitive_words_all_for_full.any? { |word| include?(exposure_text, word) }
|
sensitive_words.select!(&:remote) unless local
|
||||||
return sensitive if sensitive || !local
|
|
||||||
|
|
||||||
(spoiler_text.blank? && sensitive_words.any? { |word| include?(text, word) }) ||
|
return sensitive_words.filter(&:spoiler).any? { |word| include?(spoiler_text, word) } if spoiler_text.present?
|
||||||
sensitive_words_for_full.any? { |word| include?(exposure_text, word) }
|
|
||||||
|
sensitive_words.any? { |word| include?(exposure_text, word) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def modified_text(text, spoiler_text)
|
def modified_text(text, spoiler_text)
|
||||||
|
@ -24,27 +24,11 @@ class Admin::SensitiveWord
|
||||||
private
|
private
|
||||||
|
|
||||||
def include?(text, word)
|
def include?(text, word)
|
||||||
if word.start_with?('?') && word.size >= 2
|
if word.regexp
|
||||||
text =~ /#{word[1..]}/i
|
text =~ /#{word.keyword}/
|
||||||
else
|
else
|
||||||
text.include?(word)
|
text.include?(word.keyword)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def sensitive_words
|
|
||||||
Setting.sensitive_words || []
|
|
||||||
end
|
|
||||||
|
|
||||||
def sensitive_words_for_full
|
|
||||||
Setting.sensitive_words_for_full || []
|
|
||||||
end
|
|
||||||
|
|
||||||
def sensitive_words_all
|
|
||||||
Setting.sensitive_words_all || []
|
|
||||||
end
|
|
||||||
|
|
||||||
def sensitive_words_all_for_full
|
|
||||||
Setting.sensitive_words_all_for_full || []
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,10 +51,6 @@ class Form::AdminSettings
|
||||||
post_hash_tags_max
|
post_hash_tags_max
|
||||||
post_mentions_max
|
post_mentions_max
|
||||||
post_stranger_mentions_max
|
post_stranger_mentions_max
|
||||||
sensitive_words
|
|
||||||
sensitive_words_for_full
|
|
||||||
sensitive_words_all
|
|
||||||
sensitive_words_all_for_full
|
|
||||||
auto_warning_text
|
auto_warning_text
|
||||||
authorized_fetch
|
authorized_fetch
|
||||||
receive_other_servers_emoji_reaction
|
receive_other_servers_emoji_reaction
|
||||||
|
@ -128,10 +124,6 @@ class Form::AdminSettings
|
||||||
STRING_ARRAY_KEYS = %i(
|
STRING_ARRAY_KEYS = %i(
|
||||||
ng_words
|
ng_words
|
||||||
ng_words_for_stranger_mention
|
ng_words_for_stranger_mention
|
||||||
sensitive_words
|
|
||||||
sensitive_words_for_full
|
|
||||||
sensitive_words_all
|
|
||||||
sensitive_words_all_for_full
|
|
||||||
emoji_reaction_disallow_domains
|
emoji_reaction_disallow_domains
|
||||||
permit_new_account_domains
|
permit_new_account_domains
|
||||||
).freeze
|
).freeze
|
||||||
|
|
81
app/models/sensitive_word.rb
Normal file
81
app/models/sensitive_word.rb
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: sensitive_words
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# keyword :string not null
|
||||||
|
# regexp :boolean default(FALSE), not null
|
||||||
|
# remote :boolean default(FALSE), not null
|
||||||
|
# spoiler :boolean default(TRUE), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
|
||||||
|
class SensitiveWord < ApplicationRecord
|
||||||
|
attr_accessor :keywords, :regexps, :remotes, :spoilers
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def caches
|
||||||
|
Rails.cache.fetch('sensitive_words') { SensitiveWord.where.not(id: 0).order(:keyword).to_a }
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_from_hashes(rows)
|
||||||
|
unmatched = caches
|
||||||
|
matched = []
|
||||||
|
|
||||||
|
SensitiveWord.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.remote == item[:remote] && exists.spoiler == item[:spoiler]
|
||||||
|
|
||||||
|
exists.update!(regexp: item[:regexp], remote: item[:remote], spoiler: item[:spoiler])
|
||||||
|
elsif matched.none? { |i| i.keyword == item[:keyword] }
|
||||||
|
SensitiveWord.create!(
|
||||||
|
keyword: item[:keyword],
|
||||||
|
regexp: item[:regexp],
|
||||||
|
remote: item[:remote],
|
||||||
|
spoiler: item[:spoiler]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SensitiveWord.destroy(unmatched.map(&:id))
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
# rescue
|
||||||
|
# false
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_from_raws(rows)
|
||||||
|
regexps = rows['regexps'] || []
|
||||||
|
remotes = rows['remotes'] || []
|
||||||
|
spoilers = rows['spoilers'] || []
|
||||||
|
|
||||||
|
hashes = (rows['keywords'] || []).zip(rows['temporary_ids'] || []).map do |item|
|
||||||
|
temp_id = item[1]
|
||||||
|
{
|
||||||
|
keyword: item[0],
|
||||||
|
regexp: regexps.include?(temp_id),
|
||||||
|
remote: remotes.include?(temp_id),
|
||||||
|
spoiler: spoilers.include?(temp_id),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
save_from_hashes(hashes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def invalidate_cache!
|
||||||
|
Rails.cache.delete('sensitive_words')
|
||||||
|
end
|
||||||
|
end
|
12
app/views/admin/sensitive_words/_sensitive_word.html.haml
Normal file
12
app/views/admin/sensitive_words/_sensitive_word.html.haml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
- 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: sensitive_word.keyword }
|
||||||
|
%td
|
||||||
|
.label_input__wrapper= f.check_box :regexps, { multiple: true, checked: sensitive_word.regexp }, temporary_id, nil
|
||||||
|
%td
|
||||||
|
.label_input__wrapper= f.check_box :remotes, { multiple: true, checked: sensitive_word.remote }, temporary_id, nil
|
||||||
|
%td
|
||||||
|
.label_input__wrapper= f.check_box :spoilers, { multiple: true, checked: sensitive_word.spoiler }, temporary_id, nil
|
||||||
|
%td
|
||||||
|
= hidden_field_tag :'form_admin_settings[sensitive_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'
|
|
@ -9,17 +9,31 @@
|
||||||
|
|
||||||
%p.lead= t 'admin.sensitive_words.hint'
|
%p.lead= t 'admin.sensitive_words.hint'
|
||||||
|
|
||||||
.fields-group
|
%p= t 'admin.sensitive_words.phrases.regexp_html'
|
||||||
= f.input :sensitive_words_for_full, wrapper: :with_label, as: :text, input_html: { rows: 8 }, label: t('admin.sensitive_words.keywords_for_all'), hint: t('admin.sensitive_words.keywords_for_all_hint')
|
%p= t 'admin.sensitive_words.phrases.remote_html'
|
||||||
|
%p= t 'admin.sensitive_words.phrases.spoiler_html'
|
||||||
|
|
||||||
.fields-group
|
%hr/
|
||||||
= f.input :sensitive_words, wrapper: :with_label, as: :text, input_html: { rows: 8 }, label: t('admin.sensitive_words.keywords'), hint: t('admin.sensitive_words.keywords_hint')
|
|
||||||
|
|
||||||
.fields-group
|
.table-wrapper
|
||||||
= f.input :sensitive_words_all_for_full, wrapper: :with_label, as: :text, input_html: { rows: 8 }, label: t('admin.sensitive_words.keywords_all_for_all'), hint: t('admin.sensitive_words.keywords_for_all_hint')
|
%table.table.keywords-table#sensitive-words-table
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th= t('simple_form.labels.defaults.phrase')
|
||||||
|
%th= t('admin.sensitive_words.phrases.regexp_short')
|
||||||
|
%th= t('admin.sensitive_words.phrases.remote_short')
|
||||||
|
%th= t('admin.sensitive_words.phrases.spoiler_short')
|
||||||
|
%th
|
||||||
|
%tbody
|
||||||
|
= f.simple_fields_for :sensitive_words_test, @sensitive_words do |keyword|
|
||||||
|
= render partial: 'sensitive_word', collection: @sensitive_words, locals: { f: keyword, template: false }
|
||||||
|
|
||||||
.fields-group
|
= f.simple_fields_for :sensitive_words_test, @sensitive_words do |keyword|
|
||||||
= f.input :sensitive_words_all, wrapper: :with_label, as: :text, input_html: { rows: 8 }, label: t('admin.sensitive_words.keywords_all'), hint: t('admin.sensitive_words.keywords_hint')
|
= render partial: 'sensitive_word', collection: [SensitiveWord.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'
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :auto_warning_text, wrapper: :with_label, input_html: { placeholder: t('admin.sensitive_words.alert') }, label: t('admin.sensitive_words.auto_warning_text'), hint: t('admin.sensitive_words.auto_warning_text_hint')
|
= f.input :auto_warning_text, wrapper: :with_label, input_html: { placeholder: t('admin.sensitive_words.alert') }, label: t('admin.sensitive_words.auto_warning_text'), hint: t('admin.sensitive_words.auto_warning_text_hint')
|
||||||
|
|
|
@ -963,11 +963,13 @@ en:
|
||||||
auto_warning_text: Custom warning text
|
auto_warning_text: Custom warning text
|
||||||
auto_warning_text_hint: If not specified, the default warning text is used.
|
auto_warning_text_hint: If not specified, the default warning text is used.
|
||||||
hint: This keywords is applied to public posts only..
|
hint: This keywords is applied to public posts only..
|
||||||
keywords: Sensitive keywords for local only
|
phrases:
|
||||||
keywords_all: Sensitive keywords for local+remote
|
remote_html: <strong>Rem</strong> ote にチェックの入っている項目は、リモートからの投稿にも適用されます。
|
||||||
keywords_all_for_all: Sensitive keywords for local+remote (Contains CW alert)
|
remote_short: Rem
|
||||||
keywords_for_all: Sensitive keywords for local only (Contains CW alert)
|
regexp_html: <strong>Reg</strong> Exp にチェックの入っている項目は、正規表現を用いての比較となります。
|
||||||
keywords_for_all_hint: The first character of the line is "?". to use regular expressions
|
regexp_short: Reg
|
||||||
|
spoiler_html: <strong>War</strong> ning にチェックの入っている項目は、コンテンツ警告文にも適用されます。
|
||||||
|
spoiler_short: War
|
||||||
title: Sensitive words and moderation options
|
title: Sensitive words and moderation options
|
||||||
settings:
|
settings:
|
||||||
about:
|
about:
|
||||||
|
|
|
@ -959,12 +959,13 @@ ja:
|
||||||
auto_warning_text: カスタム警告文
|
auto_warning_text: カスタム警告文
|
||||||
auto_warning_text_hint: 指定しなかった場合は、各言語のデフォルト警告文が使用されます
|
auto_warning_text_hint: 指定しなかった場合は、各言語のデフォルト警告文が使用されます
|
||||||
hint: センシティブなキーワードの設定は、当サーバーのローカルユーザーによる公開範囲「公開」「ローカル公開」「ログインユーザーのみ」に対して適用されます。
|
hint: センシティブなキーワードの設定は、当サーバーのローカルユーザーによる公開範囲「公開」「ローカル公開」「ログインユーザーのみ」に対して適用されます。
|
||||||
keywords: ローカルの投稿に適用するセンシティブなキーワード(警告文は除外)
|
phrases:
|
||||||
keywords_all: ローカル・リモートの投稿に適用するセンシティブなキーワード(警告文は除外)
|
remote_html: <strong>リモ</strong> ート にチェックの入っている項目は、リモートからの投稿にも適用されます。
|
||||||
keywords_all_for_all: ローカル・リモートの投稿に適用するセンシティブなキーワード(警告文にも適用)
|
remote_short: リモ
|
||||||
keywords_for_all: ローカルの投稿に適用するセンシティブなキーワード(警告文にも適用)
|
regexp_html: <strong>正規</strong> 表現 にチェックの入っている項目は、正規表現を用いての比較となります。
|
||||||
keywords_for_all_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。警告文にも含まれていればCWになります。行が「?」で始まっていれば正規表現が使えます
|
regexp_short: 正規
|
||||||
keywords_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。ただし警告文に使用していた場合は無視されます
|
spoiler_html: <strong>警告</strong> 文 にチェックの入っている項目は、コンテンツ警告文にも適用されます。
|
||||||
|
spoiler_short: 警告
|
||||||
title: センシティブ単語と設定
|
title: センシティブ単語と設定
|
||||||
settings:
|
settings:
|
||||||
about:
|
about:
|
||||||
|
|
71
db/migrate/20240312230204_create_sensitive_words.rb
Normal file
71
db/migrate/20240312230204_create_sensitive_words.rb
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateSensitiveWords < 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 SensitiveWord < 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 :sensitive_words do |t|
|
||||||
|
t.string :keyword, null: false
|
||||||
|
t.boolean :regexp, null: false, default: false
|
||||||
|
t.boolean :remote, null: false, default: false
|
||||||
|
t.boolean :spoiler, null: false, default: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
settings = Setting.where(var: %i(sensitive_words sensitive_words_for_full sensitive_words_all sensitive_words_all_for_full))
|
||||||
|
sensitive_words = settings.find { |s| s.var == 'sensitive_words' }&.value&.compact_blank&.uniq || []
|
||||||
|
sensitive_words_for_full = settings.find { |s| s.var == 'sensitive_words_for_full' }&.value&.compact_blank&.uniq || []
|
||||||
|
sensitive_words_all = settings.find { |s| s.var == 'sensitive_words_all' }&.value&.compact_blank&.uniq || []
|
||||||
|
sensitive_words_all_for_full = settings.find { |s| s.var == 'sensitive_words_all_for_full' }&.value&.compact_blank&.uniq || []
|
||||||
|
|
||||||
|
(sensitive_words + sensitive_words_for_full + sensitive_words_all + sensitive_words_all_for_full).compact.uniq.each do |word|
|
||||||
|
SensitiveWord.create!(
|
||||||
|
keyword: normalized_keyword(word),
|
||||||
|
regexp: regexp?(word),
|
||||||
|
remote: (sensitive_words_all + sensitive_words_all_for_full).include?(word),
|
||||||
|
spoiler: (sensitive_words_for_full + sensitive_words_all_for_full).include?(word)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
settings.destroy_all
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
sensitive_words = SensitiveWord.where(remote: false, spoiler: false).map { |s| s.regexp ? "?#{s.keyword}" : s.keyword }
|
||||||
|
sensitive_words_for_full = SensitiveWord.where(remote: false, spoiler: true).map { |s| s.regexp ? "?#{s.keyword}" : s.keyword }
|
||||||
|
sensitive_words_all = SensitiveWord.where(remote: true, spoiler: false).map { |s| s.regexp ? "?#{s.keyword}" : s.keyword }
|
||||||
|
sensitive_words_all_for_full = SensitiveWord.where(remote: true, spoiler: true).map { |s| s.regexp ? "?#{s.keyword}" : s.keyword }
|
||||||
|
|
||||||
|
Setting.where(var: %i(sensitive_words sensitive_words_for_full sensitive_words_all sensitive_words_all_for_full)).destroy_all
|
||||||
|
|
||||||
|
Setting.new(var: :sensitive_words).tap { |s| s.value = sensitive_words }.save!
|
||||||
|
Setting.new(var: :sensitive_words_for_full).tap { |s| s.value = sensitive_words_for_full }.save!
|
||||||
|
Setting.new(var: :sensitive_words_all).tap { |s| s.value = sensitive_words_all }.save!
|
||||||
|
Setting.new(var: :sensitive_words_all_for_full).tap { |s| s.value = sensitive_words_all_for_full }.save!
|
||||||
|
|
||||||
|
drop_table :sensitive_words
|
||||||
|
end
|
||||||
|
end
|
11
db/schema.rb
11
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2024_03_10_123453) do
|
ActiveRecord::Schema[7.1].define(version: 2024_03_12_230204) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
|
@ -1233,6 +1233,15 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_10_123453) do
|
||||||
t.index ["scheduled_at"], name: "index_scheduled_statuses_on_scheduled_at"
|
t.index ["scheduled_at"], name: "index_scheduled_statuses_on_scheduled_at"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "sensitive_words", force: :cascade do |t|
|
||||||
|
t.string "keyword", null: false
|
||||||
|
t.boolean "regexp", default: false, null: false
|
||||||
|
t.boolean "remote", default: false, null: false
|
||||||
|
t.boolean "spoiler", default: true, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
end
|
||||||
|
|
||||||
create_table "session_activations", force: :cascade do |t|
|
create_table "session_activations", force: :cascade do |t|
|
||||||
t.string "session_id", null: false
|
t.string "session_id", null: false
|
||||||
t.datetime "created_at", precision: nil, null: false
|
t.datetime "created_at", precision: nil, null: false
|
||||||
|
|
5
spec/fabricators/sensitive_word_fabricator.rb
Normal file
5
spec/fabricators/sensitive_word_fabricator.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
Fabricator(:sensitive_word) do
|
||||||
|
keyword { sequence(:keyword) { |i| "keyword_#{i}" } }
|
||||||
|
end
|
|
@ -2071,7 +2071,8 @@ RSpec.describe ActivityPub::Activity::Create do
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Form::AdminSettings.new(sensitive_words_all: sensitive_words_all, sensitive_words: 'ipsum').save
|
Fabricate(:sensitive_word, keyword: sensitive_words_all, remote: true, spoiler: false) if sensitive_words_all.present?
|
||||||
|
Fabricate(:sensitive_word, keyword: 'ipsum')
|
||||||
subject.perform
|
subject.perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
75
spec/models/admin/sensitive_word_spec.rb
Normal file
75
spec/models/admin/sensitive_word_spec.rb
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Admin::SensitiveWord do
|
||||||
|
describe '#sensitive?' do
|
||||||
|
subject { described_class.sensitive?(text, spoiler_text, local: local) }
|
||||||
|
|
||||||
|
let(:text) { 'This is a ohagi.' }
|
||||||
|
let(:spoiler_text) { '' }
|
||||||
|
let(:local) { true }
|
||||||
|
|
||||||
|
context 'when a local post' do
|
||||||
|
it 'local word hits' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'ohagi', remote: false)
|
||||||
|
expect(subject).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'remote word hits' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'ohagi', remote: true)
|
||||||
|
expect(subject).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a remote post' do
|
||||||
|
let(:local) { false }
|
||||||
|
|
||||||
|
it 'local word does not hit' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'ohagi', remote: false)
|
||||||
|
expect(subject).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'remote word hits' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'ohagi', remote: true)
|
||||||
|
expect(subject).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using regexp' do
|
||||||
|
it 'regexp hits with enable' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'oha[ghi]i', regexp: true)
|
||||||
|
expect(subject).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'regexp does not hit without enable' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'oha[ghi]i', regexp: false)
|
||||||
|
expect(subject).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when spoiler text is set' do
|
||||||
|
let(:spoiler_text) { 'amy' }
|
||||||
|
|
||||||
|
it 'sensitive word in content is escaped' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'ohagi', spoiler: false)
|
||||||
|
expect(subject).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sensitive word in content is escaped even if spoiler is true' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'ohagi', spoiler: true)
|
||||||
|
expect(subject).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'non-spoiler word does not hit' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'amy', spoiler: false)
|
||||||
|
expect(subject).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'spoiler word hits' do
|
||||||
|
Fabricate(:sensitive_word, keyword: 'amy', spoiler: true)
|
||||||
|
expect(subject).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -758,7 +758,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
|
||||||
let(:content) { 'ng word aiueo' }
|
let(:content) { 'ng word aiueo' }
|
||||||
|
|
||||||
it 'update status' do
|
it 'update status' do
|
||||||
Form::AdminSettings.new(sensitive_words_all: 'test').save
|
Fabricate(:sensitive_word, keyword: 'test', remote: true, spoiler: false)
|
||||||
|
|
||||||
subject.call(status, json, json)
|
subject.call(status, json, json)
|
||||||
expect(status.reload.text).to eq content
|
expect(status.reload.text).to eq content
|
||||||
|
@ -770,7 +770,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
|
||||||
let(:content) { 'ng word test' }
|
let(:content) { 'ng word test' }
|
||||||
|
|
||||||
it 'update status' do
|
it 'update status' do
|
||||||
Form::AdminSettings.new(sensitive_words_all: 'test').save
|
Fabricate(:sensitive_word, keyword: 'test', remote: true, spoiler: false)
|
||||||
|
|
||||||
subject.call(status, json, json)
|
subject.call(status, json, json)
|
||||||
expect(status.reload.text).to eq content
|
expect(status.reload.text).to eq content
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue