censor_bear 0.1.13 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
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 %>