From 8312a6e51017498c417070cbcbf115da184b786d Mon Sep 17 00:00:00 2001 From: multiple creatures Date: Sat, 21 Dec 2019 13:22:18 -0600 Subject: add option to use phrase filters as an allow list --- app/controllers/settings/preferences_controller.rb | 3 +++ app/lib/status_filter.rb | 9 ++++++--- app/models/status.rb | 8 +++++++- app/models/user.rb | 1 + app/views/settings/preferences/show.html.haml | 1 + config/locales/simple_form.en.yml | 1 + db/migrate/20191221183232_add_invert_filters_to_user.rb | 5 +++++ db/schema.rb | 3 ++- streaming/index.js | 5 +++-- 9 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20191221183232_add_invert_filters_to_user.rb diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 66e9033d3..4ac654590 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Settings::PreferencesController < Settings::BaseController + include Redisable + layout 'admin' before_action :authenticate_user! @@ -29,6 +31,7 @@ class Settings::PreferencesController < Settings::BaseController :locale, :hide_boosts, :only_known, + :invert_filters, chosen_languages: [] ) end diff --git a/app/lib/status_filter.rb b/app/lib/status_filter.rb index 81faf129f..fb675cbbd 100644 --- a/app/lib/status_filter.rb +++ b/app/lib/status_filter.rb @@ -15,7 +15,7 @@ class StatusFilter def filtered? return true if status.nil? || account.nil? return false if !account.nil? && account.id == status.account_id - return true if redis.sismember("filtered_statuses:#{account.id}", status.id) + return !account.user.invert_filters if redis.sismember("filtered_statuses:#{account.id}", status.id) if blocked_by_policy? || (account_present? && filtered_status?) || silenced_account? redis.sadd("filtered_statuses:#{account.id}", status.id) return true @@ -40,7 +40,7 @@ class StatusFilter return true if account.user_hides_replies_of_blocker? && reply_to_blocker? # filtered by user? - return true if phrase_filtered?(status, account.id) + return true if !account.user.invert_filters && phrase_filtered?(status, account.id) # kajiht has no filters if status has no mentions return false if status&.mentions.blank? @@ -74,7 +74,10 @@ class StatusFilter return true if !@preloaded_relations[:muting] && account.user_hides_mentions_of_muted? && account.muting?(mentioned_account_ids) return true if !@preloaded_relations[:blocking] && account.user_hides_mentions_of_blocked? && account.blocking?(mentioned_account_ids) return false unless status.reply? && status.private_visibility? && account.user_hides_mentions_outside_scope? - !@preloaded_relations[:following] && (mentioned_account_ids - account.following_ids).any? + return true if !@preloaded_relations[:following] && (mentioned_account_ids - account.following_ids).any? + + # filtered by user? + account.user.invert_filters && !phrase_filtered?(status, account.id) end def reply_to_blocked? diff --git a/app/models/status.rb b/app/models/status.rb index 8a1680ccf..a47f846a5 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -580,7 +580,13 @@ class Status < ApplicationRecord query = query.in_chosen_languages(account) if account.chosen_languages.present? query = query.reply_not_excluded_by_account(account) unless tag_timeline query = query.mention_not_excluded_by_account(account) - query = query.regex_not_filtered_by_account(account.id) if account.custom_filters.present? + unless account.custom_filters.nil? + if account.user.invert_filters + query = query.regex_filtered_by_account(account.id) + else + query = query.regex_not_filtered_by_account(account.id) + end + end query = query.not_missing_media_desc if account.filter_undescribed? query.merge(account_silencing_filter(account)) end diff --git a/app/models/user.rb b/app/models/user.rb index 59067cb36..f6e1a369d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -41,6 +41,7 @@ # vars :jsonb not null # hide_boosts :boolean # only_known :boolean +# invert_filters :boolean default(FALSE), not null # class User < ApplicationRecord diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index 81bfc7345..8f4563fde 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -46,6 +46,7 @@ %hr#settings_other/ .fields-group + = f.input :invert_filters, as: :boolean, wrapper: :with_label = f.input :setting_rawr_federated, as: :boolean, wrapper: :with_label = f.input :hide_boosts, as: :boolean, wrapper: :with_label = f.input :only_known, as: :boolean, wrapper: :with_label diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 6983d0867..cb106082d 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -182,6 +182,7 @@ en: setting_unfollow_modal: Show confirmation dialog before unfollowing someone hide_boosts: Don't show boosts on any timeline only_known: Only show posts from packmates on all timelines + invert_filters: Use allow list mode for filters severity: Severity type: Import type username: Username diff --git a/db/migrate/20191221183232_add_invert_filters_to_user.rb b/db/migrate/20191221183232_add_invert_filters_to_user.rb new file mode 100644 index 000000000..d30b865f1 --- /dev/null +++ b/db/migrate/20191221183232_add_invert_filters_to_user.rb @@ -0,0 +1,5 @@ +class AddInvertFiltersToUser < ActiveRecord::Migration[5.2] + def change + add_column :users, :invert_filters, :boolean, null: false, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index f6f1ba209..631469a2a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_12_21_164844) do +ActiveRecord::Schema.define(version: 2019_12_21_183232) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -810,6 +810,7 @@ ActiveRecord::Schema.define(version: 2019_12_21_164844) do t.jsonb "vars", default: {}, null: false t.boolean "hide_boosts" t.boolean "only_known" + t.boolean "invert_filters", default: false, null: false t.index ["account_id"], name: "index_users_on_account_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id" diff --git a/streaming/index.js b/streaming/index.js index 20ec00d9f..db421ff03 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -202,7 +202,7 @@ const startWorker = (workerId) => { return; } - client.query('SELECT oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, users.hide_boosts, users.only_known, oauth_access_tokens.scopes FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token], (err, result) => { + client.query('SELECT oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, users.hide_boosts, users.only_known, users.invert_filters, oauth_access_tokens.scopes FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token], (err, result) => { done(); if (err) { @@ -232,6 +232,7 @@ const startWorker = (workerId) => { req.chosenLanguages = result.rows[0].chosen_languages; req.hideBoosts = result.rows[0].hide_boosts; req.onlyKnown = result.rows[0].only_known; + req.invertFilters = result.rows[0].invert_filters; req.allowNotifications = scopes.some(scope => ['read', 'read:notifications'].includes(scope)); next(); @@ -423,7 +424,7 @@ const startWorker = (workerId) => { } const queries = [ - client.query(`SELECT 1 FROM blocks WHERE (account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 3)})) OR (account_id = $2 AND target_account_id = $1) UNION SELECT 1 FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 3)}) UNION SELECT 1 FROM normalized_statuses WHERE status_id = $3 AND text ~ ANY(ARRAY(SELECT f_normalize(phrase) FROM custom_filters WHERE account_id = $1)) UNION SELECT 1 FROM media_attachments WHERE (1 = (SELECT 1 FROM accounts WHERE id = $1 AND filter_undescribed)) AND status_id = $3 AND description IS NULL LIMIT 1`, [req.accountId, unpackedPayload.account.id, unpackedPayload.id].concat(targetAccountIds)), + client.query(`SELECT 1 FROM blocks WHERE (account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 3)})) OR (account_id = $2 AND target_account_id = $1) UNION SELECT 1 FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 3)}) UNION SELECT 1 FROM normalized_statuses WHERE status_id = $3 AND text ${req.invertFilters ? '!~' : '~'} ANY(ARRAY(SELECT f_normalize(phrase) FROM custom_filters WHERE account_id = $1)) UNION SELECT 1 FROM media_attachments WHERE (1 = (SELECT 1 FROM accounts WHERE id = $1 AND filter_undescribed)) AND status_id = $3 AND description IS NULL LIMIT 1`, [req.accountId, unpackedPayload.account.id, unpackedPayload.id].concat(targetAccountIds)), ]; if (accountDomain) { -- cgit