Skip to content

Commit

Permalink
Add: #545 NGワード指定で実際に除外された投稿のログ
Browse files Browse the repository at this point in the history
  • Loading branch information
kmycode committed Feb 16, 2024
1 parent 0ca2a73 commit 0f42269
Show file tree
Hide file tree
Showing 19 changed files with 262 additions and 17 deletions.
19 changes: 19 additions & 0 deletions app/controllers/admin/ngword_histories_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Admin
class NgwordHistoriesController < BaseController
before_action :set_histories

PER_PAGE = 20

def index
authorize :ng_words, :show?
end

private

def set_histories
@histories = NgwordHistory.order(id: :desc).page(params[:page]).per(PER_PAGE)
end
end
end
4 changes: 2 additions & 2 deletions app/lib/activitypub/activity/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ def process_status_params
end

def valid_status?
valid = !Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}") && !Admin::NgWord.hashtag_reject?(@tags.size)
valid = !Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}", uri: @params[:uri], target_type: :status, public: @status_parser.distributable_visibility?) && !Admin::NgWord.hashtag_reject?(@tags.size)

valid = !Admin::NgWord.stranger_mention_reject?("#{@params[:spoiler_text]}\n#{@params[:text]}") if valid && mention_to_local_but_not_followed?
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_but_not_followed?

valid
end
Expand Down
4 changes: 4 additions & 0 deletions app/lib/activitypub/parser/status_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ def visibility
end
end

def distributable_visibility?
%i(public public_unlisted unlisted login).include?(visibility)
end

def searchability
from_audience = searchability_from_audience
return from_audience if from_audience
Expand Down
29 changes: 23 additions & 6 deletions app/models/admin/ng_word.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

class Admin::NgWord
class << self
def reject?(text)
ng_words.any? { |word| include?(text, word) }
def reject?(text, **options)
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?
end

def stranger_mention_reject?(text, **options)
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?
end

def reject_with_custom_words?(text, custom_ng_words)
Expand All @@ -18,10 +26,6 @@ def hashtag_reject_with_extractor?(text)
hashtag_reject?(Extractor.extract_hashtags(text)&.size || 0)
end

def stranger_mention_reject?(text)
ng_words_for_stranger_mention.any? { |word| include?(text, word) }
end

private

def include?(text, word)
Expand All @@ -44,5 +48,18 @@ def post_hash_tags_max
value = Setting.post_hash_tags_max
value.is_a?(Integer) && value.positive? ? value : 0
end

def record!(type, text, keyword, options)
return unless options[:uri] && options[:target_type]
return if options.key?(:public) && !options.delete(:public)

return if NgwordHistory.where('created_at > ?', 1.day.ago).exists?(uri: options[:uri], keyword: options[:keyword])

NgwordHistory.create(options.merge({
reason: type,
text: text,
keyword: keyword,
}))
end
end
end
21 changes: 21 additions & 0 deletions app/models/ngword_history.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

# == Schema Information
#
# Table name: ngword_histories
#
# id :bigint(8) not null, primary key
# uri :string not null
# target_type :integer not null
# reason :integer not null
# text :string not null
# keyword :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
class NgwordHistory < ApplicationRecord
include Paginable

enum target_type: { status: 0, account_note: 1, account_name: 2 }, _suffix: :blocked
enum reason: { ng_words: 0, ng_words_for_stranger_mention: 1 }, _prefix: :within
end
2 changes: 0 additions & 2 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
# sign_in_count :integer default(0), not null
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# admin :boolean default(FALSE), not null
# confirmation_token :string
# confirmed_at :datetime
# confirmation_sent_at :datetime
Expand All @@ -29,7 +28,6 @@
# otp_backup_codes :string is an Array
# account_id :bigint(8) not null
# disabled :boolean default(FALSE), not null
# moderator :boolean default(FALSE), not null
# invite_id :bigint(8)
# chosen_languages :string is an Array
# created_by_application_id :bigint(8)
Expand Down
3 changes: 2 additions & 1 deletion app/services/activitypub/process_account_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ def set_immediate_attributes!
def valid_account?
display_name = @json['name'] || ''
note = @json['summary'] || ''
!Admin::NgWord.reject?(display_name) && !Admin::NgWord.reject?(note)
!Admin::NgWord.reject?(display_name, uri: @uri, target_type: :account_name) &&
!Admin::NgWord.reject?(note, uri: @uri, target_type: :account_note)
end

def set_fetchable_key!
Expand Down
4 changes: 2 additions & 2 deletions app/services/activitypub/process_status_update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ def update_poll!(allow_significant_changes: true)
end

def valid_status?
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@raw_tags.size)
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}", uri: @status.uri, target_type: :status) && !Admin::NgWord.hashtag_reject?(@raw_tags.size)
end

def validate_status_mentions!
raise AbortError if mention_to_stranger? && Admin::NgWord.stranger_mention_reject?("#{@status.spoiler_text}\n#{@status.text}")
raise AbortError if mention_to_stranger? && Admin::NgWord.stranger_mention_reject?("#{@status.spoiler_text}\n#{@status.text}", uri: @status.uri, target_type: :status)
end

def mention_to_stranger?
Expand Down
8 changes: 6 additions & 2 deletions app/views/admin/ng_words/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@
= simple_form_for @admin_settings, url: admin_ng_words_path, html: { method: :post } do |f|
= render 'shared/error_messages', object: @admin_settings

%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: 12 }, label: t('admin.ng_words.keywords_for_stranger_mention'), hint: t('admin.ng_words.keywords_for_stranger_mention_hint')
= 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 :stranger_mention_from_local_ng, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.stranger_mention_from_local_ng'), hint: t('admin.ng_words.stranger_mention_from_local_ng_hint')

.fields-group
= f.input :ng_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.ng_words.keywords'), hint: t('admin.ng_words.keywords_hint')
= 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')
Expand Down
30 changes: 30 additions & 0 deletions app/views/admin/ngword_histories/_history.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.batch-table__row
%label.batch-table__row__select.batch-checkbox
-# = f.check_box :history_ids, { multiple: true, include_hidden: false }, history.id
.batch-table__row__content
.status__content><
= html_aware_format(history.text, false)

.detailed-status__meta
%span.negative-hint= history.keyword
·
- if history.within_ng_words?
= t('admin.ng_words.keywords')
- elsif history.within_ng_words_for_stranger_mention?
= t('admin.ng_words.keywords_for_stranger_mention')

%br/

%time.formatted{ datetime: history.created_at.iso8601, title: l(history.created_at) }= l(history.created_at)
·
- if history.account_note_blocked?
= t('admin.ngword_history.target_types.account_note')
- elsif history.account_name_blocked?
= t('admin.ngword_history.target_types.account_name')
- elsif history.status_blocked?
= t('admin.ngword_history.target_types.status')
·
= history.uri
-# if history.application
= history.application.name
·
22 changes: 22 additions & 0 deletions app/views/admin/ngword_histories/index.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
- content_for :page_title do
= t('admin.ngword_histories.title')

.filters
.back-link
= link_to admin_ng_words_path do
= fa_icon 'chevron-left fw'
= t('admin.ngword_histories.back_to_ng_words')

%hr.spacer/

.batch-table
.batch-table__toolbar
%label.batch-table__toolbar__select.batch-checkbox-all
= check_box_tag :batch_checkbox_all, nil, false
.batch-table__body
- if @histories.empty?
= nothing_here 'nothing-here--under-tabs'
- else
= render partial: 'admin/ngword_histories/history', collection: @histories

= paginate @histories
8 changes: 8 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ en:
title: Media attachments
ng_words:
hide_local_users_for_anonymous: Hide timeline local user posts from anonymous
history_hint: We recommend that you regularly check your NG words to make sure that you have not specified the NG words incorrectly.
keywords: Reject keywords
keywords_for_stranger_mention: Reject keywords when mention/reply from strangers
keywords_for_stranger_mention_hint: Currently this words are checked posts from other servers only.
Expand All @@ -651,6 +652,13 @@ en:
stranger_mention_from_local_ng_hint: サーバーの登録が承認制でない場合、あなたのサーバーにもスパムが入り込む可能性があります
test_error: Testing is returned any errors
title: NG words and against spams
ngword_histories:
back_to_ng_words: NG words and against spams
target_types:
account_name: Account name
account_note: Account note
status: Post
title: NG words history
relationships:
title: "%{acct}'s relationships"
relays:
Expand Down
8 changes: 8 additions & 0 deletions config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,7 @@ ja:
title: 投稿された画像
ng_words:
hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする
history_hint: 設定されたNGワードによって実際に拒否された投稿などは、履歴より確認できます。NGワードの指定に誤りがないか定期的に確認することをおすすめします。
keywords: 投稿できないキーワード
keywords_for_stranger_mention: フォローしていないアカウントへのメンションで利用できないキーワード
keywords_for_stranger_mention_hint: フォローしていないアカウントへのメンションにのみ適用されます。現状は外部サーバーから来た投稿のみに適用されます
Expand All @@ -644,6 +645,13 @@ ja:
stranger_mention_from_local_ng_hint: サーバーの登録が承認制でない場合、あなたのサーバーにもスパムが入り込む可能性があります
test_error: NGワードのテストに失敗しました。正規表現のミスが含まれているかもしれません
title: NGワードとスパム
ngword_histories:
back_to_ng_words: NGワードとスパム
target_types:
account_name: アカウントの名前
account_note: アカウントの説明文
status: 投稿
title: NGワード検出履歴
relationships:
title: "%{acct} さんのフォロー・フォロワー"
relays:
Expand Down
2 changes: 1 addition & 1 deletion config/navigation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
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}, 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|ngword_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) }
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) }
Expand Down
1 change: 1 addition & 0 deletions config/routes/admin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
resources :action_logs, only: [:index]
resources :warning_presets, except: [:new, :show]
resource :ng_words, only: [:show, :create]
resources :ngword_histories, only: [:index]
resource :sensitive_words, only: [:show, :create]
resource :special_instances, only: [:show, :create]

Expand Down
17 changes: 17 additions & 0 deletions db/migrate/20240216042730_create_ngword_histories.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

class CreateNgwordHistories < ActiveRecord::Migration[7.1]
def change
create_table :ngword_histories do |t|
t.string :uri, null: false
t.integer :target_type, null: false
t.integer :reason, null: false
t.string :text, null: false
t.string :keyword, null: false

t.timestamps
end

add_index :ngword_histories, [:uri, :keyword, :created_at], unique: false
end
end
13 changes: 12 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2024_02_12_230358) do
ActiveRecord::Schema[7.1].define(version: 2024_02_16_042730) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

Expand Down Expand Up @@ -868,6 +868,17 @@
t.index ["target_account_id"], name: "index_mutes_on_target_account_id"
end

create_table "ngword_histories", force: :cascade do |t|
t.string "uri", null: false
t.integer "target_type", null: false
t.integer "reason", null: false
t.string "text", null: false
t.string "keyword", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["uri", "keyword", "created_at"], name: "index_ngword_histories_on_uri_and_keyword_and_created_at"
end

create_table "notifications", force: :cascade do |t|
t.bigint "activity_id", null: false
t.string "activity_type", null: false
Expand Down
Loading

0 comments on commit 0f42269

Please sign in to comment.