mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 07:07:35 +01:00
Refactored group chat flagging (#9907)
This commit is contained in:
@@ -11,6 +11,8 @@ import { removeFromArray } from '../../libs/collectionManipulators';
|
|||||||
import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email';
|
import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email';
|
||||||
import slack from '../../libs/slack';
|
import slack from '../../libs/slack';
|
||||||
import pusher from '../../libs/pusher';
|
import pusher from '../../libs/pusher';
|
||||||
|
import { getAuthorEmailFromMessage } from '../../libs/chat';
|
||||||
|
import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird';
|
||||||
import bannedWords from '../../libs/bannedWords';
|
import bannedWords from '../../libs/bannedWords';
|
||||||
@@ -18,7 +20,6 @@ import guildsAllowingBannedWords from '../../libs/guildsAllowingBannedWords';
|
|||||||
import { getMatchesByWordArray } from '../../libs/stringUtils';
|
import { getMatchesByWordArray } from '../../libs/stringUtils';
|
||||||
import bannedSlurs from '../../libs/bannedSlurs';
|
import bannedSlurs from '../../libs/bannedSlurs';
|
||||||
|
|
||||||
const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL');
|
|
||||||
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => {
|
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => {
|
||||||
return { email, canSend: true };
|
return { email, canSend: true };
|
||||||
});
|
});
|
||||||
@@ -40,22 +41,6 @@ const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email)
|
|||||||
|
|
||||||
let api = {};
|
let api = {};
|
||||||
|
|
||||||
async function getAuthorEmailFromMessage (message) {
|
|
||||||
let authorId = message.uuid;
|
|
||||||
|
|
||||||
if (authorId === 'system') {
|
|
||||||
return 'system';
|
|
||||||
}
|
|
||||||
|
|
||||||
let author = await User.findOne({_id: authorId}, {auth: 1}).exec();
|
|
||||||
|
|
||||||
if (author) {
|
|
||||||
return getUserInfo(author, ['email']).email;
|
|
||||||
} else {
|
|
||||||
return 'Author Account Deleted';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function textContainsBannedSlur (message) {
|
function textContainsBannedSlur (message) {
|
||||||
let bannedSlursMatched = getMatchesByWordArray(message, bannedSlurs);
|
let bannedSlursMatched = getMatchesByWordArray(message, bannedSlurs);
|
||||||
return bannedSlursMatched.length > 0;
|
return bannedSlursMatched.length > 0;
|
||||||
@@ -303,79 +288,8 @@ api.flagChat = {
|
|||||||
url: '/groups/:groupId/chat/:chatId/flag',
|
url: '/groups/:groupId/chat/:chatId/flag',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
const chatReporter = chatReporterFactory('Group', req, res);
|
||||||
let groupId = req.params.groupId;
|
const message = await chatReporter.flag();
|
||||||
req.checkParams('groupId', res.t('groupIdRequired')).notEmpty();
|
|
||||||
req.checkParams('chatId', res.t('chatIdRequired')).notEmpty();
|
|
||||||
|
|
||||||
let validationErrors = req.validationErrors();
|
|
||||||
if (validationErrors) throw validationErrors;
|
|
||||||
|
|
||||||
let group = await Group.getGroup({
|
|
||||||
user,
|
|
||||||
groupId,
|
|
||||||
optionalMembership: user.contributor.admin,
|
|
||||||
});
|
|
||||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
|
||||||
let message = _.find(group.chat, {id: req.params.chatId});
|
|
||||||
|
|
||||||
if (!message) throw new NotFound(res.t('messageGroupChatNotFound'));
|
|
||||||
if (message.uuid === 'system') throw new BadRequest(res.t('messageCannotFlagSystemMessages', {communityManagerEmail: COMMUNITY_MANAGER_EMAIL}));
|
|
||||||
let update = {$set: {}};
|
|
||||||
|
|
||||||
// Log user ids that have flagged the message
|
|
||||||
if (!message.flags) message.flags = {};
|
|
||||||
// TODO fix error type
|
|
||||||
if (message.flags[user._id] && !user.contributor.admin) throw new NotFound(res.t('messageGroupChatFlagAlreadyReported'));
|
|
||||||
message.flags[user._id] = true;
|
|
||||||
update.$set[`chat.$.flags.${user._id}`] = true;
|
|
||||||
|
|
||||||
// Log total number of flags (publicly viewable)
|
|
||||||
if (!message.flagCount) message.flagCount = 0;
|
|
||||||
if (user.contributor.admin) {
|
|
||||||
// Arbitrary amount, higher than 2
|
|
||||||
message.flagCount = 5;
|
|
||||||
} else {
|
|
||||||
message.flagCount++;
|
|
||||||
}
|
|
||||||
update.$set['chat.$.flagCount'] = message.flagCount;
|
|
||||||
|
|
||||||
await Group.update(
|
|
||||||
{_id: group._id, 'chat.id': message.id},
|
|
||||||
update
|
|
||||||
).exec();
|
|
||||||
|
|
||||||
let reporterEmailContent = getUserInfo(user, ['email']).email;
|
|
||||||
let authorEmail = await getAuthorEmailFromMessage(message);
|
|
||||||
let groupUrl = getGroupUrl(group);
|
|
||||||
|
|
||||||
sendTxn(FLAG_REPORT_EMAILS, 'flag-report-to-mods', [
|
|
||||||
{name: 'MESSAGE_TIME', content: (new Date(message.timestamp)).toString()},
|
|
||||||
{name: 'MESSAGE_TEXT', content: message.text},
|
|
||||||
|
|
||||||
{name: 'REPORTER_USERNAME', content: user.profile.name},
|
|
||||||
{name: 'REPORTER_UUID', content: user._id},
|
|
||||||
{name: 'REPORTER_EMAIL', content: reporterEmailContent},
|
|
||||||
{name: 'REPORTER_MODAL_URL', content: `/static/front/#?memberId=${user._id}`},
|
|
||||||
|
|
||||||
{name: 'AUTHOR_USERNAME', content: message.user},
|
|
||||||
{name: 'AUTHOR_UUID', content: message.uuid},
|
|
||||||
{name: 'AUTHOR_EMAIL', content: authorEmail},
|
|
||||||
{name: 'AUTHOR_MODAL_URL', content: `/static/front/#?memberId=${message.uuid}`},
|
|
||||||
|
|
||||||
{name: 'GROUP_NAME', content: group.name},
|
|
||||||
{name: 'GROUP_TYPE', content: group.type},
|
|
||||||
{name: 'GROUP_ID', content: group._id},
|
|
||||||
{name: 'GROUP_URL', content: groupUrl},
|
|
||||||
]);
|
|
||||||
|
|
||||||
slack.sendFlagNotification({
|
|
||||||
authorEmail,
|
|
||||||
flagger: user,
|
|
||||||
group,
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.respond(200, message);
|
res.respond(200, message);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
18
website/server/libs/chat.js
Normal file
18
website/server/libs/chat.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { model as User } from '../models/user';
|
||||||
|
import { getUserInfo } from './email';
|
||||||
|
|
||||||
|
export async function getAuthorEmailFromMessage (message) {
|
||||||
|
let authorId = message.uuid;
|
||||||
|
|
||||||
|
if (authorId === 'system') {
|
||||||
|
return 'system';
|
||||||
|
}
|
||||||
|
|
||||||
|
let author = await User.findOne({_id: authorId}, {auth: 1}).exec();
|
||||||
|
|
||||||
|
if (author) {
|
||||||
|
return getUserInfo(author, ['email']).email;
|
||||||
|
} else {
|
||||||
|
return 'Author Account Deleted';
|
||||||
|
}
|
||||||
|
}
|
||||||
36
website/server/libs/chatReporting/chatReporter.js
Normal file
36
website/server/libs/chatReporting/chatReporter.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
} from '../errors';
|
||||||
|
import { getUserInfo } from '../email';
|
||||||
|
import { getAuthorEmailFromMessage } from '../chat';
|
||||||
|
|
||||||
|
export default class ChatReporter {
|
||||||
|
constructor (req, res) {
|
||||||
|
this.req = req;
|
||||||
|
this.res = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate () {}
|
||||||
|
|
||||||
|
async notify (group, message) {
|
||||||
|
const reporterEmailContent = getUserInfo(this.user, ['email']).email;
|
||||||
|
this.authorEmail = await getAuthorEmailFromMessage(message);
|
||||||
|
this.emailVariables = [
|
||||||
|
{name: 'MESSAGE_TIME', content: (new Date(message.timestamp)).toString()},
|
||||||
|
{name: 'MESSAGE_TEXT', content: message.text},
|
||||||
|
|
||||||
|
{name: 'REPORTER_USERNAME', content: this.user.profile.name},
|
||||||
|
{name: 'REPORTER_UUID', content: this.user._id},
|
||||||
|
{name: 'REPORTER_EMAIL', content: reporterEmailContent},
|
||||||
|
{name: 'REPORTER_MODAL_URL', content: `/static/front/#?memberId=${this.user._id}`},
|
||||||
|
|
||||||
|
{name: 'AUTHOR_USERNAME', content: message.user},
|
||||||
|
{name: 'AUTHOR_UUID', content: message.uuid},
|
||||||
|
{name: 'AUTHOR_EMAIL', content: this.authorEmail},
|
||||||
|
{name: 'AUTHOR_MODAL_URL', content: `/static/front/#?memberId=${message.uuid}`},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async flag () {
|
||||||
|
throw new Error('Flag must be implemented');
|
||||||
|
}
|
||||||
|
}
|
||||||
11
website/server/libs/chatReporting/chatReporterFactory.js
Normal file
11
website/server/libs/chatReporting/chatReporterFactory.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import GroupChatReporter from './groupChatReporter';
|
||||||
|
// import InboxChatReporter from './inboxChatReporter';
|
||||||
|
|
||||||
|
export function chatReporterFactory (type, req, res) {
|
||||||
|
if (type === 'Group') {
|
||||||
|
return new GroupChatReporter(req, res);
|
||||||
|
}
|
||||||
|
// else if (type === 'Inbox') {
|
||||||
|
// return new InboxChatReporter(req, res);
|
||||||
|
// }
|
||||||
|
}
|
||||||
97
website/server/libs/chatReporting/groupChatReporter.js
Normal file
97
website/server/libs/chatReporting/groupChatReporter.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import find from 'lodash/find';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
|
import ChatReporter from './chatReporter';
|
||||||
|
import {
|
||||||
|
BadRequest,
|
||||||
|
NotFound,
|
||||||
|
} from '../errors';
|
||||||
|
import { getGroupUrl, sendTxn } from '../email';
|
||||||
|
import slack from '../slack';
|
||||||
|
import { model as Group } from '../../models/group';
|
||||||
|
|
||||||
|
const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL');
|
||||||
|
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => {
|
||||||
|
return { email, canSend: true };
|
||||||
|
});
|
||||||
|
|
||||||
|
export default class GroupChatReporter extends ChatReporter {
|
||||||
|
constructor (req, res) {
|
||||||
|
super(req, res);
|
||||||
|
|
||||||
|
this.user = res.locals.user;
|
||||||
|
this.groupId = req.params.groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate () {
|
||||||
|
this.req.checkParams('groupId', this.res.t('groupIdRequired')).notEmpty();
|
||||||
|
this.req.checkParams('chatId', this.res.t('chatIdRequired')).notEmpty();
|
||||||
|
|
||||||
|
let validationErrors = this.req.validationErrors();
|
||||||
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
|
let group = await Group.getGroup({
|
||||||
|
user: this.user,
|
||||||
|
groupId: this.groupId,
|
||||||
|
optionalMembership: this.user.contributor.admin,
|
||||||
|
});
|
||||||
|
if (!group) throw new NotFound(this.res.t('groupNotFound'));
|
||||||
|
|
||||||
|
let message = find(group.chat, {id: this.req.params.chatId});
|
||||||
|
if (!message) throw new NotFound(this.res.t('messageGroupChatNotFound'));
|
||||||
|
if (message.uuid === 'system') throw new BadRequest(this.res.t('messageCannotFlagSystemMessages', {communityManagerEmail: COMMUNITY_MANAGER_EMAIL}));
|
||||||
|
|
||||||
|
return {message, group};
|
||||||
|
}
|
||||||
|
|
||||||
|
async notify (group, message) {
|
||||||
|
await super.notify(group, message);
|
||||||
|
|
||||||
|
const groupUrl = getGroupUrl(group);
|
||||||
|
sendTxn(FLAG_REPORT_EMAILS, 'flag-report-to-mods', this.emailVariables.concat([
|
||||||
|
{name: 'GROUP_NAME', content: group.name},
|
||||||
|
{name: 'GROUP_TYPE', content: group.type},
|
||||||
|
{name: 'GROUP_ID', content: group._id},
|
||||||
|
{name: 'GROUP_URL', content: groupUrl},
|
||||||
|
]));
|
||||||
|
|
||||||
|
slack.sendFlagNotification({
|
||||||
|
authorEmail: this.authorEmail,
|
||||||
|
flagger: this.user,
|
||||||
|
group,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async flagGroupMessage (group, message) {
|
||||||
|
let update = {$set: {}};
|
||||||
|
// Log user ids that have flagged the message
|
||||||
|
if (!message.flags) message.flags = {};
|
||||||
|
// TODO fix error type
|
||||||
|
if (message.flags[this.user._id] && !this.user.contributor.admin) throw new NotFound(this.res.t('messageGroupChatFlagAlreadyReported'));
|
||||||
|
message.flags[this.user._id] = true;
|
||||||
|
update.$set[`chat.$.flags.${this.user._id}`] = true;
|
||||||
|
|
||||||
|
// Log total number of flags (publicly viewable)
|
||||||
|
if (!message.flagCount) message.flagCount = 0;
|
||||||
|
if (this.user.contributor.admin) {
|
||||||
|
// Arbitrary amount, higher than 2
|
||||||
|
message.flagCount = 5;
|
||||||
|
} else {
|
||||||
|
message.flagCount++;
|
||||||
|
}
|
||||||
|
update.$set['chat.$.flagCount'] = message.flagCount;
|
||||||
|
|
||||||
|
await Group.update(
|
||||||
|
{_id: group._id, 'chat.id': message.id},
|
||||||
|
update
|
||||||
|
).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
async flag () {
|
||||||
|
let {message, group} = await this.validate();
|
||||||
|
await this.flagGroupMessage(group, message);
|
||||||
|
await this.notify(group, message);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user