From 62187caa673eb2b32c8390dd3a9ad6281a9a0790 Mon Sep 17 00:00:00 2001 From: gasoved Date: Fri, 4 Mar 2022 23:38:16 +0300 Subject: [PATCH] feat: post auto flagging on downvotes #10029 (#10367) * feat: post auto flagging on downvotes * fix: just get one admin --- install/data/defaults.json | 1 + .../en-GB/admin/settings/reputation.json | 1 + public/language/en-GB/flags.json | 3 ++- src/cli/user.js | 2 +- src/flags.js | 16 +++++++++------- src/posts/votes.js | 9 +++++++++ src/user/index.js | 4 ++++ src/views/admin/settings/reputation.tpl | 4 ++++ 8 files changed, 31 insertions(+), 9 deletions(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index 2eb0a15bb1..952702a3fb 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -87,6 +87,7 @@ "min:rep:aboutme": 0, "min:rep:signature": 0, "flags:limitPerTarget": 0, + "flags:autoFlagOnDownvoteThreshold": 0, "notificationType_upvote": "notification", "notificationType_new-topic": "notification", "notificationType_new-reply": "notification", diff --git a/public/language/en-GB/admin/settings/reputation.json b/public/language/en-GB/admin/settings/reputation.json index 7cfa636521..8ea8cda4c1 100644 --- a/public/language/en-GB/admin/settings/reputation.json +++ b/public/language/en-GB/admin/settings/reputation.json @@ -18,5 +18,6 @@ "flags.limit-per-target": "Maximum number of times something can be flagged", "flags.limit-per-target-placeholder": "Default: 0", "flags.limit-per-target-help": "When a post or user is flagged multiple times, each additional flag is considered a "report" and added to the original flag. Set this option to a number other than zero to limit the number of reports an item can receive.", + "flags.auto-flag-on-downvote-threshold": "Number of downvotes to auto flag posts (Set to 0 to disable, default: 0)", "flags.auto-resolve-on-ban": "Automatically resolve all of a user's tickets when they are banned" } \ No newline at end of file diff --git a/public/language/en-GB/flags.json b/public/language/en-GB/flags.json index 5bd46b6b1d..d526bd6e25 100644 --- a/public/language/en-GB/flags.json +++ b/public/language/en-GB/flags.json @@ -81,5 +81,6 @@ "bulk-actions": "Bulk Actions", "bulk-resolve": "Resolve Flag(s)", "bulk-success": "%1 flags updated", - "flagged-timeago-readable": "Flagged (%2)" + "flagged-timeago-readable": "Flagged (%2)", + "auto-flagged": "[Auto Flagged] Received %1 downvotes." } \ No newline at end of file diff --git a/src/cli/user.js b/src/cli/user.js index 097ff8bd3b..bbd747865f 100644 --- a/src/cli/user.js +++ b/src/cli/user.js @@ -101,7 +101,7 @@ async function execute(cmd, args) { function UserCmdHelpers() { async function getAdminUidOrFail() { - const adminUid = (await db.getSortedSetMembers('group:administrators:members')).reverse()[0]; + const adminUid = await user.getFirstAdminUid(); if (!adminUid) { const err = new Error('An admin account does not exists to execute the operation.'); err.name = 'UserError'; diff --git a/src/flags.js b/src/flags.js index 60543ae1ce..990cced428 100644 --- a/src/flags.js +++ b/src/flags.js @@ -377,7 +377,7 @@ Flags.deleteNote = async function (flagId, datetime) { await db.sortedSetRemove(`flag:${flagId}:notes`, note[0]); }; -Flags.create = async function (type, id, uid, reason, timestamp) { +Flags.create = async function (type, id, uid, reason, timestamp, forceFlag = false) { let doHistoryAppend = false; if (!timestamp) { timestamp = Date.now(); @@ -387,14 +387,14 @@ Flags.create = async function (type, id, uid, reason, timestamp) { // Sanity checks Flags.exists(type, id, uid), Flags.targetExists(type, id), - Flags.canFlag(type, id, uid), + Flags.canFlag(type, id, uid, forceFlag), Flags.targetFlagged(type, id), // Extra data for zset insertion Flags.getTargetUid(type, id), Flags.getTargetCid(type, id), ]); - if (flagExists) { + if (!forceFlag && flagExists) { throw new Error(`[[error:${type}-already-flagged]]`); } else if (!targetExists) { throw new Error('[[error:invalid-data]]'); @@ -499,9 +499,9 @@ Flags.exists = async function (type, id, uid) { return await db.isSortedSetMember('flags:hash', [type, id, uid].join(':')); }; -Flags.canFlag = async function (type, id, uid) { +Flags.canFlag = async function (type, id, uid, skipLimitCheck = false) { const limit = meta.config['flags:limitPerTarget']; - if (limit > 0) { + if (!skipLimitCheck && limit > 0) { const score = await db.sortedSetScore('flags:byTarget', `${type}:${id}`); if (score >= limit) { throw new Error(`[[error:${type}-flagged-too-many-times]]`); @@ -729,7 +729,7 @@ Flags.appendNote = async function (flagId, uid, note, datetime) { }); }; -Flags.notify = async function (flagObj, uid) { +Flags.notify = async function (flagObj, uid, notifySelf = false) { const [admins, globalMods] = await Promise.all([ groups.getMembers('administrators', 0, -1), groups.getMembers('Global Moderators', 0, -1), @@ -780,7 +780,9 @@ Flags.notify = async function (flagObj, uid) { from: uid, to: uids, }); - uids = uids.filter(_uid => parseInt(_uid, 10) !== parseInt(uid, 10)); + if (!notifySelf) { + uids = uids.filter(_uid => parseInt(_uid, 10) !== parseInt(uid, 10)); + } await notifications.push(notifObj, uids); }; diff --git a/src/posts/votes.js b/src/posts/votes.js index 1a56c4a66d..08466a2fe8 100644 --- a/src/posts/votes.js +++ b/src/posts/votes.js @@ -2,10 +2,12 @@ const meta = require('../meta'); const db = require('../database'); +const flags = require('../flags'); const user = require('../user'); const topics = require('../topics'); const plugins = require('../plugins'); const privileges = require('../privileges'); +const translator = require('../translator'); module.exports = function (Posts) { const votesInProgress = {}; @@ -243,6 +245,13 @@ module.exports = function (Posts) { if (!postData || !postData.pid || !postData.tid) { return; } + const threshold = meta.config['flags:autoFlagOnDownvoteThreshold']; + if (threshold && postData.votes <= (-threshold)) { + const adminUid = await user.getFirstAdminUid(); + const reportMsg = await translator.translate(`[[flags:auto-flagged, ${-postData.votes}]]`); + const flagObj = await flags.create('post', postData.pid, adminUid, reportMsg, null, true); + await flags.notify(flagObj, adminUid, true); + } await Promise.all([ updateTopicVoteCount(postData), db.sortedSetAdd('posts:votes', postData.votes, postData.pid), diff --git a/src/user/index.js b/src/user/index.js index 6a52886011..3f409669cf 100644 --- a/src/user/index.js +++ b/src/user/index.js @@ -211,6 +211,10 @@ User.getAdminsandGlobalModsandModerators = async function () { return await User.getUsersData(_.union(...results)); }; +User.getFirstAdminUid = async function () { + return (await db.getSortedSetRange('group:administrators:members', 0, 0))[0]; +}; + User.getModeratorUids = async function () { const cids = await categories.getAllCidsFromSet('categories:cid'); const uids = await categories.getModeratorUids(cids); diff --git a/src/views/admin/settings/reputation.tpl b/src/views/admin/settings/reputation.tpl index c6e5c62387..24f8e632a1 100644 --- a/src/views/admin/settings/reputation.tpl +++ b/src/views/admin/settings/reputation.tpl @@ -82,6 +82,10 @@ [[admin/settings/reputation:flags.limit-per-target-help]]

+
+ + +