decidim-spam_detection 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/jobs/decidim/spam_detection/notify_admins.rb +19 -0
- data/app/mailers/decidim/spam_detection/spam_detection_mailer.rb +19 -0
- data/app/services/decidim/spam_detection/mark_users_service.rb +23 -3
- data/app/views/decidim/spam_detection/spam_detection_mailer/notify_detection.html.erb +14 -0
- data/config/i18n-tasks.yml +1 -1
- data/config/locales/ca.yml +16 -0
- data/config/locales/en.yml +10 -0
- data/config/locales/es.yml +16 -0
- data/config/locales/fr.yml +16 -0
- data/lib/decidim/spam_detection/abstract_spam_user_command.rb +12 -3
- data/lib/decidim/spam_detection/block_spam_user_command.rb +48 -16
- data/lib/decidim/spam_detection/report_spam_user_command.rb +32 -18
- data/lib/decidim/spam_detection/version.rb +1 -1
- metadata +8 -3
- data/config/assets.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38cc3310b2ed4c7e48cadea2c1bf9f0ff86d8f28bad7b3e04398d5922598442d
|
4
|
+
data.tar.gz: be191b16b6827c2e6963b72578fe899d1feace76156b87cb246928dc869619d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef0c0fcc58dfdb427725cc53ad5a3fa2829b0482180e4b311427a483345b97f09ec90d55ffdcfaf778903ed00913bf17976ae56e2a9fb13deb17605cf45c0425
|
7
|
+
data.tar.gz: 9d6b782f0b236cd000b1bf361b5d83524c037da23fa22aa779321f5c09fafbc849d45d713c965b2e9a6eb2a37d465565e91b33bfcff0a1e73436065ee76265b6
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SpamDetection
|
5
|
+
class NotifyAdmins < ApplicationJob
|
6
|
+
queue_as :default
|
7
|
+
|
8
|
+
def perform(results_hash)
|
9
|
+
results_hash.each do |id, result|
|
10
|
+
next if result.keys == [:nothing]
|
11
|
+
|
12
|
+
Decidim::Organization.find(id).admins.each do |admin|
|
13
|
+
Decidim::SpamDetection::SpamDetectionMailer.notify_detection(admin, result).deliver_later
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SpamDetection
|
5
|
+
class SpamDetectionMailer < Decidim::ApplicationMailer
|
6
|
+
def notify_detection(user, results)
|
7
|
+
with_user(user) do
|
8
|
+
@reported_count = results[:reported_user]
|
9
|
+
@blocked_count = results[:blocked_user]
|
10
|
+
@organization = user.organization
|
11
|
+
@user = user
|
12
|
+
|
13
|
+
subject = I18n.t("notify_detection.subject", scope: "decidim.spam_detection_mailer")
|
14
|
+
mail(to: @user.email, subject: subject)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -27,7 +27,7 @@ module Decidim
|
|
27
27
|
.where(admin: false, blocked: false, deleted_at: nil)
|
28
28
|
.where("(extended_data #> '{spam_detection, unreported_at}') is null")
|
29
29
|
.where("(extended_data #> '{spam_detection, unblocked_at}') is null")
|
30
|
-
@results =
|
30
|
+
@results = {}
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.call
|
@@ -38,11 +38,15 @@ module Decidim
|
|
38
38
|
spam_probability_array = Decidim::SpamDetection::ApiProxy.request(cleaned_users)
|
39
39
|
|
40
40
|
mark_spam_users(merge_response_with_users(spam_probability_array))
|
41
|
+
notify_admins!
|
41
42
|
end
|
42
43
|
|
43
44
|
def mark_spam_users(probability_array)
|
44
45
|
probability_array.each do |probability_hash|
|
45
|
-
|
46
|
+
result = Decidim::SpamDetection::SpamUserCommandAdapter.call(probability_hash).result
|
47
|
+
organization_id = probability_hash["decidim_organization_id"]
|
48
|
+
|
49
|
+
add_to_results(organization_id.to_s, result)
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
@@ -56,7 +60,23 @@ module Decidim
|
|
56
60
|
end
|
57
61
|
|
58
62
|
def status
|
59
|
-
@results.
|
63
|
+
@results.each_with_object({}) do |result, hash|
|
64
|
+
hash[result[0]] = result[1].tally
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def notify_admins!
|
69
|
+
Decidim::SpamDetection::NotifyAdmins.perform_later(status)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def add_to_results(organization_id, result)
|
75
|
+
if @results[organization_id]
|
76
|
+
@results[organization_id] << result
|
77
|
+
else
|
78
|
+
@results[organization_id] = [result]
|
79
|
+
end
|
60
80
|
end
|
61
81
|
end
|
62
82
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<p class="email-greeting"><%= t(".hello", name: @user.name) %></p>
|
2
|
+
<p><%= t(".intro") %></p>
|
3
|
+
|
4
|
+
<% if @reported_count.present? %>
|
5
|
+
<p>
|
6
|
+
<%= t(".reported_count", count: @reported_count) %>
|
7
|
+
</p>
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
<% if @blocked_count.present? %>
|
11
|
+
<p>
|
12
|
+
<%= t(".blocked_count", count: @blocked_count) %>
|
13
|
+
</p>
|
14
|
+
<% end %>
|
data/config/i18n-tasks.yml
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
ca:
|
3
|
+
decidim:
|
4
|
+
components:
|
5
|
+
spam_detection:
|
6
|
+
name: SpamDetection
|
7
|
+
spam_detection:
|
8
|
+
spam_detection_mailer:
|
9
|
+
notify_detection:
|
10
|
+
blocked_count: blocked_count %{count}
|
11
|
+
hello: Hello %{name}
|
12
|
+
intro: Aquí teniu l'informe de la tasca de detecció de correu brossa
|
13
|
+
reported_count: reported_count %{count}
|
14
|
+
spam_detection_mailer:
|
15
|
+
notify_detection:
|
16
|
+
subject: Resum de detecció de correu brossa
|
data/config/locales/en.yml
CHANGED
@@ -4,3 +4,13 @@ en:
|
|
4
4
|
components:
|
5
5
|
spam_detection:
|
6
6
|
name: SpamDetection
|
7
|
+
spam_detection:
|
8
|
+
spam_detection_mailer:
|
9
|
+
notify_detection:
|
10
|
+
blocked_count: 'Blocked users count: %{count}'
|
11
|
+
hello: Hello %{name}
|
12
|
+
intro: Here is the report of the spam detection task
|
13
|
+
reported_count: 'Reported users count: %{count}'
|
14
|
+
spam_detection_mailer:
|
15
|
+
notify_detection:
|
16
|
+
subject: Spam detection digest
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
es:
|
3
|
+
decidim:
|
4
|
+
components:
|
5
|
+
spam_detection:
|
6
|
+
name: SpamDetection
|
7
|
+
spam_detection:
|
8
|
+
spam_detection_mailer:
|
9
|
+
notify_detection:
|
10
|
+
blocked_count: blocked_count %{count}
|
11
|
+
hello: Hello %{name}
|
12
|
+
intro: Here is the report of the spam detection task
|
13
|
+
reported_count: reported_count %{count}
|
14
|
+
spam_detection_mailer:
|
15
|
+
notify_detection:
|
16
|
+
subject: Subject
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
fr:
|
3
|
+
decidim:
|
4
|
+
components:
|
5
|
+
spam_detection:
|
6
|
+
name: SpamDetection
|
7
|
+
spam_detection:
|
8
|
+
spam_detection_mailer:
|
9
|
+
notify_detection:
|
10
|
+
blocked_count: blocked_count %{count}
|
11
|
+
hello: Hello %{name}
|
12
|
+
intro: Here is the report of the spam detection task
|
13
|
+
reported_count: reported_count %{count}
|
14
|
+
spam_detection_mailer:
|
15
|
+
notify_detection:
|
16
|
+
subject: Subject
|
@@ -24,6 +24,14 @@ module Decidim
|
|
24
24
|
raise NotImplementedError
|
25
25
|
end
|
26
26
|
|
27
|
+
def reason
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def details
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
27
35
|
def moderation_user
|
28
36
|
moderation_admin_params = {
|
29
37
|
name: SPAM_USER[:name],
|
@@ -56,9 +64,10 @@ module Decidim
|
|
56
64
|
end
|
57
65
|
|
58
66
|
def add_spam_detection_metadata!(metadata)
|
59
|
-
@user.
|
60
|
-
|
61
|
-
|
67
|
+
@user.extended_data = @user.extended_data
|
68
|
+
.dup
|
69
|
+
.deep_merge("spam_detection" => metadata)
|
70
|
+
@user.save(validate: false)
|
62
71
|
end
|
63
72
|
end
|
64
73
|
end
|
@@ -9,28 +9,60 @@ module Decidim
|
|
9
9
|
prepend Decidim::SpamDetection::Command
|
10
10
|
|
11
11
|
def call
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
ActiveRecord::Base.transaction do
|
13
|
+
create_user_moderation
|
14
|
+
block!
|
15
|
+
register_justification!
|
16
|
+
notify_user!
|
17
|
+
add_spam_detection_metadata!({ "blocked_at" => Time.current, "spam_probability" => @probability })
|
18
|
+
end
|
15
19
|
|
16
|
-
|
17
|
-
user = @user
|
20
|
+
Rails.logger.info("User with id #{@user["id"]} was blocked for spam with a probability of #{@probability}%")
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
form.define_singleton_method(:blocking_user) { moderator }
|
22
|
-
|
23
|
-
Decidim::Admin::BlockUser.call(form)
|
22
|
+
:ok
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
"blocked_at" => Time.current,
|
27
|
-
"spam_probability" => @probability
|
28
|
-
})
|
25
|
+
private
|
29
26
|
|
27
|
+
def create_user_moderation
|
30
28
|
@user.create_user_moderation
|
31
|
-
|
29
|
+
end
|
32
30
|
|
33
|
-
|
31
|
+
def register_justification!
|
32
|
+
UserBlock.create!(justification: reason, user: @user, blocking_user: @moderator)
|
33
|
+
end
|
34
|
+
|
35
|
+
def notify_user!
|
36
|
+
Decidim::BlockUserJob.perform_later(@user, reason)
|
37
|
+
end
|
38
|
+
|
39
|
+
def block!
|
40
|
+
Decidim.traceability.perform_action!(
|
41
|
+
"block",
|
42
|
+
@user,
|
43
|
+
@moderator,
|
44
|
+
extra: {
|
45
|
+
reportable_type: @user.class.name,
|
46
|
+
current_justification: reason
|
47
|
+
},
|
48
|
+
resource: {
|
49
|
+
# Make sure the action log entry gets the original user name instead
|
50
|
+
# of "Blocked user". Otherwise the log entries would show funny
|
51
|
+
# messages such as "Mr. Admin blocked user Blocked user"-
|
52
|
+
title: @user.name
|
53
|
+
}
|
54
|
+
) do
|
55
|
+
@user.blocked = true
|
56
|
+
@user.blocked_at = Time.current
|
57
|
+
@user.blocking = @current_blocking
|
58
|
+
@user.extended_data["user_name"] = @user.name
|
59
|
+
@user.name = "Blocked user"
|
60
|
+
@user.save!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def reason
|
65
|
+
"The user was blocked because of a high spam probability by Decidim spam detection bot with a probability of #{@probability}%"
|
34
66
|
end
|
35
67
|
end
|
36
68
|
end
|
@@ -9,29 +9,43 @@ module Decidim
|
|
9
9
|
prepend Decidim::SpamDetection::Command
|
10
10
|
|
11
11
|
def call
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
ActiveRecord::Base.transaction do
|
13
|
+
find_or_create_moderation!
|
14
|
+
create_report!
|
15
|
+
update_report_count!
|
16
|
+
add_spam_detection_metadata!({ "reported_at" => Time.current, "spam_probability" => @probability })
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
moderator = @moderator
|
19
|
-
user = @user
|
19
|
+
Rails.logger.info("User with id #{@user.id} was reported for spam with a probability of #{@probability}%")
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
report.define_singleton_method(:current_user) { moderator }
|
24
|
-
report.define_singleton_method(:reportable) { user }
|
25
|
-
report.call
|
21
|
+
:ok
|
22
|
+
end
|
26
23
|
|
27
|
-
|
28
|
-
"reported_at" => Time.current,
|
29
|
-
"spam_probability" => @probability
|
30
|
-
})
|
24
|
+
private
|
31
25
|
|
32
|
-
|
26
|
+
def reason
|
27
|
+
"spam"
|
28
|
+
end
|
33
29
|
|
34
|
-
|
30
|
+
def details
|
31
|
+
"The user was marked as spam by Decidim spam detection bot with a probability of #{@probability}%"
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_or_create_moderation!
|
35
|
+
@moderation = UserModeration.find_or_create_by!(user: @user)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_report!
|
39
|
+
@report = UserReport.create!(
|
40
|
+
moderation: @moderation,
|
41
|
+
user: @moderator,
|
42
|
+
reason: reason,
|
43
|
+
details: details
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_report_count!
|
48
|
+
@moderation.update!(report_count: @moderation.report_count + 1)
|
35
49
|
end
|
36
50
|
end
|
37
51
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: decidim-spam_detection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Armand Fardeau
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-02-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: decidim-core
|
@@ -40,10 +40,15 @@ files:
|
|
40
40
|
- app/commands/decidim/admin/unblock_user.rb
|
41
41
|
- app/commands/decidim/admin/unreport_user.rb
|
42
42
|
- app/jobs/decidim/spam_detection/mark_users_job.rb
|
43
|
+
- app/jobs/decidim/spam_detection/notify_admins.rb
|
44
|
+
- app/mailers/decidim/spam_detection/spam_detection_mailer.rb
|
43
45
|
- app/services/decidim/spam_detection/mark_users_service.rb
|
44
|
-
-
|
46
|
+
- app/views/decidim/spam_detection/spam_detection_mailer/notify_detection.html.erb
|
45
47
|
- config/i18n-tasks.yml
|
48
|
+
- config/locales/ca.yml
|
46
49
|
- config/locales/en.yml
|
50
|
+
- config/locales/es.yml
|
51
|
+
- config/locales/fr.yml
|
47
52
|
- lib/decidim/spam_detection.rb
|
48
53
|
- lib/decidim/spam_detection/abstract_spam_user_command.rb
|
49
54
|
- lib/decidim/spam_detection/admin.rb
|
data/config/assets.rb
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
base_path = File.expand_path("..", __dir__)
|
4
|
-
|
5
|
-
Decidim::Webpacker.register_path("#{base_path}/app/packs")
|
6
|
-
Decidim::Webpacker.register_entrypoints(
|
7
|
-
decidim_spam_detection: "#{base_path}/app/packs/entrypoints/decidim_spam_detection.js"
|
8
|
-
)
|
9
|
-
Decidim::Webpacker.register_stylesheet_import("stylesheets/decidim/spam_detection/spam_detection")
|