censor_bear 0.1.11 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -1
  3. data/app/assets/stylesheets/censor_bear/home.css +4 -0
  4. data/app/controllers/censor_bear/home_controller.rb +31 -0
  5. data/app/controllers/censor_bear/logs_controller.rb +1 -0
  6. data/app/controllers/censor_bear/mod_logs_controller.rb +21 -21
  7. data/app/controllers/censor_bear/stop_words_controller.rb +11 -2
  8. data/app/helpers/censor_bear/home_helper.rb +4 -0
  9. data/app/models/censor_bear/log.rb +18 -0
  10. data/app/models/censor_bear/mod_log.rb +39 -16
  11. data/app/models/censor_bear/stop_word.rb +2 -2
  12. data/app/views/censor_bear/home/debug.turbo_stream.haml +8 -0
  13. data/app/views/censor_bear/home/debugger.html.haml +9 -0
  14. data/app/views/censor_bear/home/index.html.erb +2 -0
  15. data/app/views/censor_bear/logs/_log.html.haml +8 -2
  16. data/app/views/censor_bear/logs/index.html.haml +2 -1
  17. data/app/views/censor_bear/logs/show.html.haml +29 -0
  18. data/app/views/censor_bear/mod_logs/_mod_log.html.haml +42 -37
  19. data/app/views/censor_bear/mod_logs/index.html.haml +12 -2
  20. data/app/views/censor_bear/stop_words/index.html.haml +2 -1
  21. data/app/views/layouts/censor_bear/application.html.haml +3 -2
  22. data/config/locales/censor_bear.yml +5 -0
  23. data/config/routes.rb +9 -4
  24. data/db/migrate/20211209093050_add_response_to_censor_bear_logs.rb +5 -0
  25. data/db/migrate/20211209113710_add_aasm_state_to_censor_bear_mod_logs.rb +5 -0
  26. data/lib/censor_bear/censor.rb +30 -16
  27. data/lib/censor_bear/configuration.rb +2 -0
  28. data/lib/censor_bear/version.rb +1 -1
  29. data/lib/censor_bear.rb +5 -0
  30. metadata +25 -3
  31. data/app/views/censor_bear/logs/show.html.erb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d66f337e22893a4e167117afdc22e5d7645968e71bf6efa8d63da423d10b72ab
4
- data.tar.gz: 78ddb85ff26bebe0e8d095e77918be861b75c038f9ce41cd6ad23e6f6ddae0d3
3
+ metadata.gz: 775cd9c238b951f9b9d5ce8564ae76e3e4b5d38fb3e9b71dc599d6a75bc04f5e
4
+ data.tar.gz: f8daa1001d2682697143aec92e6098d1e291d3c4d8cb7c9d28f60455fee7e6b4
5
5
  SHA512:
6
- metadata.gz: 211817d225399526a04702f2477f4b12df5df055bb18486505db058eff0d563c1b99efc253bc64bc5cfded0624e830377f9ed9984b5c8f6fd4f766d728795ef1
7
- data.tar.gz: 9bc3c4c91e07400f1f81b38e4a45ff833e6c35422632aa3235cf0eeb2db86b669d52a0dbd754158bd37514ee48a49144ddf47b820aaeab5f297410b9b98ea466
6
+ metadata.gz: 3bd8ea39fb05994a01083fdaf574501657bf085238a1c9a24b883bddadf374a8a441f910a95594f7a42081d981a2800b9b2b02c9b3cc578e200536faa52ed4b8
7
+ data.tar.gz: 6ee79015b14bf37b4f070e041c91738d906bb3d1f8c39b215a94ccf6fbe89ccc0816a9d919e1436ee3689b3bb62bd9be2df1a025afbe1090e09ee9bf0be1022d
data/README.md CHANGED
@@ -50,10 +50,12 @@ rails censor_bear:install:migrations
50
50
 
51
51
  ## 🦆 Methods
52
52
  ```ruby
53
- censor_delete # 删除内容
53
+ censor_remove # 删除内容
54
+ censor_cancel # 撤销删除
54
55
  censor_approve # 通过审核
55
56
  censor_show_path # 详情路由
56
57
  censor_ban_user # 封禁用户
58
+
57
59
  ```
58
60
 
59
61
  ## Exceptions
@@ -63,6 +65,22 @@ censor_ban_user # 封禁用户
63
65
  class NotPassedException < StandardError; end
64
66
  ```
65
67
 
68
+ ## 初始化配置
69
+ 详细配置项可在`lib/censor_bear/configuration.rb`中查看。
70
+
71
+ ```ruby
72
+ CensorBear.configure do |config|
73
+ config.user_class = 'User'
74
+ config.user_name_method = 'nickname'
75
+
76
+ config.aliyun_green_access_key_id = 'access key id'
77
+ config.aliyun_green_access_key_secret = 'access key secret'
78
+ config.aliyun_green_enable_internal = false
79
+
80
+ config.main_app_root_path_method = 'root_path'
81
+ end
82
+ ```
83
+
66
84
  ## 注意事项
67
85
  1. 为 MOD 时,表示需要人工审核,需要被审查对象(UGC内容)支持`is_approved`字段(默认为`true`),此时内容自己能看到,别人暂时看不到,需审核通过后可见。
68
86
  2. 被审查对象可以通过软删实现,隐藏(目的是防止错判,撤销),删除的内容对自己和别人都是不可见的。
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,31 @@
1
+ require_dependency 'censor_bear/application_controller'
2
+
3
+ module CensorBear
4
+ class HomeController < ApplicationController
5
+ def index; end
6
+
7
+ def debugger; end
8
+
9
+ def debug
10
+ content = params[:content]
11
+ type = params[:type]
12
+
13
+ case params[:stage]
14
+ when 'aliyun_check'
15
+ @r = AliyunGreen::Text.scan(content)
16
+ when 'check_text'
17
+ @r = CensorBear.check_text(content, type.to_sym)
18
+ when 'check_search'
19
+ # TODO: 搜索前置检查
20
+ @r = {}
21
+ when 'check_image'
22
+ # TODO: 检查图片
23
+ @r = {}
24
+ when 'qq_regex'
25
+ @r = content.match(Censor::QQ_REG)
26
+ when 'wx_regex'
27
+ @r = content.match(Censor::WX_REG)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -55,6 +55,7 @@ module CensorBear
55
55
  def load_logs
56
56
  builder ||= log_scope
57
57
  builder = builder.where("original_content ilike ?", "%#{params[:q]}%") if params[:q].present?
58
+ builder = builder.where(stage: params[:stage]) if params[:stage].present?
58
59
  builder = builder.order(id: :desc)
59
60
 
60
61
  @pagy, @logs = pagy(builder)
@@ -1,28 +1,23 @@
1
- require_dependency "censor_bear/application_controller"
1
+ require_dependency 'censor_bear/application_controller'
2
2
 
3
3
  module CensorBear
4
4
  class ModLogsController < ApplicationController
5
- before_action :set_mod_log, only: %i[show edit update destroy ignore approve ban delete]
5
+ before_action :set_mod_log, only: %i[show edit update destroy suspend ban pass undo reject]
6
6
 
7
- # GET /mod_logs
8
7
  def index
9
8
  load_mod_logs
10
9
  end
11
10
 
12
- # GET /mod_logs/1
13
11
  def show
14
12
  end
15
13
 
16
- # GET /mod_logs/new
17
14
  def new
18
15
  @mod_log = ModLog.new
19
16
  end
20
17
 
21
- # GET /mod_logs/1/edit
22
18
  def edit
23
19
  end
24
20
 
25
- # POST /mod_logs
26
21
  def create
27
22
  @mod_log = ModLog.new(mod_log_params)
28
23
 
@@ -33,7 +28,6 @@ module CensorBear
33
28
  end
34
29
  end
35
30
 
36
- # PATCH/PUT /mod_logs/1
37
31
  def update
38
32
  if @mod_log.update(mod_log_params)
39
33
  redirect_to @mod_log, notice: 'Mod log was successfully updated.'
@@ -42,7 +36,6 @@ module CensorBear
42
36
  end
43
37
  end
44
38
 
45
- # DELETE /mod_logs/1
46
39
  def destroy
47
40
  @mod_log.destroy
48
41
 
@@ -53,23 +46,29 @@ module CensorBear
53
46
  end
54
47
 
55
48
 
56
- def ignore
49
+ def suspend
57
50
  @mod_log.suspend!
58
51
 
59
52
  redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
60
53
  end
61
54
 
62
- def delete
63
- # TODO: 判断参数不能为空
64
- @mod_log.delete(params[:reason])
55
+ def pass
56
+ @mod_log.approve
65
57
 
66
- redirect_to mod_logs_url, notice: 'Mod log was successfully deleted.'
58
+ redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
67
59
  end
68
60
 
69
- def approve
70
- @mod_log.approve
61
+ def undo
62
+ # TODO: 判断状态,确定是撤销通过呢还是撤销删除
63
+ @mod_log.cancel
64
+
65
+ redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
66
+ end
67
+
68
+ def reject
69
+ @mod_log.remove(params[:reason])
71
70
 
72
- redirect_to mod_logs_url, notice: 'Mod log was successfully approved.'
71
+ redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
73
72
  end
74
73
 
75
74
  def ban
@@ -80,8 +79,11 @@ module CensorBear
80
79
 
81
80
  def load_mod_logs
82
81
  builder ||= mod_log_scope.includes(:record)
83
- builder = builder.where("content ilike ?", "%#{params[:q]}%") if params[:q].present?
84
-
82
+ builder = builder.where('content ilike ?', "%#{params[:q]}%") if params[:q].present?
83
+ if params[:filter] != 'all'
84
+ builder = builder.pending
85
+ builder = builder.unscope(where: :aasm_state).where(aasm_state: params[:state]) if params[:state].present?
86
+ end
85
87
  builder = builder.order(id: :desc)
86
88
 
87
89
  @pagy, @mod_logs = pagy(builder)
@@ -89,7 +91,6 @@ module CensorBear
89
91
 
90
92
  private
91
93
 
92
- # Use callbacks to share common setup or constraints between actions.
93
94
  def set_mod_log
94
95
  @mod_log = ModLog.find(params[:id] || params[:mod_log_id])
95
96
  end
@@ -98,7 +99,6 @@ module CensorBear
98
99
  ModLog
99
100
  end
100
101
 
101
- # Only allow a list of trusted parameters through.
102
102
  def mod_log_params
103
103
  params.require(:mod_log).permit(:record_id, :record_type, :mod_words, :user_id, :reason)
104
104
  end
@@ -1,4 +1,4 @@
1
- require_dependency "censor_bear/application_controller"
1
+ require_dependency 'censor_bear/application_controller'
2
2
 
3
3
  module CensorBear
4
4
  class StopWordsController < ApplicationController
@@ -46,7 +46,16 @@ module CensorBear
46
46
 
47
47
  def load_stop_words
48
48
  builder ||= stop_word_scope
49
- builder = builder.where("key ilike ?", "%#{params[:q]}%") if params[:q].present?
49
+ builder = builder.where('key ilike ?', "%#{params[:q]}%") if params[:q].present?
50
+ case params[:filter]
51
+ when 'size_one'
52
+ builder = builder.where('length(key) = 1')
53
+ when 'size_two'
54
+ builder = builder.where('length(key) = 2')
55
+ when 'size_three'
56
+ builder = builder.where('length(key) = 3')
57
+ end
58
+ builder = builder.where('key ilike ?', "%#{params[:q]}%")
50
59
  builder = builder.order(id: :desc)
51
60
 
52
61
  @pagy, @stop_words = pagy(builder)
@@ -0,0 +1,4 @@
1
+ module CensorBear
2
+ module HomeHelper
3
+ end
4
+ end
@@ -1,5 +1,23 @@
1
1
  module CensorBear
2
2
  class Log < ApplicationRecord
3
3
  belongs_to :user, class_name: CensorBear.config.user_class, optional: true
4
+
5
+ def suggestion
6
+ return nil if response.blank?
7
+
8
+ response['data']&.first['results']&.first['suggestion']
9
+ end
10
+
11
+ def rate
12
+ return nil if response.blank?
13
+
14
+ response['data']&.first['results']&.first['rate']
15
+ end
16
+
17
+ def label
18
+ return nil if response.blank?
19
+
20
+ response['data']&.first['results']&.first['label']
21
+ end
4
22
  end
5
23
  end
@@ -1,6 +1,8 @@
1
1
  module CensorBear
2
2
  class ModLog < ApplicationRecord
3
- REASONS = %w(无 广告/SPAM 恶意灌水 违规内容 文不对题 重复发布 其它)
3
+ include AASM
4
+
5
+ REASONS = %w(无 广告/SPAM 恶意灌水 违规内容 文不对题 重复发布 其它).freeze
4
6
  belongs_to :record, polymorphic: true, required: false
5
7
 
6
8
  def record_path
@@ -9,14 +11,14 @@ module CensorBear
9
11
  record.censor_show_path
10
12
  end
11
13
 
12
- def delete(reason)
14
+ def remove(reason)
13
15
  return if record.blank?
14
16
  return if record.discarded?
15
17
  # ugc内容需要开软删,便于撤销&查看统计,可以定时删除统计并删除软删内容,类似回收站机制
16
- raise NoMethodError.new("undefined censor_delete for record") unless record.respond_to?(:censor_delete)
18
+ raise NoMethodError, 'undefined censor_remove for record' unless record.respond_to?(:censor_remove)
17
19
 
18
- ret = record.censor_delete
19
- handled!(reason) if ret
20
+ ret = record.censor_remove
21
+ reject!(reason) if ret
20
22
  end
21
23
 
22
24
  def approve
@@ -24,7 +26,15 @@ module CensorBear
24
26
  raise NoMethodError unless record.respond_to?(:censor_approve)
25
27
 
26
28
  ret = record.censor_approve
27
- handled! if ret
29
+ pass! if ret
30
+ end
31
+
32
+ def cancel
33
+ return if record.blank?
34
+ raise NoMethodError unless record.respond_to?(:censor_cancel)
35
+
36
+ ret = record.censor_cancel
37
+ undo! if ret
28
38
  end
29
39
 
30
40
  def ban_user
@@ -32,23 +42,36 @@ module CensorBear
32
42
  raise NoMethodError unless record.respond_to?(:censor_ban_user)
33
43
 
34
44
  ret = record.censor_ban_user
35
- delete if ret
45
+ reject!('已被封禁该用户') if ret
36
46
  end
37
47
 
38
- scope :pending, -> { where(status: 0) }
39
- scope :handled, -> { where(status: 1) }
40
- scope :suspended, -> { where(status: 2) }
48
+ aasm do
49
+ state :pending, initial: true
50
+ state :rejected, :passed, :suspended
51
+
52
+ event :reject do
53
+ transitions from: %i[pending suspended], to: :rejected, after: Proc.new {|*args| set_reason(*args) }
54
+ end
55
+
56
+ event :pass do
57
+ transitions from: %i[pending suspended], to: :passed
58
+ end
59
+
60
+ event :suspend do
61
+ transitions from: :pending, to: :suspended, after: :unset_reason
62
+ end
41
63
 
42
- def handled!(reason = nil)
43
- update_columns(status: 1, reason: reason)
64
+ event :undo do
65
+ transitions from: %i[passed rejected], to: :pending, after: :unset_reason
66
+ end
44
67
  end
45
68
 
46
- def pend!
47
- update_columns(status: 0)
69
+ def unset_reason
70
+ update_columns(reason: nil)
48
71
  end
49
72
 
50
- def suspend!
51
- update_columns(status: 2)
73
+ def set_reason(reason)
74
+ update_columns(reason: reason)
52
75
  end
53
76
  end
54
77
  end
@@ -1,7 +1,7 @@
1
1
  module CensorBear
2
2
  class StopWord < ApplicationRecord
3
- FIELDS = %w(ugc username nickname signature dialog)
4
- ACTIONS_MAP = %w(ignore mod banned replace)
3
+ FIELDS = %w(ugc username nickname signature dialog).freeze
4
+ ACTIONS_MAP = %w(ignore mod banned replace).freeze
5
5
  belongs_to :user, class_name: CensorBear.config.user_class
6
6
  end
7
7
  end
@@ -0,0 +1,8 @@
1
+ = turbo_stream.update(:result) do
2
+ %h3{class: "text-lg"} 检测结果
3
+ %div{class: "bg-yellow-100 text-yellow-700 p-4 rounded-lg"}
4
+ %pre{class: "whitespace-pre-wrap"}
5
+ - if @r.is_a?(MatchData)
6
+ = @r.pretty_inspect
7
+ - else
8
+ = JSON.pretty_generate(@r.is_a?(CensorBear::Result) ? JSON.parse(@r.to_json) : @r)
@@ -0,0 +1,9 @@
1
+ %div{class: "flex flex-col space-y-2 rounded-lg border p-4"}
2
+ %h2{class: "text-xl font-medium text-center"} 在线调试
3
+ %div{class: "flex justify-center"}
4
+ = form_with(url: debug_path, method: :post, class: "flex flex-col items-center w-full space-y-4") do |f|
5
+ = f.select :stage, options_for_select([%w[check_text check_text], %w[check_search check_search], %w[check_search check_search], %w[qq_regex qq_regex], %w[wx_regex wx_regex]], params[:stage]), {}, class: "border rounded-md m py-0.5 px-1"
6
+ = f.select :type, options_for_select(CensorBear::StopWord::FIELDS.map{|f| [f.upcase, f]}, params[:type]), {}, class: "border rounded-md m py-0.5 px-1"
7
+ = f.text_area :content, value: params[:content], placeholder: "输入正文", rows: 6, class: "bg-gray-100 rounded-lg p-4 w-full"
8
+ = f.submit "检测", class: "rounded-lg px-10 py-1 bg-black text-white cursor-pointer"
9
+ #result
@@ -0,0 +1,2 @@
1
+ <h1>Home#index</h1>
2
+ <p>Find me in app/views/censor_bear/home/index.html.erb</p>
@@ -9,6 +9,12 @@
9
9
  %span{class: "text-sm bg-pink-100 text-pink-600 py-1 px-2 rounded-lg"}=t("censor_log.stage.#{log.stage}")
10
10
  - unless log.action.blank?
11
11
  %span{class: "text-sm bg-yellow-100 text-yellow-600 py-1 px-2 rounded-lg"}=t("censor_log.action.#{log.action.downcase}")
12
+ - unless log.suggestion.blank?
13
+ %span{class: "text-sm bg-green-100 text-green-600 py-1 px-2 rounded-lg"}=log.suggestion.upcase
14
+ - unless log.rate.blank?
15
+ %span{class: "text-sm bg-green-100 text-green-600 py-1 px-2 rounded-lg"}=log.rate
16
+ - unless log.label.blank?
17
+ %span{class: "text-sm bg-green-100 text-green-600 py-1 px-2 rounded-lg"}=log.label.upcase
12
18
  - unless log.user_id.blank?
13
19
  %span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg font-bold"}="##{log.user_id}"
14
20
  - unless log.ip.blank?
@@ -19,7 +25,7 @@
19
25
  %span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg"}= log.created_at
20
26
  %div{class: "flex space-x-2 items-center"}
21
27
  %span
22
- = link_to '详情', log
28
+ = link_to '详情', log, data: { turbo: false }
23
29
  %span
24
30
  = button_to '删除', log, method: :delete, data: { confirm: 'Are you sure?' }, class: "text-red-600 rounded-md px-2 py-0.5"
25
31
  %div{class: "flex justify-between items-center"}
@@ -32,5 +38,5 @@
32
38
  %div{class: "text-yellow-500 font-meidum text-xl"}
33
39
  - log.mod_words.each do |word|
34
40
  %span [
35
- %span= link_to word, stop_words_path(q: word), data: { turbo: false }, target: "_blank"
41
+ %span= link_to word, stop_words_path(q: word), data: { turbo: false }
36
42
  %span ]
@@ -4,10 +4,11 @@
4
4
  = form_with(url: logs_path, method: :get, class: "flex items-center") do |f|
5
5
  %div{class: "space-x-0.5"}
6
6
  = f.text_field :q, value: params[:q], placeholder: "关键词", class: "border rounded-md m py-0.5 px-1"
7
+ = f.select :stage, options_for_select([%w[aliyun_check aliyun_check], %w[aliyun_check aliyun_check], %w[qq_regex qq_regex], %w[wx_regex wx_regex], %w[local_check local_check]], params[:stage]), {include_blank: "-- 按Stage筛选 --"}, class: "border rounded-md m py-0.5 px-1"
7
8
  = f.submit "检索", class: "rounded-md px-2 py-1 text-sm bg-black text-white cursor-pointer"
8
9
  = link_to "重置", logs_path, class: "text-sm"
9
10
 
10
- %div="共 #{@pagy.count} 条"
11
+ %div{class: "text-sm text-gray-600"}="共 #{@pagy.count} 条"
11
12
  %div
12
13
  - if @logs.blank?
13
14
  %div{class: "flex justify-center text-gray-500 p-8 border rounded-md"} 空空如也
@@ -0,0 +1,29 @@
1
+ %div{class: "flex flex-col space-y-2"}
2
+ %div
3
+ %div{class: "font-bold"} 源内容:
4
+ %div= @log.original_content
5
+ %div
6
+ %div{class: "font-bold"} 过滤后内容:
7
+ %div= @log.filtered_content
8
+ %div
9
+ %div{class: "font-bold"} 动作:
10
+ %div= @log.action
11
+ %div
12
+ %div{class: "font-bold"} 场景:
13
+ %div= @log.scenario
14
+ %div
15
+ %div{class: "font-bold"} 检测阶段:
16
+ %div= @log.stage
17
+ %div
18
+ %div{class: "font-bold"} 命中敏感词:
19
+ %div= @log.mod_words
20
+ %div
21
+ %div{class: "font-bold"} 云检测返回标签:
22
+ %div= @log.labels
23
+ %div
24
+ %div{class: "font-bold"} 云检测返回标签:
25
+ %div= @log.labels
26
+ %div
27
+ %div{class: "font-bold"} 云检测返回:
28
+ %div{class: "p-1 bg-gray-100 rounded-lg"}
29
+ %pre= JSON.pretty_generate(@log.response)
@@ -2,44 +2,49 @@
2
2
  %div{class: "flex flex-col"}
3
3
  %div{class: "border rounded-md mb-2 p-2 flex flex-col space-y-1"}
4
4
  %div{class: "flex justify-between"}
5
- %div{class: ""}
5
+ %div{class: "text-sm"}
6
6
  = link_to mod_log.record_path, data: { turbo: false }, target: "_blank" do
7
- %span{class: "text-sm bg-indigo-100 text-indigo-600 py-1 px-2 rounded-lg"}="#{mod_log.record_type.upcase}##{mod_log.record_id}"
8
- - unless mod_log.labels.blank?
9
- - mod_log.labels.each do |label|
10
- %span{class: "text-sm bg-red-100 text-red-600 py-1 px-2 rounded-lg"}=t("censor_log.label.#{label}")
11
- - unless mod_log.mod_words.blank?
12
- - mod_log.mod_words.each do |word|
13
- %span{class: "text-sm bg-yellow-100 text-yellow-600 py-1 px-2 rounded-lg"}="##{word}"
14
- %span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg"}= mod_log.created_at
15
- %div{class: "flex space-x-2 items-center"}
7
+ %span{class: "bg-indigo-100 text-indigo-600 py-1 px-2 rounded-lg"}="#{mod_log.record_type.upcase}##{mod_log.record_id}"
8
+ %span{class: "bg-yellow-100 text-yellow-600 rounded-md p-1"}=t("censor_log.aasm_state.#{mod_log.aasm_state}")
9
+ %div{class: "flex space-x-2 items-center text-sm"}
16
10
  - if mod_log.reason
17
11
  %span{class: "bg-red-100 text-red-600 p-1 rounded-lg"}
18
12
  = mod_log.reason
19
- %span{class: "bg-green-100 text-green-600 rounded-md p-1"}
20
- - if mod_log.status == 0
21
- = "待审"
22
- - if mod_log.status == 1
23
- = "已处理"
24
- - if mod_log.status == 2
25
- = "暂缓"
26
- %span
27
- = link_to "详情", mod_log, data: { turbo: false }, target: "_blank"
28
- %span
29
- = button_to '删除', mod_log, method: :delete, data: { confirm: 'Are you sure?'}, class: "text-red-600 rounded-md px-2 py-0.5"
30
- %div{class: "flex justify-between items-center"}
31
- %div{class: "flex flex-col"}
32
- %div{class: "text-sm border rounded-lg border-black p-2"}
33
- %div= mod_log.content
34
- %div
35
- %div{class: "flex space-x-2"}
36
- %span{class: "text-yellow-600"}
37
- = link_to "忽略", mod_log_ignore_path(mod_log)
38
- %span{class: "text-green-600"}
39
- = link_to "通过", mod_log_approve_path(mod_log)
40
- %div
41
- = form_with(url: mod_log_delete_path(mod_log), method: :get, class: "space-x-1 flex items-center") do |f|
42
- = f.submit "删除", class: "rounded-md px-2 py-1 text-sm bg-red-600 text-white cursor-pointer"
43
- = f.text_field :reason, value: mod_log.reason, placeholder: "不通过原因", class: "border rounded-md m py-0.5 px-1"
44
- -#%span{class: "text-red-600 font-bold"}
45
- -# = link_to "封禁用户!", mod_log_ban_path(mod_log)
13
+ %span{class: "bg-gray-100 text-gray-600 py-1 px-2 rounded-lg"}= mod_log.created_at
14
+ - if current_user.admin?
15
+ = button_to '删除', mod_log, method: :delete, data: { confirm: 'Are you sure?'}, class: "bg-red-600 text-white rounded-md px-2 py-0.5 cursor-pointer"
16
+ %div{class: "flex justify-between items-center py-2 w-full"}
17
+ %div{class: "flex flex-col rounded-lg bg-gray-50 p-3 text-sm space-y-4 w-full"}
18
+ -unless mod_log.record
19
+ %h3{class: "text-2xl text-red-600"} 对应条目已被永久删除,当前审核请求已无效,请删除
20
+ - else
21
+ %div
22
+ %span{class: "border border-pink-600 text-pink-600 rounded-lg text-xs p-0.5"}=mod_log.record.is_private ? "隐私" : "公开"
23
+ %span{class: "border border-blue-600 text-blue-600 rounded-lg text-xs p-0.5"}=mod_log.record.is_approved ? "审核通过" : "审核中..."
24
+ - if mod_log.record.discarded?
25
+ %div{class: "text-gray-900 leading-relaxed"}
26
+ %del= mod_log.record.content
27
+ - else
28
+ %div{class: "text-gray-900 leading-relaxed"}= mod_log.record.content
29
+ %div
30
+ - mod_log.record.pictures.each do |pic|
31
+ = image_tag pic.full_url, class: "w-16 rounded-md"
32
+ %div
33
+ - unless mod_log.labels.blank?
34
+ - mod_log.labels.each do |label|
35
+ %span{class: "text-xs bg-gray-200 text-gray-600 py-0.5 px-1 rounded-lg"}=t("censor_log.label.#{label}")
36
+ - unless mod_log.mod_words.blank?
37
+ - mod_log.mod_words.each do |word|
38
+ %span{class: "text-sm bg-gray-100 text-gray-600 py-0.5 px-1 rounded-lg"}="##{word}"
39
+ %div{class: "flex space-x-2 items-center justify-end"}
40
+ - if mod_log.may_suspend?
41
+ = link_to "忽略", mod_log_suspend_path(mod_log), class: "text-yellow-600 cursor-pointer"
42
+ - if mod_log.may_pass?
43
+ = link_to "通过", mod_log_pass_path(mod_log), class: "text-green-600 cursor-pointer"
44
+ - if mod_log.may_undo?
45
+ = link_to "撤销", mod_log_undo_path(mod_log), class: "text-red-600 cursor-pointer"
46
+ - if mod_log.may_reject?
47
+ = form_with(url: mod_log_reject_path(mod_log), method: :get, class: "space-x-1 flex items-center") do |f|
48
+ = f.submit "拒绝", class: "text-red-600 bg-transparent cursor-pointer"
49
+ = f.text_field :reason, value: mod_log.reason, placeholder: "不通过原因", class: "border rounded-md m py-0.5 px-1 text-sm"
50
+
@@ -6,8 +6,18 @@
6
6
  = f.text_field :q, value: params[:q], placeholder: "关键词", class: "border rounded-md m py-0.5 px-1"
7
7
  = f.submit "检索", class: "rounded-md px-2 py-1 text-sm bg-black text-white cursor-pointer"
8
8
  = link_to "重置", mod_logs_path, class: "text-sm"
9
-
10
- %div="共 #{@pagy.count} 条"
9
+ %div{class: "flex items-center space-x-2"}
10
+ %div{class: "text-sm text-gray-600"}="共 #{@pagy.count} 条"
11
+ %div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:filter] == "all" ? 'font-bold' : ''}"}
12
+ = link_to "全部", request.params.merge(filter: "all", state: "")
13
+ %div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state].blank? && params[:filter].blank? ? 'font-bold' : ''}"}
14
+ = link_to "待处理", request.params.merge(state: "", filter: "")
15
+ %div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state] == 'rejected' ? 'font-bold' : ''}"}
16
+ = link_to "被驳回", request.params.merge(state: "rejected", filter: "")
17
+ %div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state] == 'passed' ? 'font-bold' : ''}"}
18
+ = link_to "通过", request.params.merge(state: "passed", filter: "")
19
+ %div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state] == 'suspended' ? 'font-bold' : ''}"}
20
+ = link_to "暂缓处理", request.params.merge(state: "suspended", filter: "")
11
21
  %div
12
22
  - if @mod_logs.blank?
13
23
  %div{class: "flex justify-center text-gray-500 p-8 border rounded-md"} 空空如也
@@ -4,10 +4,11 @@
4
4
  = form_with(url: stop_words_path, method: :get, class: "flex items-center") do |f|
5
5
  %div{class: "space-x-0.5"}
6
6
  = f.text_field :q, value: params[:q], placeholder: "关键词", class: "border rounded-md m py-0.5 px-1"
7
+ = f.select :filter, options_for_select([%w[长度为1 size_one], %w[长度为2 size_two], %w[长度为3 size_three]], params[:filter]), {include_blank: "-- 按敏感词长度筛选 --"}, class: "border rounded-md m py-0.5 px-1"
7
8
  = f.submit "检索", class: "rounded-md px-2 py-1 text-sm bg-black text-white cursor-pointer"
8
9
  = link_to "重置", stop_words_path, class: "text-sm"
9
-
10
10
  %div
11
+ %span{class: "text-sm text-gray-600"}="共 #{@pagy.count} 条"
11
12
  = link_to '批量导入', "#", class: "text-sm"
12
13
  = link_to '新增', new_stop_word_path, class: "rounded-md px-2 py-1 text-sm bg-black text-white cursor-pointer"
13
14
  %div
@@ -10,9 +10,10 @@
10
10
  %body{class: "flex flex-col space-y-6 p-4"}
11
11
  %ul{class: "flex justify-end items-center space-x-2 text-blue-600"}
12
12
  %li= link_to "审查(#{CensorBear::ModLog.pending.count})", mod_logs_path
13
- %li= link_to "敏感词", stop_words_path
14
13
  %li= link_to "日志", logs_path
15
- %li= link_to "回主站", "/"
14
+ %li= link_to "敏感词", stop_words_path
15
+ %li= link_to "调试", debugger_path
16
+ %li= link_to "控制台", main_app.send(CensorBear.config.main_app_root_path_method.to_sym)
16
17
  %li= current_user.email
17
18
  %div
18
19
  = yield
@@ -8,7 +8,12 @@ en:
8
8
  block: 禁用
9
9
  pass: 忽略
10
10
  review: 审核
11
+ aasm_state:
12
+ pending: 待审
13
+ handled: 已处理
14
+ suspended: 暂缓处理
11
15
  stage:
16
+ check_search: 搜索检查
12
17
  aliyun_check: 阿里云检查
13
18
  tencent_check: 腾讯云检查
14
19
  local_check: 本地检查
data/config/routes.rb CHANGED
@@ -1,12 +1,17 @@
1
1
  CensorBear::Engine.routes.draw do
2
2
  resources :mod_logs do
3
- get :ignore
4
- get :approve
5
- get :delete
3
+ get :suspend
4
+ get :reject
5
+ get :pass
6
+ get :undo
6
7
  get :ban
7
8
  end
8
9
  resources :logs
9
10
  resources :stop_words
10
11
 
11
- root to: "stop_words#index"
12
+ get '/welcome', to: 'home#index'
13
+ get '/debugger', to: 'home#debugger'
14
+ post '/debug', to: 'home#debug'
15
+
16
+ root to: 'mod_logs#index'
12
17
  end
@@ -0,0 +1,5 @@
1
+ class AddResponseToCensorBearLogs < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :censor_bear_logs, :response, :json
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddAasmStateToCensorBearModLogs < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :censor_bear_mod_logs, :aasm_state, :string
4
+ end
5
+ end
@@ -30,15 +30,15 @@ module CensorBear
30
30
  return Result.new(@content) unless CensorBear::StopWord::FIELDS.include?(@type)
31
31
 
32
32
  # 正则过滤
33
- if @content.match(QQ_REG)
34
- CensorBear.info(@content, @type, 'BANNED', 'qq_regex', ip: @ip, user_id: @user_id)
35
- raise NotPassedException
36
- end
33
+ # if @content.match(QQ_REG)
34
+ # CensorBear.info(@content, @type, 'BANNED', 'qq_regex', ip: @ip, user_id: @user_id)
35
+ # raise NotPassedException
36
+ # end
37
37
 
38
- if @content.match(WX_REG)
39
- CensorBear.info(@content, @type, 'BANNED', 'wx_regex', ip: @ip, user_id: @user_id)
40
- raise NotPassedException
41
- end
38
+ # if @content.match(WX_REG)
39
+ # CensorBear.info(@content, @type, 'BANNED', 'wx_regex', ip: @ip, user_id: @user_id)
40
+ # raise NotPassedException
41
+ # end
42
42
 
43
43
  # 本地自定义词库粗查
44
44
  local_check
@@ -115,32 +115,44 @@ module CensorBear
115
115
  rate = r['rate']
116
116
  @mod_words = concat_words(r['details'])
117
117
  @labels = concat_labels(r['details'])
118
- if action == 'block' && rate >= 70 && %w[politics terrorism abuse].include?(r['label'])
118
+ if action == 'block' && rate >= 70 && %w[politics terrorism].include?(r['label'])
119
119
  CensorBear.info(
120
- d['content'], @type, action, 'aliyun_check',
120
+ d['content'], @type, 'banned', 'aliyun_check',
121
121
  filtered_content: d['filteredContent'],
122
122
  mod_words: @mod_words,
123
123
  labels: @labels,
124
- ip: @ip, user_id: @user_id
124
+ ip: @ip, user_id: @user_id,
125
+ response: response
125
126
  )
126
127
  raise NotPassedException
127
- elsif action == 'block'
128
+ elsif action == 'block' && rate >= 90
128
129
  @is_mod = true
129
130
  CensorBear.info(
130
- d['content'], @type, action, 'aliyun_check',
131
+ d['content'], @type, 'mod', 'aliyun_check',
131
132
  filtered_content: d['filteredContent'],
132
133
  mod_words: @mod_words,
133
134
  labels: @labels,
134
- ip: @ip, user_id: @user_id
135
+ ip: @ip, user_id: @user_id,
136
+ response: response
135
137
  )
136
138
  elsif action == 'review'
137
139
  @is_mod = true
138
140
  CensorBear.info(
139
- d['content'], @type, action, 'aliyun_check',
141
+ d['content'], @type, 'mod', 'aliyun_check',
140
142
  filtered_content: d['filteredContent'],
141
143
  mod_words: @mod_words,
142
144
  labels: @labels,
143
- ip: @ip, user_id: @user_id
145
+ ip: @ip, user_id: @user_id,
146
+ response: response
147
+ )
148
+ else
149
+ CensorBear.info(
150
+ d['content'], @type, 'ignore', 'aliyun_check',
151
+ filtered_content: d['filteredContent'],
152
+ mod_words: @mod_words,
153
+ labels: @labels,
154
+ ip: @ip, user_id: @user_id,
155
+ response: response
144
156
  )
145
157
  end
146
158
  end
@@ -149,6 +161,7 @@ module CensorBear
149
161
 
150
162
  def concat_labels(data)
151
163
  return [] if data.blank?
164
+
152
165
  labels = []
153
166
  data.map do |d|
154
167
  next if d['label'].blank?
@@ -160,6 +173,7 @@ module CensorBear
160
173
 
161
174
  def concat_words(data)
162
175
  return [] if data.blank?
176
+
163
177
  words = []
164
178
  data.each do |d|
165
179
  next if d['contexts'].blank?
@@ -17,5 +17,7 @@ module CensorBear
17
17
  attr_accessor :aliyun_green_access_key_id
18
18
  attr_accessor :aliyun_green_access_key_secret
19
19
  attr_accessor :aliyun_green_enable_internal
20
+
21
+ attr_accessor :main_app_root_path_method
20
22
  end
21
23
  end
@@ -1,3 +1,3 @@
1
1
  module CensorBear
2
- VERSION = '0.1.11'.freeze
2
+ VERSION = '0.1.15'.freeze
3
3
  end
data/lib/censor_bear.rb CHANGED
@@ -3,6 +3,7 @@ require 'censor_bear/engine'
3
3
  require 'censor_bear/censor'
4
4
  require 'censor_bear/configuration'
5
5
 
6
+ require 'aasm'
6
7
  require 'hamlit'
7
8
  require 'pagy'
8
9
  require 'aliyun_green'
@@ -24,6 +25,8 @@ module CensorBear
24
25
  @config.aliyun_green_access_key_id = ''
25
26
  @config.aliyun_green_access_key_secret = ''
26
27
  @config.aliyun_green_enable_internal = false
28
+ @config.main_app_root_path_method = 'root_path'
29
+
27
30
  @config
28
31
  end
29
32
 
@@ -45,6 +48,7 @@ module CensorBear
45
48
  labels = options[:labels] || []
46
49
  ip = options[:ip] || nil
47
50
  user_id = options[:user_id] || nil
51
+ response = options[:response] || {}
48
52
 
49
53
  CensorBear::Log.create(
50
54
  original_content: content,
@@ -56,6 +60,7 @@ module CensorBear
56
60
  labels: labels,
57
61
  ip: ip,
58
62
  user_id: user_id,
63
+ response: response
59
64
  )
60
65
  end
61
66
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: censor_bear
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - 42up
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-08 00:00:00.000000000 Z
11
+ date: 2021-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 6.0.4.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: aasm
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: aliyun_green
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -112,16 +126,19 @@ files:
112
126
  - Rakefile
113
127
  - app/assets/config/censor_bear_manifest.js
114
128
  - app/assets/stylesheets/censor_bear/application.css
129
+ - app/assets/stylesheets/censor_bear/home.css
115
130
  - app/assets/stylesheets/censor_bear/logs.css
116
131
  - app/assets/stylesheets/censor_bear/mod_logs.css
117
132
  - app/assets/stylesheets/censor_bear/stop_words.css
118
133
  - app/assets/stylesheets/censor_bear/tailwind.min.css
119
134
  - app/assets/stylesheets/scaffold.css
120
135
  - app/controllers/censor_bear/application_controller.rb
136
+ - app/controllers/censor_bear/home_controller.rb
121
137
  - app/controllers/censor_bear/logs_controller.rb
122
138
  - app/controllers/censor_bear/mod_logs_controller.rb
123
139
  - app/controllers/censor_bear/stop_words_controller.rb
124
140
  - app/helpers/censor_bear/application_helper.rb
141
+ - app/helpers/censor_bear/home_helper.rb
125
142
  - app/helpers/censor_bear/logs_helper.rb
126
143
  - app/helpers/censor_bear/mod_logs_helper.rb
127
144
  - app/helpers/censor_bear/stop_words_helper.rb
@@ -131,13 +148,16 @@ files:
131
148
  - app/models/censor_bear/log.rb
132
149
  - app/models/censor_bear/mod_log.rb
133
150
  - app/models/censor_bear/stop_word.rb
151
+ - app/views/censor_bear/home/debug.turbo_stream.haml
152
+ - app/views/censor_bear/home/debugger.html.haml
153
+ - app/views/censor_bear/home/index.html.erb
134
154
  - app/views/censor_bear/logs/_form.html.erb
135
155
  - app/views/censor_bear/logs/_log.html.haml
136
156
  - app/views/censor_bear/logs/destroy.turbo_stream.haml
137
157
  - app/views/censor_bear/logs/edit.html.erb
138
158
  - app/views/censor_bear/logs/index.html.haml
139
159
  - app/views/censor_bear/logs/new.html.erb
140
- - app/views/censor_bear/logs/show.html.erb
160
+ - app/views/censor_bear/logs/show.html.haml
141
161
  - app/views/censor_bear/mod_logs/_form.html.erb
142
162
  - app/views/censor_bear/mod_logs/_mod_log.html.haml
143
163
  - app/views/censor_bear/mod_logs/destroy.turbo_stream.haml
@@ -162,6 +182,8 @@ files:
162
182
  - db/migrate/20211129110218_create_censor_bear_mod_logs.rb
163
183
  - db/migrate/20211206092626_add_ip_to_censor_bear_logs.rb
164
184
  - db/migrate/20211208041114_add_labels_to_censor_bear_logs.rb
185
+ - db/migrate/20211209093050_add_response_to_censor_bear_logs.rb
186
+ - db/migrate/20211209113710_add_aasm_state_to_censor_bear_mod_logs.rb
165
187
  - lib/censor_bear.rb
166
188
  - lib/censor_bear/censor.rb
167
189
  - lib/censor_bear/configuration.rb
@@ -1,34 +0,0 @@
1
- <p id="notice"><%= notice %></p>
2
-
3
- <p>
4
- <strong>Orginal content:</strong>
5
- <%= @log.original_content %>
6
- </p>
7
-
8
- <p>
9
- <strong>Action:</strong>
10
- <%= @log.action %>
11
- </p>
12
-
13
- <p>
14
- <strong>Scenario:</strong>
15
- <%= @log.scenario %>
16
- </p>
17
-
18
- <p>
19
- <strong>Stage:</strong>
20
- <%= @log.stage %>
21
- </p>
22
-
23
- <p>
24
- <strong>Filtered content:</strong>
25
- <%= @log.filtered_content %>
26
- </p>
27
-
28
- <p>
29
- <strong>Mod words:</strong>
30
- <%= @log.mod_words %>
31
- </p>
32
-
33
- <%= link_to 'Edit', edit_log_path(@log) %> |
34
- <%= link_to 'Back', logs_path %>