Add exclude_follows, exclude_localusers settings to custom_filter
This commit is contained in:
parent
fb9dbfc866
commit
5a0483ed21
15 changed files with 77 additions and 22 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, :whole_word, context: [])
|
params.permit(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :whole_word, context: [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
resource_params.slice(:phrase, :expires_in, :irreversible, :context)
|
resource_params.slice(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :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, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
params.permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, 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, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
params.require(:custom_filter).permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_body_classes
|
def set_body_classes
|
||||||
|
|
|
@ -265,7 +265,7 @@ module AccountInteractions
|
||||||
|
|
||||||
def status_matches_filters(status)
|
def status_matches_filters(status)
|
||||||
active_filters = CustomFilter.cached_filters_for(id)
|
active_filters = CustomFilter.cached_filters_for(id)
|
||||||
CustomFilter.apply_cached_filters(active_filters, status)
|
CustomFilter.apply_cached_filters(active_filters, status, following?(status.account))
|
||||||
end
|
end
|
||||||
|
|
||||||
def followers_for_local_distribution
|
def followers_for_local_distribution
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# action :integer default("warn"), not null
|
# action :integer default("warn"), not null
|
||||||
|
# exclude_follows :boolean default(FALSE), not null
|
||||||
|
# exclude_localusers :boolean default(FALSE), not null
|
||||||
#
|
#
|
||||||
|
|
||||||
class CustomFilter < ApplicationRecord
|
class CustomFilter < ApplicationRecord
|
||||||
|
@ -94,8 +96,11 @@ class CustomFilter < ApplicationRecord
|
||||||
active_filters.select { |custom_filter, _| !custom_filter.expired? }
|
active_filters.select { |custom_filter, _| !custom_filter.expired? }
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.apply_cached_filters(cached_filters, status)
|
def self.apply_cached_filters(cached_filters, status, following)
|
||||||
cached_filters.filter_map do |filter, rules|
|
cached_filters.filter_map do |filter, rules|
|
||||||
|
next if filter.exclude_follows && following
|
||||||
|
next if filter.exclude_localusers && status.account.local?
|
||||||
|
|
||||||
match = rules[:keywords].match(status.proper.searchable_text) if rules[:keywords].present?
|
match = rules[:keywords].match(status.proper.searchable_text) if rules[:keywords].present?
|
||||||
keyword_matches = [match.to_s] unless match.nil?
|
keyword_matches = [match.to_s] unless match.nil?
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
# id :bigint(8) not null, primary key
|
# id :bigint(8) not null, primary key
|
||||||
# custom_filter_id :bigint(8) not null
|
# custom_filter_id :bigint(8) not null
|
||||||
# keyword :text default(""), not null
|
# keyword :text default(""), not null
|
||||||
# whole_word :boolean default(TRUE), not null
|
# whole_word :boolean default(FALSE), not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
#
|
#
|
||||||
|
|
|
@ -7,6 +7,8 @@ class StatusRelationshipsPresenter
|
||||||
:bookmarks_map, :filters_map, :emoji_reactions_map
|
:bookmarks_map, :filters_map, :emoji_reactions_map
|
||||||
|
|
||||||
def initialize(statuses, current_account_id = nil, **options)
|
def initialize(statuses, current_account_id = nil, **options)
|
||||||
|
@current_account_id = current_account_id
|
||||||
|
|
||||||
if current_account_id.nil?
|
if current_account_id.nil?
|
||||||
@reblogs_map = {}
|
@reblogs_map = {}
|
||||||
@favourites_map = {}
|
@favourites_map = {}
|
||||||
|
@ -37,7 +39,7 @@ class StatusRelationshipsPresenter
|
||||||
active_filters = CustomFilter.cached_filters_for(current_account_id)
|
active_filters = CustomFilter.cached_filters_for(current_account_id)
|
||||||
|
|
||||||
@filters_map = statuses.each_with_object({}) do |status, h|
|
@filters_map = statuses.each_with_object({}) do |status, h|
|
||||||
filter_matches = CustomFilter.apply_cached_filters(active_filters, status)
|
filter_matches = CustomFilter.apply_cached_filters(active_filters, status, following?(status.account_id))
|
||||||
|
|
||||||
unless filter_matches.empty?
|
unless filter_matches.empty?
|
||||||
h[status.id] = filter_matches
|
h[status.id] = filter_matches
|
||||||
|
@ -45,4 +47,14 @@ class StatusRelationshipsPresenter
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def following?(other_account_id)
|
||||||
|
return false if @current_account_id.nil?
|
||||||
|
|
||||||
|
@account ||= Account.find(@current_account_id)
|
||||||
|
return false unless @account
|
||||||
|
|
||||||
|
@following_map ||= @account.following.pluck(:id)
|
||||||
|
@following_map.include?(other_account_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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, :context, :expires_at, :filter_action
|
attributes :id, :title, :exclude_follows, :exclude_localusers, :context, :expires_at, :filter_action
|
||||||
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?
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class REST::V1::FilterSerializer < ActiveModel::Serializer
|
class REST::V1::FilterSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :phrase, :context, :whole_word, :expires_at,
|
attributes :id, :phrase, :context, :whole_word, :expires_at,
|
||||||
:irreversible
|
:irreversible, :exclude_follows, :exclude_localusers
|
||||||
|
|
||||||
delegate :context, :expires_at, to: :custom_filter
|
delegate :context, :expires_at, to: :custom_filter
|
||||||
|
|
||||||
|
@ -18,6 +18,10 @@ class REST::V1::FilterSerializer < ActiveModel::Serializer
|
||||||
custom_filter.irreversible?
|
custom_filter.irreversible?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
delegate :exclude_follows, to: :custom_filter
|
||||||
|
|
||||||
|
delegate :exclude_localusers, to: :custom_filter
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def custom_filter
|
def custom_filter
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :filter_action, as: :radio_buttons, collection: %i(warn hide), include_blank: false, wrapper: :with_block_label, label_method: ->(action) { safe_join([t("simple_form.labels.filters.actions.#{action}"), content_tag(:span, t("simple_form.hints.filters.actions.#{action}"), class: 'hint')]) }, hint: t('simple_form.hints.filters.action'), required: true
|
= f.input :filter_action, as: :radio_buttons, collection: %i(warn hide), include_blank: false, wrapper: :with_block_label, label_method: ->(action) { safe_join([t("simple_form.labels.filters.actions.#{action}"), content_tag(:span, t("simple_form.hints.filters.actions.#{action}"), class: 'hint')]) }, hint: t('simple_form.hints.filters.action'), required: true
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :exclude_follows, wrapper: :with_label, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_follows')
|
||||||
|
= f.input :exclude_localusers, wrapper: :with_label, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_localusers')
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
- unless f.object.statuses.empty?
|
- unless f.object.statuses.empty?
|
||||||
|
|
|
@ -260,6 +260,9 @@ en:
|
||||||
actions:
|
actions:
|
||||||
hide: Hide completely
|
hide: Hide completely
|
||||||
warn: Hide with a warning
|
warn: Hide with a warning
|
||||||
|
options:
|
||||||
|
exclude_follows: Exclude following users
|
||||||
|
exclude_localusers: Exclude local users
|
||||||
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
|
||||||
backups_retention_period: User archive retention period
|
backups_retention_period: User archive retention period
|
||||||
|
|
|
@ -268,6 +268,9 @@ ja:
|
||||||
actions:
|
actions:
|
||||||
hide: 完全に隠す
|
hide: 完全に隠す
|
||||||
warn: 警告付きで隠す
|
warn: 警告付きで隠す
|
||||||
|
options:
|
||||||
|
exclude_follows: フォロー中のユーザーをフィルターの対象にしない
|
||||||
|
exclude_localusers: ローカルユーザーをフィルターの対象にしない
|
||||||
form_admin_settings:
|
form_admin_settings:
|
||||||
activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する
|
activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する
|
||||||
backups_retention_period: ユーザーアーカイブの保持期間
|
backups_retention_period: ユーザーアーカイブの保持期間
|
||||||
|
|
11
db/migrate/20230714004824_add_exclude_options_to_filters.rb
Normal file
11
db/migrate/20230714004824_add_exclude_options_to_filters.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddExcludeOptionsToFilters < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
safety_assured do
|
||||||
|
add_column :custom_filters, :exclude_follows, :boolean, null: false, default: false
|
||||||
|
add_column :custom_filters, :exclude_localusers, :boolean, null: false, default: false
|
||||||
|
change_column_default :custom_filter_keywords, :whole_word, from: true, to: false
|
||||||
|
end
|
||||||
|
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.define(version: 2023_07_06_031715) do
|
ActiveRecord::Schema.define(version: 2023_07_14_004824) 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"
|
||||||
|
@ -446,7 +446,7 @@ ActiveRecord::Schema.define(version: 2023_07_06_031715) do
|
||||||
create_table "custom_filter_keywords", force: :cascade do |t|
|
create_table "custom_filter_keywords", force: :cascade do |t|
|
||||||
t.bigint "custom_filter_id", null: false
|
t.bigint "custom_filter_id", null: false
|
||||||
t.text "keyword", default: "", null: false
|
t.text "keyword", default: "", null: false
|
||||||
t.boolean "whole_word", default: true, null: false
|
t.boolean "whole_word", default: false, null: false
|
||||||
t.datetime "created_at", precision: 6, null: false
|
t.datetime "created_at", precision: 6, null: false
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
t.index ["custom_filter_id"], name: "index_custom_filter_keywords_on_custom_filter_id"
|
t.index ["custom_filter_id"], name: "index_custom_filter_keywords_on_custom_filter_id"
|
||||||
|
@ -469,6 +469,8 @@ ActiveRecord::Schema.define(version: 2023_07_06_031715) do
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.integer "action", default: 0, null: false
|
t.integer "action", default: 0, null: false
|
||||||
|
t.boolean "exclude_follows", default: false, null: false
|
||||||
|
t.boolean "exclude_localusers", 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
|
||||||
|
|
||||||
|
@ -1228,6 +1230,7 @@ ActiveRecord::Schema.define(version: 2023_07_06_031715) do
|
||||||
t.index ["email"], name: "index_users_on_email", unique: true
|
t.index ["email"], name: "index_users_on_email", unique: true
|
||||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, opclass: :text_pattern_ops, where: "(reset_password_token IS NOT NULL)"
|
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, opclass: :text_pattern_ops, where: "(reset_password_token IS NOT NULL)"
|
||||||
t.index ["role_id"], name: "index_users_on_role_id", where: "(role_id IS NOT NULL)"
|
t.index ["role_id"], name: "index_users_on_role_id", where: "(role_id IS NOT NULL)"
|
||||||
|
t.index ["unconfirmed_email"], name: "index_users_on_unconfirmed_email", where: "(unconfirmed_email IS NOT NULL)"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "web_push_subscriptions", force: :cascade do |t|
|
create_table "web_push_subscriptions", force: :cascade do |t|
|
||||||
|
|
|
@ -671,7 +671,13 @@ const startServer = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!unpackedPayload.filtered && !req.cachedFilters) {
|
if (!unpackedPayload.filtered && !req.cachedFilters) {
|
||||||
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, keyword.keyword AS keyword, keyword.whole_word AS whole_word 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, 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 (!unpackedPayload.filtered) {
|
||||||
|
queries.push(client.query(`SELECT accounts.domain AS domain
|
||||||
|
FROM follows
|
||||||
|
JOIN accounts ON follows.target_account_id = accounts.id
|
||||||
|
WHERE (account_id = $1 AND target_account_id = $2)`, [req.accountId, unpackedPayload.account.id]));
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(queries).then(values => {
|
Promise.all(queries).then(values => {
|
||||||
|
@ -681,6 +687,8 @@ const startServer = async () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const following = !unpackedPayload.filtered && values[values.length - 1].rows.length > 0;
|
||||||
|
|
||||||
if (!unpackedPayload.filtered && !req.cachedFilters) {
|
if (!unpackedPayload.filtered && !req.cachedFilters) {
|
||||||
const filterRows = values[accountDomain ? 2 : 1].rows;
|
const filterRows = values[accountDomain ? 2 : 1].rows;
|
||||||
|
|
||||||
|
@ -697,6 +705,8 @@ const startServer = async () => {
|
||||||
context: row.context,
|
context: row.context,
|
||||||
expires_at: row.expires_at,
|
expires_at: row.expires_at,
|
||||||
filter_action: ['warn', 'hide'][row.filter_action],
|
filter_action: ['warn', 'hide'][row.filter_action],
|
||||||
|
excludeFollows: row.exclude_follows,
|
||||||
|
excludeLocalusers: row.exclude_localusers,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -732,7 +742,7 @@ const startServer = async () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
payload.filtered = [];
|
payload.filtered = [];
|
||||||
Object.values(req.cachedFilters).forEach((cachedFilter) => {
|
Object.values(req.cachedFilters).forEach((cachedFilter) => {
|
||||||
if ((cachedFilter.expires_at === null || cachedFilter.expires_at > now)) {
|
if ((cachedFilter.expires_at === null || cachedFilter.expires_at > now) && (!cachedFilter.repr.excludeFollows || !following) && (!cachedFilter.repr.excludeLocalusers || accountDomain)) {
|
||||||
const keyword_matches = searchIndex.match(cachedFilter.regexp);
|
const keyword_matches = searchIndex.match(cachedFilter.regexp);
|
||||||
if (keyword_matches) {
|
if (keyword_matches) {
|
||||||
payload.filtered.push({
|
payload.filtered.push({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue