mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Chat flag inbox (#9761)
* Refactored chat reporting * Added inbox flag chat * Added flag chat to inbox * Added you have flagged message
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
import { find } from 'lodash';
|
import { find } from 'lodash';
|
||||||
|
|
||||||
describe('POST /chat/:chatId/flag', () => {
|
describe('POST /chat/:chatId/flag', () => {
|
||||||
|
context('Flags a Group Chat', () => {
|
||||||
let user, admin, anotherUser, group;
|
let user, admin, anotherUser, group;
|
||||||
const TEST_MESSAGE = 'Test Message';
|
const TEST_MESSAGE = 'Test Message';
|
||||||
|
|
||||||
@@ -145,4 +146,40 @@ describe('POST /chat/:chatId/flag', () => {
|
|||||||
|
|
||||||
expect(auMessageToCheck).to.not.exist;
|
expect(auMessageToCheck).to.not.exist;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('Flags an inbox chat', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Flags a chat', async () => {
|
||||||
|
const messageToSend = 'messageToSend';
|
||||||
|
const receiver = await generateUser();
|
||||||
|
|
||||||
|
await user.post('/members/send-private-message', {
|
||||||
|
message: messageToSend,
|
||||||
|
toUserId: receiver._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedReceiver = await receiver.get('/user');
|
||||||
|
const sendersMessageInReceiversInbox = find(updatedReceiver.inbox.messages, (message) => {
|
||||||
|
return message.uuid === user._id && message.text === messageToSend;
|
||||||
|
});
|
||||||
|
|
||||||
|
const flagResult = await receiver.post(`/chat/${sendersMessageInReceiversInbox.id}/flag`);
|
||||||
|
expect(flagResult.flags[receiver._id]).to.equal(true);
|
||||||
|
expect(flagResult.flagCount).to.equal(1);
|
||||||
|
|
||||||
|
const updatedReceiverAfterFlag = await receiver.get('/user');
|
||||||
|
const receiversInboxAfterFlag = _.find(updatedReceiverAfterFlag.inbox.messages, (message) => {
|
||||||
|
return message.uuid === user._id && message.text === messageToSend;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(receiversInboxAfterFlag.flags[receiver._id]).to.equal(true);
|
||||||
|
expect(receiversInboxAfterFlag.flagCount).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ div
|
|||||||
h2 {{$t('tipTitle', {tipNumber: currentTipNumber})}}
|
h2 {{$t('tipTitle', {tipNumber: currentTipNumber})}}
|
||||||
p {{currentTip}}
|
p {{currentTip}}
|
||||||
#app(:class='{"casting-spell": castingSpell}')
|
#app(:class='{"casting-spell": castingSpell}')
|
||||||
|
report-flag-modal
|
||||||
amazon-payments-modal
|
amazon-payments-modal
|
||||||
snackbars
|
snackbars
|
||||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||||
@@ -118,6 +119,7 @@ import SelectMembersModal from 'client/components/selectMembersModal.vue';
|
|||||||
import notifications from 'client/mixins/notifications';
|
import notifications from 'client/mixins/notifications';
|
||||||
import { setup as setupPayments } from 'client/libs/payments';
|
import { setup as setupPayments } from 'client/libs/payments';
|
||||||
import amazonPaymentsModal from 'client/components/payments/amazonModal';
|
import amazonPaymentsModal from 'client/components/payments/amazonModal';
|
||||||
|
import reportFlagModal from 'client/components/chat/reportFlagModal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [notifications],
|
mixins: [notifications],
|
||||||
@@ -131,6 +133,7 @@ export default {
|
|||||||
BuyModal,
|
BuyModal,
|
||||||
SelectMembersModal,
|
SelectMembersModal,
|
||||||
amazonPaymentsModal,
|
amazonPaymentsModal,
|
||||||
|
reportFlagModal,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ div
|
|||||||
.mentioned-icon(v-if='isUserMentioned')
|
.mentioned-icon(v-if='isUserMentioned')
|
||||||
.message-hidden(v-if='msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
|
.message-hidden(v-if='msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
|
||||||
.message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
|
.message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
|
||||||
|
.flag-message(v-if='msg.flags && msg.flags[user._id]') {{$t('youFlaggedThisMessage')}}
|
||||||
.card-body
|
.card-body
|
||||||
h3.leader(
|
h3.leader(
|
||||||
:class='userLevelStyle(msg)',
|
:class='userLevelStyle(msg)',
|
||||||
@@ -23,7 +24,7 @@ div
|
|||||||
.svg-icon(v-html="icons.copy")
|
.svg-icon(v-html="icons.copy")
|
||||||
| {{$t('copyAsTodo')}}
|
| {{$t('copyAsTodo')}}
|
||||||
// @TODO make copyAsTodo work in the inbox
|
// @TODO make copyAsTodo work in the inbox
|
||||||
span.action(v-if='!inbox && user.flags.communityGuidelinesAccepted && msg.uuid !== "system"', @click='report(msg)')
|
span.action(v-if='user.flags.communityGuidelinesAccepted && msg.uuid !== "system"', @click='report(msg)')
|
||||||
.svg-icon(v-html="icons.report")
|
.svg-icon(v-html="icons.report")
|
||||||
| {{$t('report')}}
|
| {{$t('report')}}
|
||||||
// @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
|
// @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
|
||||||
@@ -49,7 +50,7 @@ div
|
|||||||
top: -.5em;
|
top: -.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-hidden {
|
.message-hidden, .flag-message {
|
||||||
margin-left: 1.5em;
|
margin-left: 1.5em;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
color: red;
|
color: red;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
.row
|
.row
|
||||||
.col-12
|
.col-12
|
||||||
copy-as-todo-modal(:group-name='groupName', :group-id='groupId')
|
copy-as-todo-modal(:group-name='groupName', :group-id='groupId')
|
||||||
report-flag-modal
|
|
||||||
div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)')
|
div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)')
|
||||||
// @TODO: is there a different way to do these conditionals? This creates an infinite loop
|
// @TODO: is there a different way to do these conditionals? This creates an infinite loop
|
||||||
//.hr(v-if='displayDivider(msg)')
|
//.hr(v-if='displayDivider(msg)')
|
||||||
@@ -83,14 +82,12 @@ import findIndex from 'lodash/findIndex';
|
|||||||
|
|
||||||
import Avatar from '../avatar';
|
import Avatar from '../avatar';
|
||||||
import copyAsTodoModal from './copyAsTodoModal';
|
import copyAsTodoModal from './copyAsTodoModal';
|
||||||
import reportFlagModal from './reportFlagModal';
|
|
||||||
import chatCard from './chatCard';
|
import chatCard from './chatCard';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['chat', 'groupId', 'groupName', 'inbox'],
|
props: ['chat', 'groupId', 'groupName', 'inbox'],
|
||||||
components: {
|
components: {
|
||||||
copyAsTodoModal,
|
copyAsTodoModal,
|
||||||
reportFlagModal,
|
|
||||||
chatCard,
|
chatCard,
|
||||||
Avatar,
|
Avatar,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
b-modal#report-flag(:title='$t("abuseFlagModalHeading")', size='lg', :hide-footer='true')
|
b-modal#report-flag(:title='$t("abuseFlagModalHeading")', size='lg', :hide-footer='true', v-if='user')
|
||||||
.modal-header
|
.modal-header
|
||||||
h4(v-html="$t('abuseFlagModalHeading', reportData)")
|
h4(v-html="$t('abuseFlagModalHeading', reportData)")
|
||||||
.modal-body
|
.modal-body
|
||||||
@@ -49,7 +49,7 @@ export default {
|
|||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.$root.$on('habitica::report-chat', data => {
|
this.$root.$on('habitica::report-chat', data => {
|
||||||
if (!data.message || !data.groupId) return;
|
if (!data.message) return;
|
||||||
this.abuseObject = data.message;
|
this.abuseObject = data.message;
|
||||||
this.groupId = data.groupId;
|
this.groupId = data.groupId;
|
||||||
this.$root.$emit('bv::show::modal', 'report-flag');
|
this.$root.$emit('bv::show::modal', 'report-flag');
|
||||||
@@ -64,10 +64,18 @@ export default {
|
|||||||
},
|
},
|
||||||
async reportAbuse () {
|
async reportAbuse () {
|
||||||
this.notify('Thank you for reporting this violation. The moderators have been notified.');
|
this.notify('Thank you for reporting this violation. The moderators have been notified.');
|
||||||
|
|
||||||
|
if (!this.groupId) {
|
||||||
|
await this.$store.dispatch('chat:flagInbox', {
|
||||||
|
chatId: this.abuseObject.id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
await this.$store.dispatch('chat:flag', {
|
await this.$store.dispatch('chat:flag', {
|
||||||
groupId: this.groupId,
|
groupId: this.groupId,
|
||||||
chatId: this.abuseObject.id,
|
chatId: this.abuseObject.id,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
async clearFlagCount () {
|
async clearFlagCount () {
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ export default {
|
|||||||
user: message.user,
|
user: message.user,
|
||||||
uuid: message.uuid,
|
uuid: message.uuid,
|
||||||
id: message.id,
|
id: message.id,
|
||||||
|
flags: message.flags,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (message.sent) {
|
if (message.sent) {
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ export async function flag (store, payload) {
|
|||||||
return response.data.data;
|
return response.data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function flagInbox (store, payload) {
|
||||||
|
let url = `/api/v3/chat/${payload.chatId}/flag`;
|
||||||
|
let response = await axios.post(url);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
export async function clearFlagCount (store, payload) {
|
export async function clearFlagCount (store, payload) {
|
||||||
let url = `/api/v3/groups/${payload.groupId}/chat/${payload.chatId}/clearflags`;
|
let url = `/api/v3/groups/${payload.groupId}/chat/${payload.chatId}/clearflags`;
|
||||||
let response = await axios.post(url);
|
let response = await axios.post(url);
|
||||||
|
|||||||
@@ -66,5 +66,6 @@
|
|||||||
"messageNotificationNotFound": "Notification not found.",
|
"messageNotificationNotFound": "Notification not found.",
|
||||||
|
|
||||||
"notificationsRequired": "Notification ids are required.",
|
"notificationsRequired": "Notification ids are required.",
|
||||||
"beginningOfConversation": "This is the beginning of your conversation with <%= userName %>. Remember to be kind, respectful, and follow the Community Guidelines!"
|
"beginningOfConversation": "This is the beginning of your conversation with <%= userName %>. Remember to be kind, respectful, and follow the Community Guidelines!",
|
||||||
|
"youFlaggedThisMessage": "You have reported this message to moderators. They will contact you about the issue soon."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -22,7 +24,6 @@ const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email)
|
|||||||
return { email, canSend: true };
|
return { email, canSend: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL');
|
|
||||||
/**
|
/**
|
||||||
* @apiDefine MessageNotFound
|
* @apiDefine MessageNotFound
|
||||||
* @apiError (404) {NotFound} MessageNotFound The specified message could not be found.
|
* @apiError (404) {NotFound} MessageNotFound The specified message could not be found.
|
||||||
@@ -40,22 +41,6 @@ const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS:COMMUNITY_MANAGER_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;
|
||||||
@@ -298,85 +283,51 @@ api.likeChat = {
|
|||||||
* @apiError (404) {NotFound} AlreadyFlagged Chat messages cannot be flagged more than once by a user
|
* @apiError (404) {NotFound} AlreadyFlagged Chat messages cannot be flagged more than once by a user
|
||||||
* @apiError (404) {NotFound} messageGroupChatFlagAlreadyReported The message has already been flagged
|
* @apiError (404) {NotFound} messageGroupChatFlagAlreadyReported The message has already been flagged
|
||||||
*/
|
*/
|
||||||
api.flagChat = {
|
api.flagGroupChat = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
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();
|
||||||
|
res.respond(200, message);
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// @TODO:
|
||||||
|
/**
|
||||||
|
* @api {post} /api/v3/groups/:groupId/chat/:chatId/flag Flag a group chat message
|
||||||
|
* @apiDescription A message will be hidden from chat if two or more users flag a message. It will be hidden immediately if a moderator flags the message. An email is sent to the moderators about every flagged message.
|
||||||
|
* @apiName FlagChat
|
||||||
|
* @apiGroup Chat
|
||||||
|
*
|
||||||
|
* @apiParam (Path) {UUID} groupId The group id ('party' for the user party and 'habitrpg' for tavern are accepted)
|
||||||
|
* @apiParam (Path) {UUID} chatId The chat message id
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data The flagged chat message
|
||||||
|
* @apiSuccess {UUID} data.id The id of the message
|
||||||
|
* @apiSuccess {String} data.text The text of the message
|
||||||
|
* @apiSuccess {Number} data.timestamp The timestamp of the message in milliseconds
|
||||||
|
* @apiSuccess {Object} data.likes The likes of the message
|
||||||
|
* @apiSuccess {Object} data.flags The flags of the message
|
||||||
|
* @apiSuccess {Number} data.flagCount The number of flags the message has
|
||||||
|
* @apiSuccess {UUID} data.uuid The user id of the author of the message
|
||||||
|
* @apiSuccess {String} data.user The username of the author of the message
|
||||||
|
*
|
||||||
|
* @apiUse GroupNotFound
|
||||||
|
* @apiUse MessageNotFound
|
||||||
|
* @apiUse GroupIdRequired
|
||||||
|
* @apiUse ChatIdRequired
|
||||||
|
* @apiError (404) {NotFound} AlreadyFlagged Chat messages cannot be flagged more than once by a user
|
||||||
|
* @apiError (404) {NotFound} messageGroupChatFlagAlreadyReported The message has already been flagged
|
||||||
|
*/
|
||||||
|
api.flagChat = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/chat/:chatId/flag',
|
||||||
|
middlewares: [authWithHeaders()],
|
||||||
|
async handler (req, res) {
|
||||||
|
const chatReporter = chatReporterFactory('Inbox', req, res);
|
||||||
|
const message = await chatReporter.flag();
|
||||||
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
10
website/server/libs/chatReporting/chatReporterFactory.js
Normal file
10
website/server/libs/chatReporting/chatReporterFactory.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
website/server/libs/chatReporting/inboxChatReporter.js
Normal file
74
website/server/libs/chatReporting/inboxChatReporter.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import find from 'lodash/find';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
|
import ChatReporter from './chatReporter';
|
||||||
|
import {
|
||||||
|
NotFound,
|
||||||
|
} from '../errors';
|
||||||
|
import { sendTxn } from '../email';
|
||||||
|
import slack from '../slack';
|
||||||
|
|
||||||
|
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => {
|
||||||
|
return { email, canSend: true };
|
||||||
|
});
|
||||||
|
|
||||||
|
export default class InboxChatReporter extends ChatReporter {
|
||||||
|
constructor (req, res) {
|
||||||
|
super(req, res);
|
||||||
|
this.user = res.locals.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate () {
|
||||||
|
this.req.checkParams('chatId', this.res.t('chatIdRequired')).notEmpty();
|
||||||
|
|
||||||
|
const validationErrors = this.req.validationErrors();
|
||||||
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
|
const message = find(this.user.inbox.messages, {id: this.req.params.chatId});
|
||||||
|
if (!message) throw new NotFound(this.res.t('messageChatNotFound'));
|
||||||
|
|
||||||
|
return {message};
|
||||||
|
}
|
||||||
|
|
||||||
|
async notify (group, message) {
|
||||||
|
await super.notify(group, message);
|
||||||
|
|
||||||
|
sendTxn(FLAG_REPORT_EMAILS, 'flag-report-to-mods', this.emailVariables);
|
||||||
|
|
||||||
|
slack.sendInboxFlagNotification({
|
||||||
|
authorEmail: this.authorEmail,
|
||||||
|
flagger: this.user,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async flagInboxMessage (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[`inbox.messages.$.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['inbox.messages.$.flagCount'] = message.flagCount;
|
||||||
|
|
||||||
|
this.user.markModified('inbox.messages');
|
||||||
|
await this.user.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async flag () {
|
||||||
|
let {message} = await this.validate();
|
||||||
|
await this.flagInboxMessage(message);
|
||||||
|
await this.notify(null, message);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,42 @@ function sendFlagNotification ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendInboxFlagNotification ({
|
||||||
|
authorEmail,
|
||||||
|
flagger,
|
||||||
|
message,
|
||||||
|
}) {
|
||||||
|
if (!SLACK_FLAGGING_URL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let titleLink;
|
||||||
|
let authorName;
|
||||||
|
let title = `Flag in ${flagger.profile.name}'s Inbox'`;
|
||||||
|
let text = `${flagger.profile.name} (${flagger.id}) flagged a message (language: ${flagger.preferences.language})`;
|
||||||
|
|
||||||
|
if (!message.user && message.uuid === 'system') {
|
||||||
|
authorName = 'System Message';
|
||||||
|
} else {
|
||||||
|
authorName = `${message.user} - ${authorEmail} - ${message.uuid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
flagSlack.send({
|
||||||
|
text,
|
||||||
|
attachments: [{
|
||||||
|
fallback: 'Flag Message',
|
||||||
|
color: 'danger',
|
||||||
|
author_name: authorName,
|
||||||
|
title,
|
||||||
|
title_link: titleLink,
|
||||||
|
text: message.text,
|
||||||
|
footer: `<${SLACK_FLAGGING_FOOTER_LINK}?chatId=${message.id}|Flag this message>`,
|
||||||
|
mrkdwn_in: [
|
||||||
|
'text',
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function sendSubscriptionNotification ({
|
function sendSubscriptionNotification ({
|
||||||
buyer,
|
buyer,
|
||||||
recipient,
|
recipient,
|
||||||
@@ -137,5 +173,5 @@ function sendSlurNotification ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sendFlagNotification, sendSubscriptionNotification, sendSlurNotification,
|
sendFlagNotification, sendSubscriptionNotification, sendSlurNotification, sendInboxFlagNotification,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user