mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: add mute history, closes #10596
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"banned": "Banned",
|
"banned": "Banned",
|
||||||
|
"muted": "Muted",
|
||||||
"offline": "Offline",
|
"offline": "Offline",
|
||||||
"deleted": "Deleted",
|
"deleted": "Deleted",
|
||||||
"username": "User Name",
|
"username": "User Name",
|
||||||
@@ -173,6 +174,10 @@
|
|||||||
"info.banned-permanently": "Banned permanently",
|
"info.banned-permanently": "Banned permanently",
|
||||||
"info.banned-reason-label": "Reason",
|
"info.banned-reason-label": "Reason",
|
||||||
"info.banned-no-reason": "No reason given.",
|
"info.banned-no-reason": "No reason given.",
|
||||||
|
"info.mute-history": "Recent Mute History",
|
||||||
|
"info.no-mute-history": "This user has never been muted",
|
||||||
|
"info.muted-until": "Muted until %1",
|
||||||
|
"info.muted-expiry": "Expiry",
|
||||||
"info.muted-no-reason": "No reason given.",
|
"info.muted-no-reason": "No reason given.",
|
||||||
"info.username-history": "Username History",
|
"info.username-history": "Username History",
|
||||||
"info.email-history": "Email History",
|
"info.email-history": "Email History",
|
||||||
|
|||||||
@@ -231,11 +231,24 @@ usersAPI.mute = async function (caller, data) {
|
|||||||
} else if (await user.isAdministrator(data.uid)) {
|
} else if (await user.isAdministrator(data.uid)) {
|
||||||
throw new Error('[[error:cant-mute-other-admins]]');
|
throw new Error('[[error:cant-mute-other-admins]]');
|
||||||
}
|
}
|
||||||
|
const reason = data.reason || '[[user:info.muted-no-reason]]';
|
||||||
await db.setObject(`user:${data.uid}`, {
|
await db.setObject(`user:${data.uid}`, {
|
||||||
mutedUntil: data.until,
|
mutedUntil: data.until,
|
||||||
mutedReason: data.reason || '[[user:info.muted-no-reason]]',
|
mutedReason: reason,
|
||||||
});
|
});
|
||||||
|
const now = Date.now();
|
||||||
|
const muteKey = `uid:${data.uid}:mute:${now}`;
|
||||||
|
const muteData = {
|
||||||
|
fromUid: caller.uid,
|
||||||
|
uid: data.uid,
|
||||||
|
timestamp: now,
|
||||||
|
expire: data.until,
|
||||||
|
};
|
||||||
|
if (data.reason) {
|
||||||
|
muteData.reason = reason;
|
||||||
|
}
|
||||||
|
await db.sortedSetAdd(`uid:${data.uid}:mutes:timestamp`, now, muteKey);
|
||||||
|
await db.setObject(muteKey, muteData);
|
||||||
await events.log({
|
await events.log({
|
||||||
type: 'user-mute',
|
type: 'user-mute',
|
||||||
uid: caller.uid,
|
uid: caller.uid,
|
||||||
|
|||||||
31
src/flags.js
31
src/flags.js
@@ -757,6 +757,7 @@ Flags.getHistory = async function (flagId) {
|
|||||||
|
|
||||||
// Append ban history and username change data
|
// Append ban history and username change data
|
||||||
history = await mergeBanHistory(history, targetUid, uids);
|
history = await mergeBanHistory(history, targetUid, uids);
|
||||||
|
history = await mergeMuteHistory(history, targetUid, uids);
|
||||||
history = await mergeUsernameEmailChanges(history, targetUid, uids);
|
history = await mergeUsernameEmailChanges(history, targetUid, uids);
|
||||||
|
|
||||||
const userData = await user.getUsersFields(uids, ['username', 'userslug', 'picture']);
|
const userData = await user.getUsersFields(uids, ['username', 'userslug', 'picture']);
|
||||||
@@ -854,21 +855,39 @@ Flags.notify = async function (flagObj, uid, notifySelf = false) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function mergeBanHistory(history, targetUid, uids) {
|
async function mergeBanHistory(history, targetUid, uids) {
|
||||||
let recentBans = await db.getSortedSetRevRange(`uid:${targetUid}:bans:timestamp`, 0, 19);
|
return await mergeBanMuteHistory(history, uids, {
|
||||||
recentBans = await db.getObjects(recentBans);
|
set: `uid:${targetUid}:bans:timestamp`,
|
||||||
|
label: '[[user:banned]]',
|
||||||
|
reasonDefault: '[[user:info.banned-no-reason]]',
|
||||||
|
expiryKey: '[[user:info.banned-expiry]]',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return history.concat(recentBans.reduce((memo, cur) => {
|
async function mergeMuteHistory(history, targetUid, uids) {
|
||||||
|
return await mergeBanMuteHistory(history, uids, {
|
||||||
|
set: `uid:${targetUid}:mutes:timestamp`,
|
||||||
|
label: '[[user:muted]]',
|
||||||
|
reasonDefault: '[[user:info.muted-no-reason]]',
|
||||||
|
expiryKey: '[[user:info.muted-expiry]]',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mergeBanMuteHistory(history, uids, params) {
|
||||||
|
let recentObjs = await db.getSortedSetRevRange(params.set, 0, 19);
|
||||||
|
recentObjs = await db.getObjects(recentObjs);
|
||||||
|
|
||||||
|
return history.concat(recentObjs.reduce((memo, cur) => {
|
||||||
uids.push(cur.fromUid);
|
uids.push(cur.fromUid);
|
||||||
memo.push({
|
memo.push({
|
||||||
uid: cur.fromUid,
|
uid: cur.fromUid,
|
||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
key: '[[user:banned]]',
|
key: params.label,
|
||||||
value: validator.escape(String(cur.reason)),
|
value: validator.escape(String(cur.reason || params.reasonDefault)),
|
||||||
labelClass: 'danger',
|
labelClass: 'danger',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: '[[user:info.banned-expiry]]',
|
key: params.expiryKey,
|
||||||
value: new Date(parseInt(cur.expire, 10)).toISOString(),
|
value: new Date(parseInt(cur.expire, 10)).toISOString(),
|
||||||
labelClass: 'default',
|
labelClass: 'default',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ module.exports = function (User) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
User.getModerationHistory = async function (uid) {
|
User.getModerationHistory = async function (uid) {
|
||||||
let [flags, bans] = await Promise.all([
|
let [flags, bans, mutes] = await Promise.all([
|
||||||
db.getSortedSetRevRangeWithScores(`flags:byTargetUid:${uid}`, 0, 19),
|
db.getSortedSetRevRangeWithScores(`flags:byTargetUid:${uid}`, 0, 19),
|
||||||
db.getSortedSetRevRange(`uid:${uid}:bans:timestamp`, 0, 19),
|
db.getSortedSetRevRange(`uid:${uid}:bans:timestamp`, 0, 19),
|
||||||
|
db.getSortedSetRevRange(`uid:${uid}:mutes:timestamp`, 0, 19),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Get pids from flag objects
|
// Get pids from flag objects
|
||||||
@@ -51,14 +52,16 @@ module.exports = function (User) {
|
|||||||
return memo;
|
return memo;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
[flags, bans] = await Promise.all([
|
[flags, bans, mutes] = await Promise.all([
|
||||||
getFlagMetadata(flags),
|
getFlagMetadata(flags),
|
||||||
formatBanData(bans),
|
formatBanMuteData(bans, '[[user:info.banned-no-reason]]'),
|
||||||
|
formatBanMuteData(mutes, '[[user:info.muted-no-reason]]'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
flags: flags,
|
flags: flags,
|
||||||
bans: bans,
|
bans: bans,
|
||||||
|
mutes: mutes,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,17 +98,17 @@ module.exports = function (User) {
|
|||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function formatBanData(bans) {
|
async function formatBanMuteData(keys, noReasonLangKey) {
|
||||||
const banData = await db.getObjects(bans);
|
const data = await db.getObjects(keys);
|
||||||
const uids = banData.map(banData => banData.fromUid);
|
const uids = data.map(d => d.fromUid);
|
||||||
const usersData = await User.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture']);
|
const usersData = await User.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture']);
|
||||||
return banData.map((banObj, index) => {
|
return data.map((banObj, index) => {
|
||||||
banObj.user = usersData[index];
|
banObj.user = usersData[index];
|
||||||
banObj.until = parseInt(banObj.expire, 10);
|
banObj.until = parseInt(banObj.expire, 10);
|
||||||
banObj.untilReadable = new Date(banObj.until).toString();
|
banObj.untilReadable = new Date(banObj.until).toString();
|
||||||
banObj.timestampReadable = new Date(parseInt(banObj.timestamp, 10)).toString();
|
banObj.timestampReadable = new Date(parseInt(banObj.timestamp, 10)).toString();
|
||||||
banObj.timestampISO = utils.toISOString(banObj.timestamp);
|
banObj.timestampISO = utils.toISOString(banObj.timestamp);
|
||||||
banObj.reason = validator.escape(String(banObj.reason || '')) || '[[user:info.banned-no-reason]]';
|
banObj.reason = validator.escape(String(banObj.reason || '')) || noReasonLangKey;
|
||||||
return banObj;
|
return banObj;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user