Add option to not consider word boundaries when processing keyword filtering (#7975)
* Add option to not consider word boundaries when filtering phrases * Add a few tests for keyword/phrase filtering
This commit is contained in:
		
							parent
							
								
									451e585b97
								
							
						
					
					
						commit
						1ca4e51eb3
					
				
					 9 changed files with 61 additions and 11 deletions
				
			
		| 
						 | 
				
			
			@ -43,6 +43,6 @@ class Api::V1::FiltersController < Api::BaseController
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  def resource_params
 | 
			
		||||
    params.permit(:phrase, :expires_in, :irreversible, context: [])
 | 
			
		||||
    params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,10 @@ export const regexFromFilters = filters => {
 | 
			
		|||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return new RegExp(filters.map(filter => escapeRegExp(filter.get('phrase'))).map(expr => `\\b${expr}\\b`).join('|'), 'i');
 | 
			
		||||
  return new RegExp(filters.map(filter => {
 | 
			
		||||
    let expr = escapeRegExp(filter.get('phrase'));
 | 
			
		||||
    return filter.get('whole_word') ? `\\b${expr}\\b` : expr;
 | 
			
		||||
  }).join('|'), 'i');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const makeGetStatus = () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -200,7 +200,16 @@ class FeedManager
 | 
			
		|||
    active_filters = Rails.cache.fetch("filters:#{receiver_id}") { CustomFilter.where(account_id: receiver_id).active_irreversible.to_a }.to_a
 | 
			
		||||
 | 
			
		||||
    active_filters.select! { |filter| filter.context.include?(context.to_s) && !filter.expired? }
 | 
			
		||||
    active_filters.map! { |filter| Regexp.new("\\b#{Regexp.escape(filter.phrase)}\\b", true) }
 | 
			
		||||
    active_filters.map! do |filter|
 | 
			
		||||
      if filter.whole_word
 | 
			
		||||
        sb = filter.phrase =~ /\A[[:word:]]/ ? '\b' : ''
 | 
			
		||||
        eb = filter.phrase =~ /[[:word:]]\Z/ ? '\b' : ''
 | 
			
		||||
 | 
			
		||||
        /(?mix:#{sb}#{Regexp.escape(filter.phrase)}#{eb})/
 | 
			
		||||
      else
 | 
			
		||||
        /#{Regexp.escape(filter.phrase)}/i
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    return false if active_filters.empty?
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@
 | 
			
		|||
#  expires_at   :datetime
 | 
			
		||||
#  phrase       :text             default(""), not null
 | 
			
		||||
#  context      :string           default([]), not null, is an Array
 | 
			
		||||
#  whole_word   :boolean          default(TRUE), not null
 | 
			
		||||
#  irreversible :boolean          default(FALSE), not null
 | 
			
		||||
#  created_at   :datetime         not null
 | 
			
		||||
#  updated_at   :datetime         not null
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class REST::FilterSerializer < ActiveModel::Serializer
 | 
			
		||||
  attributes :id, :phrase, :context, :expires_at,
 | 
			
		||||
  attributes :id, :phrase, :context, :whole_word, :expires_at,
 | 
			
		||||
             :irreversible
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,5 +7,8 @@
 | 
			
		|||
.fields-group
 | 
			
		||||
  = f.input :irreversible, wrapper: :with_label
 | 
			
		||||
 | 
			
		||||
.fields-group
 | 
			
		||||
  = f.input :whole_word, wrapper: :with_label
 | 
			
		||||
 | 
			
		||||
.fields-group
 | 
			
		||||
  = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								db/migrate/20180707154237_add_whole_word_to_custom_filter.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								db/migrate/20180707154237_add_whole_word_to_custom_filter.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
 | 
			
		||||
 | 
			
		||||
class AddWholeWordToCustomFilter < ActiveRecord::Migration[5.2]
 | 
			
		||||
  include Mastodon::MigrationHelpers
 | 
			
		||||
 | 
			
		||||
  disable_ddl_transaction!
 | 
			
		||||
 | 
			
		||||
  def change
 | 
			
		||||
    safety_assured do
 | 
			
		||||
      add_column_with_default :custom_filters, :whole_word, :boolean, default: true, allow_null: false
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def down
 | 
			
		||||
    remove_column :custom_filters, :whole_word
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +10,7 @@
 | 
			
		|||
#
 | 
			
		||||
# It's strongly recommended that you check this file into your version control system.
 | 
			
		||||
 | 
			
		||||
ActiveRecord::Schema.define(version: 2018_06_28_181026) do
 | 
			
		||||
ActiveRecord::Schema.define(version: 2018_07_07_154237) do
 | 
			
		||||
 | 
			
		||||
  # These are extensions that must be enabled in order to support this database
 | 
			
		||||
  enable_extension "plpgsql"
 | 
			
		||||
| 
						 | 
				
			
			@ -149,6 +149,7 @@ ActiveRecord::Schema.define(version: 2018_06_28_181026) do
 | 
			
		|||
    t.text "phrase", default: "", null: false
 | 
			
		||||
    t.string "context", default: [], null: false, array: true
 | 
			
		||||
    t.boolean "irreversible", default: false, null: false
 | 
			
		||||
    t.boolean "whole_word", default: true, null: false
 | 
			
		||||
    t.datetime "created_at", null: false
 | 
			
		||||
    t.datetime "updated_at", null: false
 | 
			
		||||
    t.index ["account_id"], name: "index_custom_filters_on_account_id"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -127,12 +127,28 @@ RSpec.describe FeedManager do
 | 
			
		|||
        expect(FeedManager.instance.filter?(:home, reblog, alice.id)).to be true
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'returns true if status contains irreversibly muted phrase' do
 | 
			
		||||
        alice.custom_filters.create!(phrase: 'farts', context: %w(home public), irreversible: true)
 | 
			
		||||
        alice.custom_filters.create!(phrase: 'pop tarts', context: %w(home), irreversible: true)
 | 
			
		||||
        alice.follow!(jeff)
 | 
			
		||||
        status = Fabricate(:status, text: 'i sure like POP TARts', account: jeff)
 | 
			
		||||
        expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
 | 
			
		||||
      context 'for irreversibly muted phrases' do
 | 
			
		||||
        it 'considers word boundaries when matching' do
 | 
			
		||||
          alice.custom_filters.create!(phrase: 'bob', context: %w(home), irreversible: true)
 | 
			
		||||
          alice.follow!(jeff)
 | 
			
		||||
          status = Fabricate(:status, text: 'bobcats', account: jeff)
 | 
			
		||||
          expect(FeedManager.instance.filter?(:home, status, alice.id)).to be_falsy
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it 'returns true if phrase is contained' do
 | 
			
		||||
          alice.custom_filters.create!(phrase: 'farts', context: %w(home public), irreversible: true)
 | 
			
		||||
          alice.custom_filters.create!(phrase: 'pop tarts', context: %w(home), irreversible: true)
 | 
			
		||||
          alice.follow!(jeff)
 | 
			
		||||
          status = Fabricate(:status, text: 'i sure like POP TARts', account: jeff)
 | 
			
		||||
          expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it 'matches substrings if whole_word is false' do
 | 
			
		||||
          alice.custom_filters.create!(phrase: 'take', context: %w(home), whole_word: false, irreversible: true)
 | 
			
		||||
          alice.follow!(jeff)
 | 
			
		||||
          status = Fabricate(:status, text: 'shiitake', account: jeff)
 | 
			
		||||
          expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue