censor_bear 0.1.13 → 0.1.18

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +96 -24
  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 +18 -0
  6. data/app/controllers/censor_bear/mod_logs_controller.rb +24 -22
  7. data/app/helpers/censor_bear/home_helper.rb +4 -0
  8. data/app/models/censor_bear/log.rb +18 -0
  9. data/app/models/censor_bear/mod_log.rb +57 -16
  10. data/app/models/censor_bear/stop_word.rb +2 -2
  11. data/app/views/censor_bear/home/debug.turbo_stream.haml +8 -0
  12. data/app/views/censor_bear/home/debugger.html.haml +9 -0
  13. data/app/views/censor_bear/home/index.html.erb +2 -0
  14. data/app/views/censor_bear/logs/_log.html.haml +9 -2
  15. data/app/views/censor_bear/logs/index.html.haml +6 -1
  16. data/app/views/censor_bear/logs/show.html.haml +29 -0
  17. data/app/views/censor_bear/mod_logs/_mod_log.html.haml +44 -37
  18. data/app/views/censor_bear/mod_logs/index.html.haml +12 -2
  19. data/app/views/layouts/censor_bear/application.html.haml +1 -0
  20. data/config/locales/censor_bear.yml +6 -2
  21. data/config/routes.rb +12 -5
  22. data/db/migrate/20211129110218_create_censor_bear_mod_logs.rb +0 -1
  23. data/db/migrate/20211209093050_add_response_to_censor_bear_logs.rb +5 -0
  24. data/db/migrate/20211209113710_add_aasm_state_to_censor_bear_mod_logs.rb +5 -0
  25. data/lib/censor_bear/censor.rb +27 -11
  26. data/lib/censor_bear/configuration.rb +1 -0
  27. data/lib/censor_bear/version.rb +1 -1
  28. data/lib/censor_bear.rb +4 -0
  29. metadata +27 -5
  30. 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: a3bf3dd6d88b6f7ece7434deabf56aa6322c305536e44d5ead4bae5c1aa94111
4
- data.tar.gz: 9484d90389880990a519229d851394f7a484455972316f90f3d9891dda32851d
3
+ metadata.gz: 718c2ec6b519d6148ea047125d8a756b3cdc982aba68b4c7ca6d62e73b71a606
4
+ data.tar.gz: af687db6053250f6da78e3eea1192c648725039158a236472f7e5bcc539226ba
5
5
  SHA512:
6
- metadata.gz: e283297831488e216fe57089166d37084725ef93a387ea615d6897298d95616b181880af06c19e1ce3caf536ac29bdaedf1a25408f7392719390da40d721be8d
7
- data.tar.gz: 11568a082cc47a024aecb4bd7a2b1f09e785b7690e88bba1bb6acb5445b7eb31ac4f4834f4a63c5429fe8a2c1c40ae6e6c72c21efd130903d3cdb4df81a25d16
6
+ metadata.gz: 6f83eae4f76dca1658c2d585f36a2a5419dc49c4e3f3beaeec17fd856a7464035def3b347ecd6ae59b6e4fe31f9ce6d3bae52a9659bc0bbab7cff66219f4d5c6
7
+ data.tar.gz: 07b471f86713559334a67950d35483f22007005caad6c0aecc93b7cbaa01ecc5cfda3aac7d5d95f6cec37abf38218d67aa18f01965a82c5d9e39a3b6bedccbdb
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
- # CensorBear
2
- Short description and motivation.
1
+ # 🐻 CensorBear
3
2
 
4
- ## Usage
5
- How to use my plugin.
3
+ CensorBear 是一个可插拔的内容审核管理工具。
6
4
 
7
5
  ## Installation
8
6
  Add this line to your application's Gemfile:
@@ -21,15 +19,104 @@ Or install it yourself as:
21
19
  $ gem install censor_bear
22
20
  ```
23
21
 
22
+ Then execute to copy migrations:
23
+
24
+ ```bash
25
+ rails censor_bear:install:migrations
26
+ ```
27
+
28
+ ## 初始化配置
29
+ 详细配置项可在`lib/censor_bear/configuration.rb`中查看。
30
+
31
+ ### 待办
32
+ - [ ] 用户详情跳转路由
33
+
34
+ ```ruby
35
+ CensorBear.configure do |config|
36
+ config.user_class = 'User'
37
+ config.user_name_method = 'nickname'
38
+
39
+ config.aliyun_green_access_key_id = 'access key id'
40
+ config.aliyun_green_access_key_secret = 'access key secret'
41
+ config.aliyun_green_enable_internal = false
42
+
43
+ config.main_app_root_path_method = 'root_path'
44
+ config.main_app_user_path_method = 'admins_user_path'
45
+ end
46
+ ```
47
+
48
+ ## I18n 配置
49
+ 复制i18n文件到`config/locales/censor_bear.yml`
50
+ ```yml
51
+ "zh-CN":
52
+ censor_log:
53
+ action:
54
+ mod: 审核
55
+ banned: 禁用
56
+ ignore: 忽略
57
+ replace: 替换
58
+ block: 禁用
59
+ pass: 忽略
60
+ review: 审核
61
+ aasm_state:
62
+ pending: 待审
63
+ handled: 已处理
64
+ suspended: 暂缓处理
65
+ stage:
66
+ check_search: 搜索检查
67
+ aliyun_check: 阿里云检查
68
+ tencent_check: 腾讯云检查
69
+ local_check: 本地检查
70
+ qq_regex: QQ正则检查
71
+ wechat_regex: 微信正则检查
72
+ label:
73
+ normal: 正常文本
74
+ spam: 含垃圾信息
75
+ ad: 广告
76
+ politics: 涉政
77
+ terrorism: 暴恐
78
+ abuse: 辱骂
79
+ porn: 色情
80
+ flood: 灌水
81
+ contraband: 违禁
82
+ meaningless: 无意义
83
+ harmful: 不良场景
84
+ customized: 自定义
85
+ ```
86
+
87
+ ## 🦆 需要实现的钩子方法
88
+ ```ruby
89
+ censor_show_path # 被审资源路由详情
90
+ censor_images # 被审资源图片关联资源
91
+ censor_remove # 删除内容
92
+ censor_undo_remove # 撤销删除内容
93
+ censor_approve # 通过审核
94
+ censor_undo_approve # 撤销通过审核
95
+ censor_ban_user # 封禁用户
96
+ ```
97
+
98
+ ## 用法
99
+ ```ruby
100
+
101
+ ```
102
+
103
+ ## Exceptions
104
+ 检测到被ban的关键词,会抛出`NotPassedException`异常,禁止用户继续创建内容,可在controller捕获该方法处理好返回值。
105
+
106
+ ```ruby
107
+ class NotPassedException < StandardError; end
108
+ ```
109
+
110
+
24
111
  ## Authentication
25
112
  ### Devise
26
113
  ```ruby
27
114
  authenticate :user, ->(user) { user.admin? } do
28
- mount Blazer::Engine, at: "censor_bear"
115
+ mount CensorBear::Engine => "/censor_bear", as: "censor_bear"
29
116
  end
30
117
  ```
31
118
 
32
- ### Other
119
+ ### Other(todo)
33
120
  Specify a before_action method to run in `censor_bear.yml`.
34
121
  ```ruby
35
122
  before_action_method: require_admin
@@ -43,25 +130,11 @@ end
43
130
  ```
44
131
  Be sure to render or redirect for unauthorized users.
45
132
 
46
- ## Migration
47
- ```bash
48
- rails censor_bear:install:migrations
49
- ```
50
133
 
51
- ## 🦆 Methods
52
- ```ruby
53
- censor_delete # 删除内容
54
- censor_approve # 通过审核
55
- censor_show_path # 详情路由
56
- censor_ban_user # 封禁用户
57
- ```
134
+ ## 数据库字段要求
135
+ - is_approved
136
+ - 支持软删
58
137
 
59
- ## Exceptions
60
- 检测到被ban的关键词,会抛出`NotPassedException`异常,禁止用户继续创建内容,可在controller捕获该方法处理好返回值。
61
-
62
- ```ruby
63
- class NotPassedException < StandardError; end
64
- ```
65
138
 
66
139
  ## 注意事项
67
140
  1. 为 MOD 时,表示需要人工审核,需要被审查对象(UGC内容)支持`is_approved`字段(默认为`true`),此时内容自己能看到,别人暂时看不到,需审核通过后可见。
@@ -72,7 +145,6 @@ class NotPassedException < StandardError; end
72
145
  6. 被BAN掉的内容,无法创建,直接返回异常,速错。
73
146
 
74
147
 
75
-
76
148
  ## Contributing
77
149
  Contribution directions go here.
78
150
 
@@ -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
@@ -52,9 +52,27 @@ module CensorBear
52
52
  end
53
53
  end
54
54
 
55
+ def remove_data
56
+ builder = log_scope
57
+ case params[:type]
58
+ when '3days'
59
+ builder = builder.where("created_at >= ?", (Time.now - 3.days).beginning_of_day)
60
+ when '7days'
61
+ builder = builder.where("created_at >= ?", (Time.now - 7.days).beginning_of_day)
62
+ when '30days'
63
+ builder = builder.where("created_at >= ?", (Time.now - 30.days).beginning_of_day)
64
+ when 'all'
65
+ builder
66
+ end
67
+ builder.delete_all
68
+
69
+ redirect_to logs_url, notice: "Mod log was successfully destroyed."
70
+ end
71
+
55
72
  def load_logs
56
73
  builder ||= log_scope
57
74
  builder = builder.where("original_content ilike ?", "%#{params[:q]}%") if params[:q].present?
75
+ builder = builder.where(stage: params[:stage]) if params[:stage].present?
58
76
  builder = builder.order(id: :desc)
59
77
 
60
78
  @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
 
@@ -52,24 +45,32 @@ module CensorBear
52
45
  end
53
46
  end
54
47
 
55
-
56
- def ignore
48
+ def suspend
57
49
  @mod_log.suspend!
58
50
 
59
51
  redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
60
52
  end
61
53
 
62
- def delete
63
- # TODO: 判断参数不能为空
64
- @mod_log.delete(params[:reason])
54
+ def pass
55
+ @mod_log.approve
65
56
 
66
- redirect_to mod_logs_url, notice: 'Mod log was successfully deleted.'
57
+ redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
67
58
  end
68
59
 
69
- def approve
70
- @mod_log.approve
60
+ def undo
61
+ if @mod_log.passed?
62
+ @mod_log.undo_approve
63
+ elsif @mod_log.rejected?
64
+ @mod_log.undo_remove
65
+ end
66
+
67
+ redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
68
+ end
69
+
70
+ def reject
71
+ @mod_log.remove(params[:reason])
71
72
 
72
- redirect_to mod_logs_url, notice: 'Mod log was successfully approved.'
73
+ redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
73
74
  end
74
75
 
75
76
  def ban
@@ -80,8 +81,11 @@ module CensorBear
80
81
 
81
82
  def load_mod_logs
82
83
  builder ||= mod_log_scope.includes(:record)
83
- builder = builder.where("content ilike ?", "%#{params[:q]}%") if params[:q].present?
84
-
84
+ builder = builder.where('content ilike ?', "%#{params[:q]}%") if params[:q].present?
85
+ if params[:filter] != 'all'
86
+ builder = builder.pending
87
+ builder = builder.unscope(where: :aasm_state).where(aasm_state: params[:state]) if params[:state].present?
88
+ end
85
89
  builder = builder.order(id: :desc)
86
90
 
87
91
  @pagy, @mod_logs = pagy(builder)
@@ -89,7 +93,6 @@ module CensorBear
89
93
 
90
94
  private
91
95
 
92
- # Use callbacks to share common setup or constraints between actions.
93
96
  def set_mod_log
94
97
  @mod_log = ModLog.find(params[:id] || params[:mod_log_id])
95
98
  end
@@ -98,7 +101,6 @@ module CensorBear
98
101
  ModLog
99
102
  end
100
103
 
101
- # Only allow a list of trusted parameters through.
102
104
  def mod_log_params
103
105
  params.require(:mod_log).permit(:record_id, :record_type, :mod_words, :user_id, :reason)
104
106
  end
@@ -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,22 +1,42 @@
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
 
8
+ validates :record_id, uniqueness: { scope: [:record_type] }
9
+
10
+ def images
11
+ return [] if record.blank?
12
+
13
+ raise NoMethodError, 'undefined censor_images for record' unless record.respond_to?(:censor_images)
14
+
15
+ record.censor_images
16
+ end
17
+
6
18
  def record_path
7
19
  return '#' if record.blank?
8
20
 
9
21
  record.censor_show_path
10
22
  end
11
23
 
12
- def delete(reason)
24
+ def remove(reason)
13
25
  return if record.blank?
14
26
  return if record.discarded?
15
27
  # ugc内容需要开软删,便于撤销&查看统计,可以定时删除统计并删除软删内容,类似回收站机制
16
- raise NoMethodError.new("undefined censor_delete for record") unless record.respond_to?(:censor_delete)
28
+ raise NoMethodError, 'undefined censor_remove for record' unless record.respond_to?(:censor_remove)
17
29
 
18
- ret = record.censor_delete
19
- handled!(reason) if ret
30
+ ret = record.censor_remove
31
+ reject!(reason) if ret
32
+ end
33
+
34
+ def undo_remove
35
+ return if record.blank?
36
+ raise NoMethodError unless record.respond_to?(:censor_undo_remove)
37
+
38
+ ret = record.censor_undo_remove
39
+ undo! if ret
20
40
  end
21
41
 
22
42
  def approve
@@ -24,7 +44,15 @@ module CensorBear
24
44
  raise NoMethodError unless record.respond_to?(:censor_approve)
25
45
 
26
46
  ret = record.censor_approve
27
- handled! if ret
47
+ pass! if ret
48
+ end
49
+
50
+ def undo_approve
51
+ return if record.blank?
52
+ raise NoMethodError unless record.respond_to?(:censor_undo_approve)
53
+
54
+ ret = record.censor_undo_approve
55
+ undo! if ret
28
56
  end
29
57
 
30
58
  def ban_user
@@ -32,23 +60,36 @@ module CensorBear
32
60
  raise NoMethodError unless record.respond_to?(:censor_ban_user)
33
61
 
34
62
  ret = record.censor_ban_user
35
- delete if ret
63
+ reject!('已被封禁该用户') if ret
36
64
  end
37
65
 
38
- scope :pending, -> { where(status: 0) }
39
- scope :handled, -> { where(status: 1) }
40
- scope :suspended, -> { where(status: 2) }
66
+ aasm do
67
+ state :pending, initial: true
68
+ state :rejected, :passed, :suspended
69
+
70
+ event :reject do
71
+ transitions from: %i[pending suspended], to: :rejected, after: Proc.new {|*args| set_reason(*args) }
72
+ end
73
+
74
+ event :pass do
75
+ transitions from: %i[pending suspended], to: :passed
76
+ end
77
+
78
+ event :suspend do
79
+ transitions from: :pending, to: :suspended, after: :unset_reason
80
+ end
41
81
 
42
- def handled!(reason = nil)
43
- update_columns(status: 1, reason: reason)
82
+ event :undo do
83
+ transitions from: %i[passed rejected], to: :pending, after: :unset_reason
84
+ end
44
85
  end
45
86
 
46
- def pend!
47
- update_columns(status: 0)
87
+ def unset_reason
88
+ update_columns(reason: nil)
48
89
  end
49
90
 
50
- def suspend!
51
- update_columns(status: 2)
91
+ def set_reason(reason)
92
+ update_columns(reason: reason)
52
93
  end
53
94
  end
54
95
  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[aliyun_check aliyun_check], %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,8 +9,15 @@
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
- %span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg font-bold"}="##{log.user_id}"
19
+ = link_to main_app.send(CensorBear.config.main_app_user_path_method.to_sym, log.user_id), data: {turbo: false} do
20
+ %span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg font-bold"}="✍️ ##{log.user_id}"
14
21
  - unless log.ip.blank?
15
22
  %span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg"}=log.ip
16
23
  - unless log.labels.blank?
@@ -19,7 +26,7 @@
19
26
  %span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg"}= log.created_at
20
27
  %div{class: "flex space-x-2 items-center"}
21
28
  %span
22
- = link_to '详情', log
29
+ = link_to '详情', log, data: { turbo: false }
23
30
  %span
24
31
  = button_to '删除', log, method: :delete, data: { confirm: 'Are you sure?' }, class: "text-red-600 rounded-md px-2 py-0.5"
25
32
  %div{class: "flex justify-between items-center"}
@@ -4,10 +4,15 @@
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[check_search check_search], %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{class: "text-sm text-gray-600"}="共 #{@pagy.count} 条"
11
+ %div{class: "flex space-x-2 items-center"}
12
+ %div{class: "text-sm text-gray-600"}="共 #{@pagy.count} 条"
13
+ = form_with(url: remove_data_logs_path, method: :delete, class: "flex items-center space-x-2") do |f|
14
+ = f.select :type, options_for_select([%w[最近3天 3days], %w[最近7天 7days], %w[最近30天 30days], %w[全部 all]], params[:type]), {include_blank: "-- 选择删除范围 --"}, class: "border text-red-600 border-red-600 rounded-md m py-0.5 px-1"
15
+ = f.submit "删除日志", class: "rounded-md px-2 py-1 text-xs bg-red-600 text-white cursor-pointer"
11
16
  %div
12
17
  - if @logs.blank?
13
18
  %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,51 @@
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: "#{mod_log.record.is_private ? 'bg-black text-white' : 'border border-black text-black' } rounded-lg text-xs p-0.5 px-1"}=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
+ - unless mod_log.labels.blank?
25
+ - mod_log.labels.each do |label|
26
+ %span{class: "border border-yellow-600 text-yellow-600 rounded-lg text-xs p-0.5"}=t("censor_log.label.#{label}")
27
+ - unless mod_log.mod_words.blank?
28
+ - mod_log.mod_words.each do |word|
29
+ %span{class: "border border-red-600 text-red-600 rounded-lg text-xs p-0.5"}="##{word}"
30
+ - unless mod_log&.record&.user_id&.blank?
31
+ = link_to main_app.send(CensorBear.config.main_app_user_path_method.to_sym, mod_log.record.user_id), data: {turbo: false} do
32
+ %span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg font-bold"}="✍️ ##{mod_log.record.user_id}"
33
+ - if mod_log.record.discarded?
34
+ %div{class: "text-gray-900 leading-relaxed"}
35
+ %del= mod_log.record.content
36
+ - else
37
+ %div{class: "text-gray-900 leading-relaxed"}= mod_log.record.content
38
+ %div
39
+ - mod_log.images.each do |img|
40
+ = image_tag img[:src], class: "w-16 rounded-md"
41
+ %div{class: "flex space-x-2 items-center justify-end"}
42
+ - if mod_log.may_suspend?
43
+ = link_to "忽略", mod_log_suspend_path(mod_log), class: "text-yellow-600 cursor-pointer"
44
+ - if mod_log.may_pass?
45
+ = link_to "通过", mod_log_pass_path(mod_log), class: "text-green-600 cursor-pointer"
46
+ - if mod_log.may_undo?
47
+ = link_to "撤销", mod_log_undo_path(mod_log), class: "text-red-600 cursor-pointer"
48
+ - if mod_log.may_reject?
49
+ = form_with(url: mod_log_reject_path(mod_log), method: :get, class: "space-x-1 flex items-center") do |f|
50
+ = f.submit "拒绝", class: "text-red-600 bg-transparent cursor-pointer"
51
+ = f.text_field :reason, value: mod_log.reason, placeholder: "不通过原因", class: "border rounded-md m py-0.5 px-1 text-sm"
52
+
@@ -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{class: "text-sm text-gray-600"}="共 #{@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[:state].blank? && params[:filter].blank? ? 'font-bold' : ''}"}
12
+ = link_to "待处理", request.params.merge(state: "", filter: "")
13
+ %div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state] == 'rejected' ? 'font-bold' : ''}"}
14
+ = link_to "被驳回", request.params.merge(state: "rejected", filter: "")
15
+ %div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state] == 'passed' ? 'font-bold' : ''}"}
16
+ = link_to "通过", request.params.merge(state: "passed", filter: "")
17
+ %div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state] == 'suspended' ? 'font-bold' : ''}"}
18
+ = link_to "暂缓处理", request.params.merge(state: "suspended", filter: "")
19
+ %div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:filter] == "all" ? 'font-bold' : ''}"}
20
+ = link_to "全部", request.params.merge(filter: "all", state: "")
11
21
  %div
12
22
  - if @mod_logs.blank?
13
23
  %div{class: "flex justify-center text-gray-500 p-8 border rounded-md"} 空空如也
@@ -12,6 +12,7 @@
12
12
  %li= link_to "审查(#{CensorBear::ModLog.pending.count})", mod_logs_path
13
13
  %li= link_to "日志", logs_path
14
14
  %li= link_to "敏感词", stop_words_path
15
+ %li= link_to "调试", debugger_path
15
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
@@ -1,4 +1,4 @@
1
- en:
1
+ "zh-CN":
2
2
  censor_log:
3
3
  action:
4
4
  mod: 审核
@@ -8,6 +8,10 @@ en:
8
8
  block: 禁用
9
9
  pass: 忽略
10
10
  review: 审核
11
+ aasm_state:
12
+ pending: 待审
13
+ handled: 已处理
14
+ suspended: 暂缓处理
11
15
  stage:
12
16
  check_search: 搜索检查
13
17
  aliyun_check: 阿里云检查
@@ -27,4 +31,4 @@ en:
27
31
  contraband: 违禁
28
32
  meaningless: 无意义
29
33
  harmful: 不良场景
30
- customized: 自定义
34
+ customized: 自定义
data/config/routes.rb CHANGED
@@ -1,12 +1,19 @@
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
- resources :logs
9
+ resources :logs do
10
+ delete :remove_data, on: :collection
11
+ end
9
12
  resources :stop_words
10
13
 
11
- root to: "stop_words#index"
14
+ get '/welcome', to: 'home#index'
15
+ get '/debugger', to: 'home#debugger'
16
+ post '/debug', to: 'home#debug'
17
+
18
+ root to: 'mod_logs#index'
12
19
  end
@@ -6,7 +6,6 @@ class CreateCensorBearModLogs < ActiveRecord::Migration[6.1]
6
6
  t.text :content
7
7
  t.string :reason
8
8
  t.string :mod_words, array: true, default: []
9
- t.integer :status, default: 0
10
9
  t.bigint :user_id
11
10
 
12
11
  t.timestamps
@@ -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
@@ -115,32 +115,46 @@ 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].include?(r['label'])
118
+ # if action == 'block' && rate >= 70 && %w[politics terrorism].include?(r['label'])
119
+ # @is_mod = true
120
+ # CensorBear.info(
121
+ # d['content'], @type, 'mod', 'aliyun_check',
122
+ # filtered_content: d['filteredContent'],
123
+ # mod_words: @mod_words,
124
+ # labels: @labels,
125
+ # ip: @ip, user_id: @user_id,
126
+ # response: response
127
+ # )
128
+ # # raise NotPassedException
129
+ # elsif action == 'block' && rate >= 90
130
+ if action == 'block'
131
+ @is_mod = true
119
132
  CensorBear.info(
120
- d['content'], @type, action, 'aliyun_check',
133
+ d['content'], @type, 'mod', 'aliyun_check',
121
134
  filtered_content: d['filteredContent'],
122
135
  mod_words: @mod_words,
123
136
  labels: @labels,
124
- ip: @ip, user_id: @user_id
137
+ ip: @ip, user_id: @user_id,
138
+ response: response
125
139
  )
126
- raise NotPassedException
127
- elsif action == 'block'
140
+ elsif action == 'review'
128
141
  @is_mod = true
129
142
  CensorBear.info(
130
- d['content'], @type, action, 'aliyun_check',
143
+ d['content'], @type, 'mod', 'aliyun_check',
131
144
  filtered_content: d['filteredContent'],
132
145
  mod_words: @mod_words,
133
146
  labels: @labels,
134
- ip: @ip, user_id: @user_id
147
+ ip: @ip, user_id: @user_id,
148
+ response: response
135
149
  )
136
- elsif action == 'review'
137
- @is_mod = true
150
+ else
138
151
  CensorBear.info(
139
- d['content'], @type, action, 'aliyun_check',
152
+ d['content'], @type, 'ignore', 'aliyun_check',
140
153
  filtered_content: d['filteredContent'],
141
154
  mod_words: @mod_words,
142
155
  labels: @labels,
143
- ip: @ip, user_id: @user_id
156
+ ip: @ip, user_id: @user_id,
157
+ response: response
144
158
  )
145
159
  end
146
160
  end
@@ -149,6 +163,7 @@ module CensorBear
149
163
 
150
164
  def concat_labels(data)
151
165
  return [] if data.blank?
166
+
152
167
  labels = []
153
168
  data.map do |d|
154
169
  next if d['label'].blank?
@@ -160,6 +175,7 @@ module CensorBear
160
175
 
161
176
  def concat_words(data)
162
177
  return [] if data.blank?
178
+
163
179
  words = []
164
180
  data.each do |d|
165
181
  next if d['contexts'].blank?
@@ -19,5 +19,6 @@ module CensorBear
19
19
  attr_accessor :aliyun_green_enable_internal
20
20
 
21
21
  attr_accessor :main_app_root_path_method
22
+ attr_accessor :main_app_user_path_method
22
23
  end
23
24
  end
@@ -1,3 +1,3 @@
1
1
  module CensorBear
2
- VERSION = '0.1.13'.freeze
2
+ VERSION = '0.1.18'.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'
@@ -25,6 +26,7 @@ module CensorBear
25
26
  @config.aliyun_green_access_key_secret = ''
26
27
  @config.aliyun_green_enable_internal = false
27
28
  @config.main_app_root_path_method = 'root_path'
29
+ @config.main_app_user_path_method = 'user_path'
28
30
 
29
31
  @config
30
32
  end
@@ -47,6 +49,7 @@ module CensorBear
47
49
  labels = options[:labels] || []
48
50
  ip = options[:ip] || nil
49
51
  user_id = options[:user_id] || nil
52
+ response = options[:response] || {}
50
53
 
51
54
  CensorBear::Log.create(
52
55
  original_content: content,
@@ -58,6 +61,7 @@ module CensorBear
58
61
  labels: labels,
59
62
  ip: ip,
60
63
  user_id: user_id,
64
+ response: response
61
65
  )
62
66
  end
63
67
 
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.13
4
+ version: 0.1.18
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-09 00:00:00.000000000 Z
11
+ date: 2021-12-13 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
@@ -100,7 +114,7 @@ dependencies:
100
114
  - - ">="
101
115
  - !ruby/object:Gem::Version
102
116
  version: '0'
103
- description: Description of CensorBear.
117
+ description: "\U0001F43B Watch your back! Young man."
104
118
  email:
105
119
  - foobar@v2up.com
106
120
  executables: []
@@ -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
@@ -193,5 +215,5 @@ requirements: []
193
215
  rubygems_version: 3.2.22
194
216
  signing_key:
195
217
  specification_version: 4
196
- summary: Summary of CensorBear.
218
+ summary: "\U0001F43B Watch your back! Young man."
197
219
  test_files: []
@@ -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 %>