about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/models/glitch/keyword_mute.rb36
-rw-r--r--spec/models/glitch/keyword_mute_spec.rb12
2 files changed, 34 insertions, 14 deletions
diff --git a/app/models/glitch/keyword_mute.rb b/app/models/glitch/keyword_mute.rb
index 823e252d3..20fd89d9b 100644
--- a/app/models/glitch/keyword_mute.rb
+++ b/app/models/glitch/keyword_mute.rb
@@ -19,35 +19,49 @@ class Glitch::KeywordMute < ApplicationRecord
   after_commit :invalidate_cached_matcher
 
   def self.matcher_for(account_id)
-    Rails.cache.fetch("keyword_mutes:matcher:#{account_id}") { Matcher.new(account_id) }
+    Matcher.new(account_id)
   end
 
   private
 
   def invalidate_cached_matcher
-    Rails.cache.delete("keyword_mutes:matcher:#{account_id}")
+    Rails.cache.delete("keyword_mutes:regex:#{account_id}")
   end
 
   class Matcher
+    attr_reader :account_id
     attr_reader :regex
 
     def initialize(account_id)
-      re = [].tap do |arr|
-        Glitch::KeywordMute.where(account_id: account_id).select(:keyword, :id, :whole_word).find_each do |m|
-          boundary = m.whole_word ? '\b' : ''
-          arr << "#{boundary}#{Regexp.escape(m.keyword.strip)}#{boundary}"
+      @account_id = account_id
+      @regex = Rails.cache.fetch("keyword_mutes:regex:#{account_id}") { regex_for_account }
+    end
+
+    def keywords
+      Glitch::KeywordMute.
+        where(account_id: account_id).
+        select(:keyword, :id, :whole_word)
+    end
+
+    def regex_for_account
+      re_text = [].tap do |arr|
+        keywords.find_each do |kw|
+          arr << (kw.whole_word ? boundary_regex_for_keyword(kw.keyword) : Regexp.escape(kw.keyword))
         end
       end.join('|')
 
-      @regex = /#{re}/i unless re.empty?
+      /#{re_text}/i unless re_text.empty?
     end
 
-    def =~(str)
-      regex ? regex =~ str : false
+    def boundary_regex_for_keyword(keyword)
+      sb = keyword =~ /\A[[:word:]]/ ? '\b' : ''
+      eb = keyword =~ /[[:word:]]\Z/ ? '\b' : ''
+
+      "#{sb}#{Regexp.escape(keyword)}#{eb}"
     end
 
-    def matches?(str)
-      !!(regex =~ str)
+    def =~(str)
+      regex ? regex =~ str : false
     end
   end
 end
diff --git a/spec/models/glitch/keyword_mute_spec.rb b/spec/models/glitch/keyword_mute_spec.rb
index 108cdafec..95e59defc 100644
--- a/spec/models/glitch/keyword_mute_spec.rb
+++ b/spec/models/glitch/keyword_mute_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Glitch::KeywordMute, type: :model do
   describe '.matcher_for' do
     let(:matcher) { Glitch::KeywordMute.matcher_for(alice) }
 
-    describe 'with no Glitch::KeywordMutes for an account' do
+    describe 'with no mutes' do
       before do
         Glitch::KeywordMute.delete_all
       end
@@ -17,7 +17,7 @@ RSpec.describe Glitch::KeywordMute, type: :model do
       end
     end
 
-    describe 'with Glitch::KeywordMutes for an account' do
+    describe 'with mutes' do
       it 'does not match keywords set by a different account' do
         Glitch::KeywordMute.create!(account: bob, keyword: 'take')
 
@@ -63,7 +63,13 @@ RSpec.describe Glitch::KeywordMute, type: :model do
       it 'matches keywords surrounded by non-alphanumeric ornamentation' do
         Glitch::KeywordMute.create!(account: alice, keyword: 'hot')
 
-        expect(matcher =~ 'This is a ~*HOT*~ take').to be_truthy
+        expect(matcher =~ '(hot take)').to be_truthy
+      end
+
+      it 'escapes metacharacters in keywords' do
+        Glitch::KeywordMute.create!(account: alice, keyword: '(hot take)')
+
+        expect(matcher =~ '(hot take)').to be_truthy
       end
 
       it 'uses case-folding rules appropriate for more than just English' do