* Add: #778 アカウント名・紹介文のフィルタリング * Fix test
This commit is contained in:
parent
d413e2d6e5
commit
8e59cd0992
13 changed files with 39 additions and 9 deletions
|
@ -52,11 +52,11 @@ class Api::V1::FiltersController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.permit(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_quote, :whole_word, context: [])
|
params.permit(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, :whole_word, context: [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
resource_params.slice(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_quote, :context)
|
resource_params.slice(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, :context)
|
||||||
end
|
end
|
||||||
|
|
||||||
def keyword_params
|
def keyword_params
|
||||||
|
|
|
@ -43,6 +43,6 @@ class Api::V2::FiltersController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :with_quote, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
params.permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,7 +49,7 @@ class FiltersController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.require(:custom_filter).permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :exclude_quote, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
params.require(:custom_filter).permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :exclude_quote, :exclude_profile, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_body_classes
|
def set_body_classes
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# exclude_follows :boolean default(FALSE), not null
|
# exclude_follows :boolean default(FALSE), not null
|
||||||
# exclude_localusers :boolean default(FALSE), not null
|
# exclude_localusers :boolean default(FALSE), not null
|
||||||
# with_quote :boolean default(TRUE), not null
|
# with_quote :boolean default(TRUE), not null
|
||||||
|
# with_profile :boolean default(FALSE), not null
|
||||||
#
|
#
|
||||||
|
|
||||||
class CustomFilter < ApplicationRecord
|
class CustomFilter < ApplicationRecord
|
||||||
|
@ -76,6 +77,14 @@ class CustomFilter < ApplicationRecord
|
||||||
!with_quote
|
!with_quote
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def exclude_profile=(value)
|
||||||
|
self.with_profile = !ActiveModel::Type::Boolean.new.cast(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_profile
|
||||||
|
!with_profile
|
||||||
|
end
|
||||||
|
|
||||||
def self.cached_filters_for(account_id)
|
def self.cached_filters_for(account_id)
|
||||||
active_filters = Rails.cache.fetch("filters:v3:#{account_id}") do
|
active_filters = Rails.cache.fetch("filters:v3:#{account_id}") do
|
||||||
filters_hash = {}
|
filters_hash = {}
|
||||||
|
@ -111,6 +120,7 @@ class CustomFilter < ApplicationRecord
|
||||||
|
|
||||||
if rules[:keywords].present?
|
if rules[:keywords].present?
|
||||||
match = rules[:keywords].match(status.proper.searchable_text)
|
match = rules[:keywords].match(status.proper.searchable_text)
|
||||||
|
match = rules[:keywords].match([status.account.display_name, status.account.note].join("\n\n")) if !match && filter.with_profile
|
||||||
if match.nil? && filter.with_quote && status.proper.reference_objects.exists?
|
if match.nil? && filter.with_quote && status.proper.reference_objects.exists?
|
||||||
references_text_cache = status.proper.references.pluck(:text).join("\n\n") if references_text_cache.nil?
|
references_text_cache = status.proper.references.pluck(:text).join("\n\n") if references_text_cache.nil?
|
||||||
references_spoiler_text_cache = status.proper.references.pluck(:spoiler_text).join("\n\n") if references_spoiler_text_cache.nil?
|
references_spoiler_text_cache = status.proper.references.pluck(:spoiler_text).join("\n\n") if references_spoiler_text_cache.nil?
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::FilterSerializer < ActiveModel::Serializer
|
class REST::FilterSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :title, :exclude_follows, :exclude_localusers, :with_quote, :context, :expires_at, :filter_action, :filter_action_ex
|
attributes :id, :title, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, :context, :expires_at, :filter_action, :filter_action_ex
|
||||||
has_many :keywords, serializer: REST::FilterKeywordSerializer, if: :rules_requested?
|
has_many :keywords, serializer: REST::FilterKeywordSerializer, if: :rules_requested?
|
||||||
has_many :statuses, serializer: REST::FilterStatusSerializer, if: :rules_requested?
|
has_many :statuses, serializer: REST::FilterStatusSerializer, if: :rules_requested?
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :exclude_quote, wrapper: :with_label, as: :boolean, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_quote')
|
= f.input :exclude_quote, wrapper: :with_label, as: :boolean, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_quote')
|
||||||
|
= f.input :exclude_profile, wrapper: :with_label, as: :boolean, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_profile')
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
|
|
|
@ -362,6 +362,7 @@ en:
|
||||||
options:
|
options:
|
||||||
exclude_follows: Exclude following users
|
exclude_follows: Exclude following users
|
||||||
exclude_localusers: Exclude local users
|
exclude_localusers: Exclude local users
|
||||||
|
exclude_profile: Exclude account name and bio
|
||||||
exclude_quote: Exclude quote or references
|
exclude_quote: Exclude quote or references
|
||||||
form_admin_settings:
|
form_admin_settings:
|
||||||
activity_api_enabled: Publish aggregate statistics about user activity in the API
|
activity_api_enabled: Publish aggregate statistics about user activity in the API
|
||||||
|
|
|
@ -358,6 +358,7 @@ ja:
|
||||||
options:
|
options:
|
||||||
exclude_follows: フォロー中のユーザーをフィルターの対象にしない
|
exclude_follows: フォロー中のユーザーをフィルターの対象にしない
|
||||||
exclude_localusers: ローカルユーザーをフィルターの対象にしない
|
exclude_localusers: ローカルユーザーをフィルターの対象にしない
|
||||||
|
exclude_profile: アカウント名および紹介文をフィルターの対象にしない
|
||||||
exclude_quote: 引用の内容をフィルターの対象にしない
|
exclude_quote: 引用の内容をフィルターの対象にしない
|
||||||
form_admin_settings:
|
form_admin_settings:
|
||||||
activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する
|
activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddWithProfileToCustomFilters < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
add_column :custom_filters, :with_profile, :boolean, default: false, null: false
|
||||||
|
end
|
||||||
|
end
|
|
@ -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_06_07_094856) do
|
ActiveRecord::Schema[7.1].define(version: 2024_07_09_063700) 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"
|
||||||
|
|
||||||
|
@ -539,6 +539,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_07_094856) do
|
||||||
t.boolean "exclude_follows", default: false, null: false
|
t.boolean "exclude_follows", default: false, null: false
|
||||||
t.boolean "exclude_localusers", default: false, null: false
|
t.boolean "exclude_localusers", default: false, null: false
|
||||||
t.boolean "with_quote", default: true, null: false
|
t.boolean "with_quote", default: true, null: false
|
||||||
|
t.boolean "with_profile", default: false, null: false
|
||||||
t.index ["account_id"], name: "index_custom_filters_on_account_id"
|
t.index ["account_id"], name: "index_custom_filters_on_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace :dangerous do
|
||||||
end
|
end
|
||||||
|
|
||||||
target_migrations = %w(
|
target_migrations = %w(
|
||||||
|
20240709063700
|
||||||
20240426233435
|
20240426233435
|
||||||
20240426000034
|
20240426000034
|
||||||
20240401222541
|
20240401222541
|
||||||
|
@ -149,6 +150,7 @@ namespace :dangerous do
|
||||||
%w(custom_emojis license),
|
%w(custom_emojis license),
|
||||||
%w(custom_filters exclude_follows),
|
%w(custom_filters exclude_follows),
|
||||||
%w(custom_filters exclude_localusers),
|
%w(custom_filters exclude_localusers),
|
||||||
|
%w(custom_filters with_profile),
|
||||||
%w(custom_filters with_quote),
|
%w(custom_filters with_quote),
|
||||||
%w(domain_blocks block_trends),
|
%w(domain_blocks block_trends),
|
||||||
%w(domain_blocks detect_invalid_subscription),
|
%w(domain_blocks detect_invalid_subscription),
|
||||||
|
|
|
@ -54,6 +54,7 @@ RSpec.describe 'Filters' do
|
||||||
filter_action: 'hide',
|
filter_action: 'hide',
|
||||||
exclude_follows: true,
|
exclude_follows: true,
|
||||||
exclude_localusers: true,
|
exclude_localusers: true,
|
||||||
|
with_profile: true,
|
||||||
keywords_attributes: [keyword: 'magic', whole_word: true],
|
keywords_attributes: [keyword: 'magic', whole_word: true],
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -75,6 +76,7 @@ RSpec.describe 'Filters' do
|
||||||
expect(json[:keywords].map { |keyword| keyword.slice(:keyword, :whole_word) }).to eq [{ keyword: 'magic', whole_word: true }]
|
expect(json[:keywords].map { |keyword| keyword.slice(:keyword, :whole_word) }).to eq [{ keyword: 'magic', whole_word: true }]
|
||||||
expect(json[:exclude_follows]).to be true
|
expect(json[:exclude_follows]).to be true
|
||||||
expect(json[:exclude_localusers]).to be true
|
expect(json[:exclude_localusers]).to be true
|
||||||
|
expect(json[:with_profile]).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a filter', :aggregate_failures do
|
it 'creates a filter', :aggregate_failures do
|
||||||
|
@ -87,6 +89,7 @@ RSpec.describe 'Filters' do
|
||||||
expect(filter.context).to eq %w(home)
|
expect(filter.context).to eq %w(home)
|
||||||
expect(filter.exclude_follows).to be true
|
expect(filter.exclude_follows).to be true
|
||||||
expect(filter.exclude_localusers).to be true
|
expect(filter.exclude_localusers).to be true
|
||||||
|
expect(filter.with_profile).to be true
|
||||||
expect(filter.irreversible?).to be true
|
expect(filter.irreversible?).to be true
|
||||||
expect(filter.expires_at).to be_nil
|
expect(filter.expires_at).to be_nil
|
||||||
end
|
end
|
||||||
|
@ -175,7 +178,7 @@ RSpec.describe 'Filters' do
|
||||||
|
|
||||||
context 'when updating filter parameters' do
|
context 'when updating filter parameters' do
|
||||||
context 'with valid params' do
|
context 'with valid params' do
|
||||||
let(:params) { { title: 'updated', context: %w(home public), exclude_follows: true, exclude_localusers: true } }
|
let(:params) { { title: 'updated', context: %w(home public), exclude_follows: true, exclude_localusers: true, exclude_profile: true } }
|
||||||
|
|
||||||
it 'updates the filter successfully', :aggregate_failures do
|
it 'updates the filter successfully', :aggregate_failures do
|
||||||
subject
|
subject
|
||||||
|
@ -187,6 +190,7 @@ RSpec.describe 'Filters' do
|
||||||
expect(filter.reload.context).to eq %w(home public)
|
expect(filter.reload.context).to eq %w(home public)
|
||||||
expect(filter.exclude_follows).to be true
|
expect(filter.exclude_follows).to be true
|
||||||
expect(filter.exclude_localusers).to be true
|
expect(filter.exclude_localusers).to be true
|
||||||
|
expect(filter.exclude_profile).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -934,7 +934,7 @@ const startServer = async () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (!payload.filtered && !req.cachedFilters) {
|
if (!payload.filtered && !req.cachedFilters) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
queries.push(client.query('SELECT filter.id AS id, filter.phrase AS title, filter.context AS context, filter.expires_at AS expires_at, filter.action AS filter_action, filter.with_quote AS with_quote, keyword.keyword AS keyword, keyword.whole_word AS whole_word, filter.exclude_follows AS exclude_follows, filter.exclude_localusers AS exclude_localusers FROM custom_filter_keywords keyword JOIN custom_filters filter ON keyword.custom_filter_id = filter.id WHERE filter.account_id = $1 AND (filter.expires_at IS NULL OR filter.expires_at > NOW())', [req.accountId]));
|
queries.push(client.query('SELECT filter.id AS id, filter.phrase AS title, filter.context AS context, filter.expires_at AS expires_at, filter.action AS filter_action, filter.with_quote AS with_quote, filter.with_profile AS with_profile, keyword.keyword AS keyword, keyword.whole_word AS whole_word, filter.exclude_follows AS exclude_follows, filter.exclude_localusers AS exclude_localusers FROM custom_filter_keywords keyword JOIN custom_filters filter ON keyword.custom_filter_id = filter.id WHERE filter.account_id = $1 AND (filter.expires_at IS NULL OR filter.expires_at > NOW())', [req.accountId]));
|
||||||
}
|
}
|
||||||
if (!payload.filtered) {
|
if (!payload.filtered) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -989,6 +989,7 @@ const startServer = async () => {
|
||||||
filter_action: filter.filter_action === 2 ? 'warn' : ['warn', 'hide', 'half_warn'][filter.filter_action],
|
filter_action: filter.filter_action === 2 ? 'warn' : ['warn', 'hide', 'half_warn'][filter.filter_action],
|
||||||
filter_action_ex: ['warn', 'hide', 'half_warn'][filter.filter_action],
|
filter_action_ex: ['warn', 'hide', 'half_warn'][filter.filter_action],
|
||||||
with_quote: filter.with_quote,
|
with_quote: filter.with_quote,
|
||||||
|
withAccountName: filter.with_profile,
|
||||||
excludeFollows: filter.exclude_follows,
|
excludeFollows: filter.exclude_follows,
|
||||||
excludeLocalusers: filter.exclude_localusers,
|
excludeLocalusers: filter.exclude_localusers,
|
||||||
},
|
},
|
||||||
|
@ -1032,6 +1033,7 @@ const startServer = async () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const searchableContent = ([status.spoiler_text || '', status.content, ...(reference_texts || [])].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
const searchableContent = ([status.spoiler_text || '', status.content, ...(reference_texts || [])].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
||||||
const searchableTextContent = JSDOM.fragment(searchableContent).textContent;
|
const searchableTextContent = JSDOM.fragment(searchableContent).textContent;
|
||||||
|
const searchableAccountContent = JSDOM.fragment([status.account.display_name, status.account.note].join('\n\n')).textContent;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -1054,7 +1056,8 @@ const startServer = async () => {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyword_matches = searchableTextContent.match(cachedFilter.regexp);
|
const keyword_matches = searchableTextContent.match(cachedFilter.regexp) ||
|
||||||
|
((cachedFilter.withAccountName && searchableAccountContent) ? searchableAccountContent.match(cachedFilter.regexp) : null);
|
||||||
if (keyword_matches) {
|
if (keyword_matches) {
|
||||||
// results is an Array of FilterResult; status_matches is always
|
// results is an Array of FilterResult; status_matches is always
|
||||||
// null as we only are only applying the keyword-based custom
|
// null as we only are only applying the keyword-based custom
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue