moderate 1.0.0.beta2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +96 -1
  3. data/.simplecov +2 -10
  4. data/AGENTS.md +1 -1
  5. data/Appraisals +10 -8
  6. data/CHANGELOG.md +654 -52
  7. data/CLAUDE.md +1 -1
  8. data/README.md +147 -7
  9. data/Rakefile +2 -0
  10. data/app/controllers/concerns/moderate/moderation.rb +23 -11
  11. data/app/controllers/moderate/appeals_controller.rb +9 -5
  12. data/app/controllers/moderate/application_controller.rb +3 -3
  13. data/app/controllers/moderate/notices_controller.rb +3 -6
  14. data/app/helpers/moderate/engine_helper.rb +32 -11
  15. data/app/views/moderate/appeals/new.html.erb +6 -0
  16. data/app/views/moderate/transparency_reports/_summary_card.html.erb +2 -2
  17. data/app/views/moderate/transparency_reports/show.html.erb +5 -5
  18. data/config/locales/en.yml +200 -0
  19. data/config/locales/es.yml +177 -0
  20. data/db/migrate/20261018200000_add_pending_key_to_moderate_flags.rb +139 -0
  21. data/db/migrate/20261018210000_add_retention_and_classifications_to_moderate_tables.rb +103 -0
  22. data/docs/chats.md +140 -0
  23. data/docs/compliance.md +92 -3
  24. data/docs/configuration.md +30 -2
  25. data/docs/dsa-notice-form.md +1 -1
  26. data/docs/madmin.md +44 -14
  27. data/docs/notifications.md +17 -4
  28. data/examples/aws_rekognition_adapter.rb +6 -6
  29. data/examples/openai_moderation_adapter.rb +1 -1
  30. data/examples/regexp_pattern_adapter.rb +68 -0
  31. data/gemfiles/{rails_7.1.gemfile → rails_8.0.gemfile} +8 -6
  32. data/gemfiles/rails_8.1.gemfile +6 -5
  33. data/lib/generators/moderate/install_generator.rb +25 -0
  34. data/lib/generators/moderate/templates/create_moderate_tables.rb.erb +17 -2
  35. data/lib/generators/moderate/templates/initializer.rb +9 -15
  36. data/lib/generators/moderate/views_generator.rb +3 -3
  37. data/lib/moderate/attachments.rb +137 -0
  38. data/lib/moderate/configuration.rb +87 -12
  39. data/lib/moderate/engine.rb +86 -25
  40. data/lib/moderate/event.rb +21 -0
  41. data/lib/moderate/filters/base.rb +1 -0
  42. data/lib/moderate/filters/wordlist.rb +5 -2
  43. data/lib/moderate/jobs/classify_job.rb +96 -16
  44. data/lib/moderate/label.rb +2 -2
  45. data/lib/moderate/macros.rb +8 -3
  46. data/lib/moderate/models/appeal.rb +10 -1
  47. data/lib/moderate/models/block.rb +23 -19
  48. data/lib/moderate/models/classification.rb +24 -0
  49. data/lib/moderate/models/concerns/actor.rb +17 -2
  50. data/lib/moderate/models/concerns/content_filterable.rb +181 -55
  51. data/lib/moderate/models/concerns/deferred_notices.rb +90 -0
  52. data/lib/moderate/models/concerns/reportable.rb +84 -4
  53. data/lib/moderate/models/flag.rb +182 -19
  54. data/lib/moderate/models/report.rb +180 -16
  55. data/lib/moderate/result.rb +3 -10
  56. data/lib/moderate/retention.rb +241 -0
  57. data/lib/moderate/services/intake_notice.rb +10 -10
  58. data/lib/moderate/services/intake_report.rb +42 -13
  59. data/lib/moderate/services/resolve_appeal.rb +8 -2
  60. data/lib/moderate/services/resolve_flag.rb +183 -35
  61. data/lib/moderate/services/resolve_report.rb +288 -35
  62. data/lib/moderate/text_validator.rb +15 -14
  63. data/lib/moderate/version.rb +1 -1
  64. data/lib/moderate.rb +337 -17
  65. data/lib/tasks/moderate.rake +23 -0
  66. metadata +22 -14
  67. data/gemfiles/rails_7.2.gemfile +0 -36
  68. data/lib/moderate/text.rb +0 -38
  69. data/lib/moderate/word_list.rb +0 -90
data/lib/moderate/text.rb DELETED
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Moderate
4
- class Text
5
- class << self
6
- def bad_words?(text)
7
- return false if text.blank?
8
-
9
- @words_set ||= Set.new(compute_word_list)
10
- text.downcase.split(/\W+/).any? { |word| @words_set.include?(word) }
11
- end
12
-
13
- private
14
-
15
- def compute_word_list
16
- @default_words ||= begin
17
- words = WordList.load
18
- logger.info("[moderate gem] Loaded #{words.size} words from word list")
19
- words
20
- end
21
-
22
- result = (@default_words + Moderate.configuration.additional_words -
23
- Moderate.configuration.excluded_words).to_set
24
- logger.debug("[moderate gem] Final word list size: #{result.size}")
25
- result
26
- end
27
-
28
- def reset_word_list!
29
- @words_set = nil
30
- @default_words = nil
31
- end
32
-
33
- def logger
34
- @logger ||= defined?(Rails) ? Rails.logger : Logger.new($stdout)
35
- end
36
- end
37
- end
38
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'net/http'
4
- require 'uri'
5
- require 'tmpdir'
6
- require 'logger'
7
-
8
- module Moderate
9
- class WordList
10
- WORD_LIST_URL = 'https://raw.githubusercontent.com/coffee-and-fun/google-profanity-words/main/data/en.txt'
11
- CACHE_TTL = 30 * 24 * 60 * 60 # 30 days in seconds
12
-
13
- class << self
14
- def load
15
- cache_path = cache_file_path
16
-
17
- begin
18
- if File.exist?(cache_path)
19
- if cache_valid?(cache_path)
20
- words = File.read(cache_path, encoding: 'UTF-8').split("\n").to_set
21
- return words unless words.empty?
22
- else
23
- logger.info("[moderate gem] Cache expired (older than #{CACHE_TTL / 86400} days). Refreshing word list...")
24
- return download_and_cache(cache_path)
25
- end
26
- end
27
-
28
- logger.info("[moderate gem] No cache found. Downloading word list for the first time...")
29
- download_and_cache(cache_path)
30
- rescue StandardError => e
31
- logger.error("[moderate gem] Error loading word list: #{e.message}")
32
- logger.debug("[moderate gem] #{e.backtrace.join("\n")}")
33
- raise Moderate::Error, "Failed to load bad words list: #{e.message}"
34
- end
35
- end
36
-
37
- def refresh_word_list!
38
- logger.info("[moderate gem] Manually refreshing word list")
39
- cache_path = cache_file_path
40
- File.delete(cache_path) if File.exist?(cache_path)
41
- download_and_cache(cache_path)
42
- end
43
-
44
- private
45
-
46
- def cache_valid?(cache_path)
47
- return false unless File.exist?(cache_path)
48
-
49
- cache_age = Time.now - File.mtime(cache_path)
50
- cache_age <= CACHE_TTL
51
- end
52
-
53
- def cache_file_path
54
- if defined?(Rails)
55
- Rails.root.join('tmp', 'moderate_bad_words.txt')
56
- else
57
- File.join(Dir.tmpdir, 'moderate_bad_words.txt')
58
- end
59
- end
60
-
61
- def download_and_cache(cache_path)
62
- uri = URI(WORD_LIST_URL)
63
- response = Net::HTTP.get_response(uri)
64
-
65
- unless response.is_a?(Net::HTTPSuccess)
66
- raise Moderate::Error, "Failed to download word list. HTTP Status: #{response.code}"
67
- end
68
-
69
- content = response.body.force_encoding('UTF-8')
70
- words = content.split("\n").map(&:strip).reject(&:empty?).to_set
71
-
72
- if words.empty?
73
- raise Moderate::Error, "Downloaded word list is empty"
74
- end
75
-
76
- logger.info("[moderate gem] Successfully downloaded and cached #{words.size} words to #{cache_path}")
77
- logger.info("[moderate gem] Next cache refresh will occur after: #{Time.now + CACHE_TTL}")
78
-
79
- File.write(cache_path, content, encoding: 'UTF-8')
80
- logger.debug("[moderate gem] Cached word list to: #{cache_path}")
81
-
82
- words
83
- end
84
-
85
- def logger
86
- @logger ||= defined?(Rails) ? Rails.logger : Logger.new($stdout)
87
- end
88
- end
89
- end
90
- end