You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
2.9 KiB

  1. # frozen_string_literal: true
  2. # == Schema Information
  3. #
  4. # Table name: glitch_keyword_mutes
  5. #
  6. # id :bigint(8) not null, primary key
  7. # account_id :bigint(8) not null
  8. # keyword :string not null
  9. # whole_word :boolean default(TRUE), not null
  10. # created_at :datetime not null
  11. # updated_at :datetime not null
  12. # apply_to_mentions :boolean default(TRUE), not null
  13. #
  14. class Glitch::KeywordMute < ApplicationRecord
  15. belongs_to :account, required: true
  16. validates_presence_of :keyword
  17. after_commit :invalidate_cached_matchers
  18. module Scopes
  19. Unscoped = 0b00
  20. HomeFeed = 0b01
  21. Mentions = 0b10
  22. end
  23. def self.text_matcher_for(account_id)
  24. TextMatcher.new(account_id)
  25. end
  26. def self.tag_matcher_for(account_id)
  27. TagMatcher.new(account_id)
  28. end
  29. def scope
  30. s = Scopes::Unscoped
  31. s |= Scopes::HomeFeed
  32. s |= Scopes::Mentions if apply_to_mentions?
  33. s
  34. end
  35. private
  36. def invalidate_cached_matchers
  37. Rails.cache.delete(TextMatcher.cache_key(account_id))
  38. Rails.cache.delete(TagMatcher.cache_key(account_id))
  39. end
  40. class CachedKeywordMute
  41. attr_reader :keyword
  42. attr_reader :whole_word
  43. attr_reader :scope
  44. def initialize(keyword, whole_word, scope)
  45. @keyword = keyword
  46. @whole_word = whole_word
  47. @scope = scope
  48. end
  49. def boundary_regex_for_keyword
  50. sb = keyword =~ /\A[[:word:]]/ ? '\b' : ''
  51. eb = keyword =~ /[[:word:]]\Z/ ? '\b' : ''
  52. /(?mix:#{sb}#{Regexp.escape(keyword)}#{eb})/
  53. end
  54. def matches?(str, required_scope)
  55. ((required_scope & scope) == required_scope) && \
  56. str =~ (whole_word ? boundary_regex_for_keyword : /#{Regexp.escape(keyword)}/i)
  57. end
  58. end
  59. class Matcher
  60. attr_reader :account_id
  61. attr_reader :keywords
  62. def initialize(account_id)
  63. @account_id = account_id
  64. @keywords = Rails.cache.fetch(self.class.cache_key(account_id)) { fetch_keywords }
  65. end
  66. protected
  67. def fetch_keywords
  68. Glitch::KeywordMute.select(:whole_word, :keyword, :apply_to_mentions)
  69. .where(account_id: account_id)
  70. .map { |kw| CachedKeywordMute.new(transform_keyword(kw.keyword), kw.whole_word, kw.scope) }
  71. end
  72. def transform_keyword(keyword)
  73. keyword
  74. end
  75. end
  76. class TextMatcher < Matcher
  77. def self.cache_key(account_id)
  78. format('keyword_mutes:regex:text:%s', account_id)
  79. end
  80. def matches?(str, scope)
  81. keywords.any? { |kw| kw.matches?(str, scope) }
  82. end
  83. end
  84. class TagMatcher < Matcher
  85. def self.cache_key(account_id)
  86. format('keyword_mutes:regex:tag:%s', account_id)
  87. end
  88. def matches?(tags, scope)
  89. tags.pluck(:name).any? do |n|
  90. keywords.any? { |kw| kw.matches?(n, scope) }
  91. end
  92. end
  93. protected
  94. def transform_keyword(kw)
  95. Tag::HASHTAG_RE =~ kw ? $1 : kw
  96. end
  97. end
  98. end