diff --git a/app/models/reviewable.rb b/app/models/reviewable.rb index da0837eb9d2..7c432845a50 100644 --- a/app/models/reviewable.rb +++ b/app/models/reviewable.rb @@ -41,7 +41,7 @@ class Reviewable < ActiveRecord::Base Jobs.enqueue(:notify_reviewable, reviewable_id: self.id) if pending? end - # The gaps are in case we want more accuracy in the future + # The gaps are in case we want more precision in the future def self.priorities @priorities ||= Enum.new( low: 0, @@ -50,6 +50,16 @@ class Reviewable < ActiveRecord::Base ) end + # The gaps are in case we want more precision in the future + def self.sensitivity + @sensitivity ||= Enum.new( + disabled: 0, + low: 9, + medium: 6, + high: 3 + ) + end + def self.statuses @statuses ||= Enum.new( pending: 0, @@ -173,6 +183,29 @@ class Reviewable < ActiveRecord::Base end end + def self.sensitivity_score(sensitivity, scale: 1.0) + return Float::MAX if sensitivity == 0 + + ratio = sensitivity / Reviewable.sensitivity[:low].to_f + high = PluginStore.get('reviewables', "priority_#{Reviewable.priorities[:high]}") + return (10.0 * scale) if high.nil? + + # We want this to be hard to reach + (high.to_f * ratio) * scale + end + + def self.score_to_auto_close_topic + sensitivity_score(SiteSetting.auto_close_topic_sensitivity, scale: 2.5) + end + + def self.spam_score_to_silence_new_user + sensitivity_score(SiteSetting.silence_new_user_sensitivity, scale: 0.6) + end + + def self.score_required_to_hide_post + sensitivity_score(SiteSetting.hide_post_sensitivity) + end + def self.min_score_for_priority(priority = nil) priority ||= SiteSetting.reviewable_default_visibility id = Reviewable.priorities[priority.to_sym] diff --git a/app/models/reviewable_sensitivity_setting.rb b/app/models/reviewable_sensitivity_setting.rb new file mode 100644 index 00000000000..1d83c46a0de --- /dev/null +++ b/app/models/reviewable_sensitivity_setting.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_dependency 'enum_site_setting' + +class ReviewableSensitivitySetting < EnumSiteSetting + + def self.valid_value?(val) + values.any? { |v| v[:value].to_s == val.to_s } + end + + def self.values + Reviewable.sensitivity.map do |p| + { name: I18n.t("reviewables.sensitivity.#{p[0]}"), value: p[1] } + end + end + + def self.translate_names? + false + end + +end + diff --git a/app/models/topic.rb b/app/models/topic.rb index 9a45a328cec..c239c876c3d 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -1373,7 +1373,7 @@ class Topic < ActiveRecord::Base .pluck("COUNT(DISTINCT reviewable_scores.user_id), COALESCE(SUM(reviewable_scores.score), 0.0)") .first - scores[0] >= SiteSetting.num_flaggers_to_close_topic && scores[1] >= SiteSetting.score_to_auto_close_topic + scores[0] >= SiteSetting.num_flaggers_to_close_topic && scores[1] >= Reviewable.score_to_auto_close_topic end def update_category_topic_count_by(num) diff --git a/app/services/spam_rule/auto_silence.rb b/app/services/spam_rule/auto_silence.rb index accebe1e341..81acb86cd2e 100644 --- a/app/services/spam_rule/auto_silence.rb +++ b/app/services/spam_rule/auto_silence.rb @@ -24,14 +24,9 @@ class SpamRule::AutoSilence return false if @user.staged? return false if @user.has_trust_level?(TrustLevel[1]) - if SiteSetting.spam_score_to_silence_new_user > 0 && - SiteSetting.num_users_to_silence_new_user > 0 && - user_spam_stats.total_spam_score >= SiteSetting.spam_score_to_silence_new_user && - user_spam_stats.spam_user_count >= SiteSetting.num_users_to_silence_new_user - return true - end - - false + SiteSetting.num_users_to_silence_new_user > 0 && + user_spam_stats.total_spam_score >= Reviewable.spam_score_to_silence_new_user && + user_spam_stats.spam_user_count >= SiteSetting.num_users_to_silence_new_user end def user_spam_stats diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 905c77a3c5c..fbcd61ebb32 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1388,7 +1388,10 @@ en: anon_polling_interval: "How often should anonymous clients poll in milliseconds" background_polling_interval: "How often should the clients poll in milliseconds (when the window is in the background)" - score_required_to_hide_post: "Score threshold that causes a post to be automatically hidden and message sent to the user (0 to disable)" + hide_post_sensitivity: "The likelyhood that a flagged post will be hidden" + silence_new_user_sensitivity: "The likelyhood that a new user will be silenced based on spam flags" + auto_close_topic_sensitivity: "The likelyhood that a flagged topic will be automatically closed" + cooldown_minutes_after_hiding_posts: "Number of minutes a user must wait before they can edit a post hidden via community flagging" max_topics_in_first_day: "The maximum number of topics a user is allowed to create in the 24 hour period after creating their first post" @@ -1397,8 +1400,6 @@ en: tl2_additional_likes_per_day_multiplier: "Increase limit of likes per day for tl2 (member) by multiplying with this number" tl3_additional_likes_per_day_multiplier: "Increase limit of likes per day for tl3 (regular) by multiplying with this number" tl4_additional_likes_per_day_multiplier: "Increase limit of likes per day for tl4 (leader) by multiplying with this number" - - spam_score_to_silence_new_user: "If a new user's posts receive this score from num_users_to_silence_new_user different users, hide all their posts and prevent future posting. 0 to disable." num_users_to_silence_new_user: "If a new user's posts get num_spam_flags_to_silence_new_user spam flags from this many different users, hide all their posts and prevent future posting. 0 to disable." num_tl3_flags_to_silence_new_user: "If a new user's posts get this many flags from num_tl3_users_to_silence_new_user different trust level 3 users, hide all their posts and prevent future posting. 0 to disable." num_tl3_users_to_silence_new_user: "If a new user's posts get num_tl3_flags_to_silence_new_user flags from this many different trust level 3 users, hide all their posts and prevent future posting. 0 to disable." @@ -1745,7 +1746,6 @@ en: max_age_unmatched_ips: "Delete unmatched screened IP entries after (N) days." num_flaggers_to_close_topic: "Minimum number of unique flaggers that is required to automatically pause a topic for intervention" - score_to_auto_close_topic: "The total flag score of that is required to automatically pause a topic for intervention" num_hours_to_close_topic: "Number of hours to pause a topic for intervention." auto_respond_to_flag_actions: "Enable automatic reply when disposing a flag." @@ -4418,7 +4418,11 @@ en: low: "Low" medium: "Medium" high: "High" - + sensitivity: + disabled: "Disabled" + low: "Low" + medium: "Medium" + high: "High" must_claim: "You must claim items before acting on them." user_claimed: "This item has been claimed by another user." missing_version: "You must supply a version parameter" diff --git a/config/site_settings.yml b/config/site_settings.yml index ac2fcf12b3d..21f570b44f7 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1366,9 +1366,15 @@ onebox: spam: add_rel_nofollow_to_user_content: true - score_required_to_hide_post: 10 + hide_post_sensitivity: + type: enum + enum: "ReviewableSensitivitySetting" + default: 6 cooldown_minutes_after_hiding_posts: 10 - spam_score_to_silence_new_user: 6.0 + silence_new_user_sensitivity: + type: enum + enum: "ReviewableSensitivitySetting" + default: 9 num_users_to_silence_new_user: 3 notify_mods_when_user_silenced: false flag_sockpuppets: false @@ -1384,7 +1390,10 @@ spam: max_age_unmatched_emails: 365 max_age_unmatched_ips: 365 num_flaggers_to_close_topic: 5 - score_to_auto_close_topic: 25.0 + auto_close_topic_sensitivity: + type: enum + enum: "ReviewableSensitivitySetting" + default: 3 num_hours_to_close_topic: default: 4 min: 1 diff --git a/lib/post_action_creator.rb b/lib/post_action_creator.rb index d38b30c3a75..5d7d92aee17 100644 --- a/lib/post_action_creator.rb +++ b/lib/post_action_creator.rb @@ -166,9 +166,9 @@ private @post.user&.trust_level != TrustLevel[4] @post.hide!(@post_action_type_id, Post.hidden_reasons[:flagged_by_tl4_user]) - elsif SiteSetting.score_required_to_hide_post > 0 + else score = ReviewableFlaggedPost.find_by(target: @post)&.score || 0 - if score >= SiteSetting.score_required_to_hide_post + if score >= Reviewable.score_required_to_hide_post @post.hide!(@post_action_type_id) end end diff --git a/spec/integration/spam_rules_spec.rb b/spec/integration/spam_rules_spec.rb index d5c482e38b0..6f325a2ae7d 100644 --- a/spec/integration/spam_rules_spec.rb +++ b/spec/integration/spam_rules_spec.rb @@ -12,8 +12,9 @@ describe "spam rules for users" do fab!(:user2) { Fabricate(:user) } before do - SiteSetting.score_required_to_hide_post = 0 - SiteSetting.spam_score_to_silence_new_user = 4.0 + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:disabled] + Reviewable.set_priorities(high: 4.0) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:low] SiteSetting.num_users_to_silence_new_user = 2 end @@ -73,9 +74,10 @@ describe "spam rules for users" do end end - context 'score_required_to_hide_post takes effect too' do + context 'hide_post_sensitivity' do it 'should silence the spammer' do - SiteSetting.score_required_to_hide_post = 2.0 + Reviewable.set_priorities(high: 2.0) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:low] PostActionCreator.create(user2, spam_post, :spam) expect(spammer.reload).to be_silenced expect(Guardian.new(spammer).can_create_topic?(nil)).to be false diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb index 7b57be3e918..5c13d8b9174 100644 --- a/spec/models/post_action_spec.rb +++ b/spec/models/post_action_spec.rb @@ -517,7 +517,8 @@ describe PostAction do mod = Fabricate(:moderator) post = Fabricate(:post, user: mod) - SiteSetting.score_required_to_hide_post = 2.0 + Reviewable.set_priorities(high: 2.0) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:low] Discourse.stubs(:site_contact_user).returns(admin) PostActionCreator.spam(eviltrout, post) @@ -531,7 +532,8 @@ describe PostAction do mod = Fabricate(:moderator) post = Fabricate(:post, user: mod) - SiteSetting.score_required_to_hide_post = 8.0 + Reviewable.set_priorities(high: 8.0) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:low] Discourse.stubs(:site_contact_user).returns(admin) PostActionCreator.spam(eviltrout, post) @@ -547,7 +549,8 @@ describe PostAction do post = create_post walterwhite = Fabricate(:walter_white) - SiteSetting.score_required_to_hide_post = 3.0 + Reviewable.set_priorities(high: 3.0) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:low] Discourse.stubs(:site_contact_user).returns(admin) PostActionCreator.spam(eviltrout, post) @@ -707,8 +710,9 @@ describe PostAction do fab!(:flagger2) { Fabricate(:user) } before do - SiteSetting.score_required_to_hide_post = 0 - SiteSetting.score_to_auto_close_topic = 12.0 + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:disabled] + Reviewable.set_priorities(high: 4.5) + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:low] SiteSetting.num_flaggers_to_close_topic = 2 SiteSetting.num_hours_to_close_topic = 1 end @@ -769,7 +773,8 @@ describe PostAction do freeze_time SiteSetting.num_flaggers_to_close_topic = 1 - SiteSetting.score_to_auto_close_topic = 2.0 + Reviewable.set_priorities(high: 0.5) + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:low] post = Fabricate(:post, topic: topic) PostActionCreator.spam(flagger1, post) @@ -792,7 +797,8 @@ describe PostAction do freeze_time timer.execute_at SiteSetting.num_flaggers_to_close_topic = 10 - SiteSetting.score_to_auto_close_topic = 20.0 + Reviewable.set_priorities(high: 10.0) + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:low] Jobs::ToggleTopicClosed.new.execute(topic_timer_id: timer.id, state: false) diff --git a/spec/models/reviewable_queued_post_spec.rb b/spec/models/reviewable_queued_post_spec.rb index 11cf5ac9ef2..016a348579e 100644 --- a/spec/models/reviewable_queued_post_spec.rb +++ b/spec/models/reviewable_queued_post_spec.rb @@ -87,7 +87,8 @@ RSpec.describe ReviewableQueuedPost, type: :model do newuser.update!(trust_level: 0) post = Fabricate(:post, user: newuser) PostActionCreator.spam(moderator, post) - SiteSetting.spam_score_to_silence_new_user = 1 + Reviewable.set_priorities(high: 1.0) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:low] SiteSetting.num_users_to_silence_new_user = 1 expect(Guardian.new(newuser).can_create_post?(topic)).to eq(false) diff --git a/spec/models/reviewable_spec.rb b/spec/models/reviewable_spec.rb index bd0d40233d5..afcbcdfe985 100644 --- a/spec/models/reviewable_spec.rb +++ b/spec/models/reviewable_spec.rb @@ -282,6 +282,73 @@ RSpec.describe Reviewable, type: :model do end end + context ".score_required_to_hide_post" do + it "returns 10 if we can't calculated any percentiles" do + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:low] + expect(Reviewable.score_required_to_hide_post).to eq(10.0) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:medium] + expect(Reviewable.score_required_to_hide_post).to eq(10.0) + end + + it "returns a fraction of the high percentile" do + Reviewable.set_priorities(high: 100.0) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:disabled] + expect(Reviewable.score_required_to_hide_post.to_f.truncate(2)).to eq(Float::MAX) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:low] + expect(Reviewable.score_required_to_hide_post.to_f.truncate(2)).to eq(100.0) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:medium] + expect(Reviewable.score_required_to_hide_post.to_f.truncate(2)).to eq(66.66) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:high] + expect(Reviewable.score_required_to_hide_post.to_f.truncate(2)).to eq(33.33) + end + end + + context ".spam_score_to_silence_new_user" do + it "returns 6 if we can't calculated any percentiles" do + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:low] + expect(Reviewable.spam_score_to_silence_new_user).to eq(6.0) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:medium] + expect(Reviewable.spam_score_to_silence_new_user).to eq(6.0) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:high] + expect(Reviewable.spam_score_to_silence_new_user).to eq(6.0) + end + + it "returns a fraction of the high percentile" do + Reviewable.set_priorities(high: 100.0) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:disabled] + expect(Reviewable.spam_score_to_silence_new_user.to_f.truncate(2)).to eq(Float::MAX) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:low] + expect(Reviewable.spam_score_to_silence_new_user.to_f.truncate(2)).to eq(60.0) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:medium] + expect(Reviewable.spam_score_to_silence_new_user.to_f.truncate(2)).to eq(39.99) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:high] + expect(Reviewable.spam_score_to_silence_new_user.to_f.truncate(2)).to eq(19.99) + end + end + + context ".score_to_auto_close_topic" do + it "returns 25 if we can't calculated any percentiles" do + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:low] + expect(Reviewable.score_to_auto_close_topic).to eq(25.0) + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:medium] + expect(Reviewable.score_to_auto_close_topic).to eq(25.0) + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:high] + expect(Reviewable.score_to_auto_close_topic).to eq(25.0) + end + + it "returns a fraction of the high percentile" do + Reviewable.set_priorities(high: 100.0) + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:disabled] + expect(Reviewable.score_to_auto_close_topic.to_f.truncate(2)).to eq(Float::MAX) + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:low] + expect(Reviewable.score_to_auto_close_topic.to_f.truncate(2)).to eq(250.0) + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:medium] + expect(Reviewable.score_to_auto_close_topic.to_f.truncate(2)).to eq(166.66) + SiteSetting.auto_close_topic_sensitivity = Reviewable.sensitivity[:high] + expect(Reviewable.score_to_auto_close_topic.to_f.truncate(2)).to eq(83.33) + end + end + context "priorities" do it "returns 0 for unknown priorities" do expect(Reviewable.min_score_for_priority(:wat)).to eq(0.0) diff --git a/spec/requests/admin/flags_controller_spec.rb b/spec/requests/admin/flags_controller_spec.rb index 9ce24775dbe..f21f36f5005 100644 --- a/spec/requests/admin/flags_controller_spec.rb +++ b/spec/requests/admin/flags_controller_spec.rb @@ -115,7 +115,8 @@ RSpec.describe Admin::FlagsController do context '#disagree' do it "unhides the post and unsilences the user if disagreed" do - SiteSetting.spam_score_to_silence_new_user = 1.0 + Reviewable.set_priorities(high: 1.0) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:low] SiteSetting.num_users_to_silence_new_user = 1 new_user = Fabricate(:newuser) diff --git a/spec/services/auto_silence_spec.rb b/spec/services/auto_silence_spec.rb index fa722589a36..0dcbe0bf252 100644 --- a/spec/services/auto_silence_spec.rb +++ b/spec/services/auto_silence_spec.rb @@ -5,8 +5,9 @@ require 'rails_helper' describe SpamRule::AutoSilence do before do - SiteSetting.score_required_to_hide_post = 0 # never - SiteSetting.spam_score_to_silence_new_user = 4.0 + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:disabled] + Reviewable.set_priorities(high: 4.0) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:low] SiteSetting.num_users_to_silence_new_user = 2 end @@ -21,7 +22,8 @@ describe SpamRule::AutoSilence do end it 'delivers punishment when user should be silenced' do - SiteSetting.spam_score_to_silence_new_user = 2.0 + Reviewable.set_priorities(high: 2.0) + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:low] SiteSetting.num_users_to_silence_new_user = 1 PostActionCreator.spam(Discourse.system_user, post) subject.perform @@ -194,8 +196,8 @@ describe SpamRule::AutoSilence do expect(subject.should_autosilence?).to eq(false) end - it 'returns false if spam_score_to_silence_new_user is 0' do - SiteSetting.spam_score_to_silence_new_user = 0 + it 'returns false if silence_new_user_sensitivity is disabled' do + SiteSetting.silence_new_user_sensitivity = Reviewable.sensitivity[:disabled] PostActionCreator.spam(flagger, post) PostActionCreator.spam(flagger2, post) expect(subject.should_autosilence?).to eq(false) diff --git a/spec/services/post_alerter_spec.rb b/spec/services/post_alerter_spec.rb index 203c472d10d..1539025c958 100644 --- a/spec/services/post_alerter_spec.rb +++ b/spec/services/post_alerter_spec.rb @@ -128,7 +128,8 @@ describe PostAlerter do coding_horror = Fabricate(:coding_horror) PostActionNotifier.enable - SiteSetting.score_required_to_hide_post = 4.0 + Reviewable.set_priorities(high: 4.0) + SiteSetting.hide_post_sensitivity = Reviewable.sensitivity[:low] PostActionCreator.spam(evil_trout, post) PostActionCreator.spam(walterwhite, post)