# frozen_string_literal: true class TextSentinel attr_accessor :text ENTROPY_SCALE = 0.7 def initialize(text, opts = nil) @opts = opts || {} @text = text.to_s.encode("UTF-8", invalid: :replace, undef: :replace, replace: "") end def self.body_sentinel(text, opts = {}) entropy = SiteSetting.body_min_entropy if opts[:private_message] scale_entropy = SiteSetting.min_personal_message_post_length.to_f / SiteSetting.min_post_length.to_f entropy = (entropy * scale_entropy).to_i entropy = (SiteSetting.min_personal_message_post_length.to_f * ENTROPY_SCALE).to_i if entropy > SiteSetting.min_personal_message_post_length else entropy = (SiteSetting.min_post_length.to_f * ENTROPY_SCALE).to_i if entropy > SiteSetting.min_post_length end TextSentinel.new(text, min_entropy: entropy) end def self.title_sentinel(text) entropy = if SiteSetting.min_topic_title_length > SiteSetting.title_min_entropy SiteSetting.title_min_entropy else (SiteSetting.min_topic_title_length.to_f * ENTROPY_SCALE).to_i end TextSentinel.new(text, min_entropy: entropy, max_word_length: SiteSetting.title_max_word_length) end # Number of unique bytes def entropy @entropy ||= @text.strip.bytes.uniq.size end def valid? @text.present? && seems_meaningful? && seems_pronounceable? && seems_unpretentious? && seems_quiet? end # Ensure minumum entropy def seems_meaningful? @opts[:min_entropy].nil? || entropy >= @opts[:min_entropy] end # At least one non-symbol character def seems_pronounceable? @text.match?(/\p{Alnum}/) end # Ensure maximum word length def seems_unpretentious? skipped_locales.include?(SiteSetting.default_locale) || @opts[:max_word_length].nil? || !@text.match?(/\p{Alnum}{#{@opts[:max_word_length] + 1},}/) end # Ensure at least one lowercase letter def seems_quiet? SiteSetting.allow_uppercase_posts || @text.match?(/\p{Lowercase_Letter}|\p{Other_Letter}/) || !@text.match?(/\p{Letter}/) end private # Hard to tell "word length" for CJK languages def skipped_locales @skipped_locales ||= %w[ja ko zh_CN zh_TW].freeze end end