mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +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,144 +5,181 @@ import {
|
||||
import { find } from 'lodash';
|
||||
|
||||
describe('POST /chat/:chatId/flag', () => {
|
||||
let user, admin, anotherUser, group;
|
||||
const TEST_MESSAGE = 'Test Message';
|
||||
context('Flags a Group Chat', () => {
|
||||
let user, admin, anotherUser, group;
|
||||
const TEST_MESSAGE = 'Test Message';
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({balance: 1});
|
||||
admin = await generateUser({balance: 1, 'contributor.admin': true});
|
||||
anotherUser = await generateUser();
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({balance: 1});
|
||||
admin = await generateUser({balance: 1, 'contributor.admin': true});
|
||||
anotherUser = await generateUser();
|
||||
|
||||
group = await user.post('/groups', {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when chat message is not found', async () => {
|
||||
await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageGroupChatNotFound'),
|
||||
group = await user.post('/groups', {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
});
|
||||
|
||||
it('Allows players to flag their own message', async () => {
|
||||
let message = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('Flags a chat', async () => {
|
||||
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
expect(flagResult.flags[user._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(1);
|
||||
|
||||
let groupWithFlags = await admin.get(`/groups/${group._id}`);
|
||||
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
expect(messageToCheck.flags[user._id]).to.equal(true);
|
||||
});
|
||||
|
||||
it('Flags a chat when the author\'s account was deleted', async () => {
|
||||
let deletedUser = await generateUser();
|
||||
let { message } = await deletedUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
await deletedUser.del('/user', {
|
||||
password: 'password',
|
||||
});
|
||||
|
||||
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
expect(flagResult.flags[user._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(1);
|
||||
|
||||
let groupWithFlags = await admin.get(`/groups/${group._id}`);
|
||||
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
expect(messageToCheck.flags[user._id]).to.equal(true);
|
||||
});
|
||||
|
||||
it('Flags a chat with a higher flag acount when an admin flags the message', async () => {
|
||||
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
let flagResult = await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
expect(flagResult.flags[admin._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(5);
|
||||
|
||||
let groupWithFlags = await admin.get(`/groups/${group._id}`);
|
||||
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
expect(messageToCheck.flags[admin._id]).to.equal(true);
|
||||
expect(messageToCheck.flagCount).to.equal(5);
|
||||
});
|
||||
|
||||
it('allows admin to flag a message in a private group', async () => {
|
||||
let privateGroup = await user.post('/groups', {
|
||||
name: 'Test party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
it('Returns an error when chat message is not found', async () => {
|
||||
await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageGroupChatNotFound'),
|
||||
});
|
||||
});
|
||||
await user.post(`/groups/${privateGroup._id}/invite`, {
|
||||
uuids: [anotherUser._id],
|
||||
|
||||
it('Allows players to flag their own message', async () => {
|
||||
let message = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`)).to.eventually.be.ok;
|
||||
});
|
||||
await anotherUser.post(`/groups/${privateGroup._id}/join`);
|
||||
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
let flagResult = await admin.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`);
|
||||
it('Flags a chat', async () => {
|
||||
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
expect(flagResult.flags[admin._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(5);
|
||||
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
expect(flagResult.flags[user._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(1);
|
||||
|
||||
let groupWithFlags = await anotherUser.get(`/groups/${privateGroup._id}`);
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
let groupWithFlags = await admin.get(`/groups/${group._id}`);
|
||||
|
||||
expect(messageToCheck).to.not.exist;
|
||||
});
|
||||
|
||||
it('does not allow non member to flag message in private group', async () => {
|
||||
let privateGroup = await user.post('/groups', {
|
||||
name: 'Test party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
expect(messageToCheck.flags[user._id]).to.equal(true);
|
||||
});
|
||||
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
await expect(anotherUser.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
it('Flags a chat when the author\'s account was deleted', async () => {
|
||||
let deletedUser = await generateUser();
|
||||
let { message } = await deletedUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
await deletedUser.del('/user', {
|
||||
password: 'password',
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when user tries to flag a message that is already flagged', async () => {
|
||||
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
expect(flagResult.flags[user._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(1);
|
||||
|
||||
await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
let groupWithFlags = await admin.get(`/groups/${group._id}`);
|
||||
|
||||
await expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageGroupChatFlagAlreadyReported'),
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
expect(messageToCheck.flags[user._id]).to.equal(true);
|
||||
});
|
||||
|
||||
it('Flags a chat with a higher flag acount when an admin flags the message', async () => {
|
||||
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
let flagResult = await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
expect(flagResult.flags[admin._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(5);
|
||||
|
||||
let groupWithFlags = await admin.get(`/groups/${group._id}`);
|
||||
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
expect(messageToCheck.flags[admin._id]).to.equal(true);
|
||||
expect(messageToCheck.flagCount).to.equal(5);
|
||||
});
|
||||
|
||||
it('allows admin to flag a message in a private group', async () => {
|
||||
let privateGroup = await user.post('/groups', {
|
||||
name: 'Test party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
});
|
||||
await user.post(`/groups/${privateGroup._id}/invite`, {
|
||||
uuids: [anotherUser._id],
|
||||
});
|
||||
await anotherUser.post(`/groups/${privateGroup._id}/join`);
|
||||
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
let flagResult = await admin.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`);
|
||||
|
||||
expect(flagResult.flags[admin._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(5);
|
||||
|
||||
let groupWithFlags = await anotherUser.get(`/groups/${privateGroup._id}`);
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
|
||||
expect(messageToCheck).to.not.exist;
|
||||
});
|
||||
|
||||
it('does not allow non member to flag message in private group', async () => {
|
||||
let privateGroup = await user.post('/groups', {
|
||||
name: 'Test party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
});
|
||||
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
await expect(anotherUser.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when user tries to flag a message that is already flagged', async () => {
|
||||
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
|
||||
await expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageGroupChatFlagAlreadyReported'),
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a hidden message to the original poster', async () => {
|
||||
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
|
||||
let groupWithFlags = await user.get(`/groups/${group._id}`);
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
|
||||
expect(messageToCheck).to.exist;
|
||||
|
||||
let auGroupWithFlags = await anotherUser.get(`/groups/${group._id}`);
|
||||
let auMessageToCheck = find(auGroupWithFlags.chat, {id: message.id});
|
||||
|
||||
expect(auMessageToCheck).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a hidden message to the original poster', async () => {
|
||||
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
context('Flags an inbox chat', () => {
|
||||
let user;
|
||||
|
||||
await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
let groupWithFlags = await user.get(`/groups/${group._id}`);
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
it('Flags a chat', async () => {
|
||||
const messageToSend = 'messageToSend';
|
||||
const receiver = await generateUser();
|
||||
|
||||
expect(messageToCheck).to.exist;
|
||||
await user.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let auGroupWithFlags = await anotherUser.get(`/groups/${group._id}`);
|
||||
let auMessageToCheck = find(auGroupWithFlags.chat, {id: message.id});
|
||||
const updatedReceiver = await receiver.get('/user');
|
||||
const sendersMessageInReceiversInbox = find(updatedReceiver.inbox.messages, (message) => {
|
||||
return message.uuid === user._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(auMessageToCheck).to.not.exist;
|
||||
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})}}
|
||||
p {{currentTip}}
|
||||
#app(:class='{"casting-spell": castingSpell}')
|
||||
report-flag-modal
|
||||
amazon-payments-modal
|
||||
snackbars
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
@@ -118,6 +119,7 @@ import SelectMembersModal from 'client/components/selectMembersModal.vue';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
import { setup as setupPayments } from 'client/libs/payments';
|
||||
import amazonPaymentsModal from 'client/components/payments/amazonModal';
|
||||
import reportFlagModal from 'client/components/chat/reportFlagModal';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
@@ -131,6 +133,7 @@ export default {
|
||||
BuyModal,
|
||||
SelectMembersModal,
|
||||
amazonPaymentsModal,
|
||||
reportFlagModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
||||
@@ -3,6 +3,7 @@ div
|
||||
.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 hidden
|
||||
.flag-message(v-if='msg.flags && msg.flags[user._id]') {{$t('youFlaggedThisMessage')}}
|
||||
.card-body
|
||||
h3.leader(
|
||||
:class='userLevelStyle(msg)',
|
||||
@@ -23,7 +24,7 @@ div
|
||||
.svg-icon(v-html="icons.copy")
|
||||
| {{$t('copyAsTodo')}}
|
||||
// @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")
|
||||
| {{$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
|
||||
@@ -49,7 +50,7 @@ div
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
.message-hidden {
|
||||
.message-hidden, .flag-message {
|
||||
margin-left: 1.5em;
|
||||
margin-top: 1em;
|
||||
color: red;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
.row
|
||||
.col-12
|
||||
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)')
|
||||
// @TODO: is there a different way to do these conditionals? This creates an infinite loop
|
||||
//.hr(v-if='displayDivider(msg)')
|
||||
@@ -83,14 +82,12 @@ import findIndex from 'lodash/findIndex';
|
||||
|
||||
import Avatar from '../avatar';
|
||||
import copyAsTodoModal from './copyAsTodoModal';
|
||||
import reportFlagModal from './reportFlagModal';
|
||||
import chatCard from './chatCard';
|
||||
|
||||
export default {
|
||||
props: ['chat', 'groupId', 'groupName', 'inbox'],
|
||||
components: {
|
||||
copyAsTodoModal,
|
||||
reportFlagModal,
|
||||
chatCard,
|
||||
Avatar,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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
|
||||
h4(v-html="$t('abuseFlagModalHeading', reportData)")
|
||||
.modal-body
|
||||
@@ -49,7 +49,7 @@ export default {
|
||||
},
|
||||
created () {
|
||||
this.$root.$on('habitica::report-chat', data => {
|
||||
if (!data.message || !data.groupId) return;
|
||||
if (!data.message) return;
|
||||
this.abuseObject = data.message;
|
||||
this.groupId = data.groupId;
|
||||
this.$root.$emit('bv::show::modal', 'report-flag');
|
||||
@@ -64,10 +64,18 @@ export default {
|
||||
},
|
||||
async reportAbuse () {
|
||||
this.notify('Thank you for reporting this violation. The moderators have been notified.');
|
||||
await this.$store.dispatch('chat:flag', {
|
||||
groupId: this.groupId,
|
||||
chatId: this.abuseObject.id,
|
||||
});
|
||||
|
||||
if (!this.groupId) {
|
||||
await this.$store.dispatch('chat:flagInbox', {
|
||||
chatId: this.abuseObject.id,
|
||||
});
|
||||
} else {
|
||||
await this.$store.dispatch('chat:flag', {
|
||||
groupId: this.groupId,
|
||||
chatId: this.abuseObject.id,
|
||||
});
|
||||
}
|
||||
|
||||
this.close();
|
||||
},
|
||||
async clearFlagCount () {
|
||||
|
||||
@@ -239,6 +239,7 @@ export default {
|
||||
user: message.user,
|
||||
uuid: message.uuid,
|
||||
id: message.id,
|
||||
flags: message.flags,
|
||||
};
|
||||
|
||||
if (message.sent) {
|
||||
|
||||
@@ -74,6 +74,12 @@ export async function flag (store, payload) {
|
||||
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) {
|
||||
let url = `/api/v3/groups/${payload.groupId}/chat/${payload.chatId}/clearflags`;
|
||||
let response = await axios.post(url);
|
||||
|
||||
@@ -66,5 +66,6 @@
|
||||
"messageNotificationNotFound": "Notification not found.",
|
||||
|
||||
"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 slack from '../../libs/slack';
|
||||
import pusher from '../../libs/pusher';
|
||||
import { getAuthorEmailFromMessage } from '../../libs/chat';
|
||||
import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory';
|
||||
import nconf from 'nconf';
|
||||
import Bluebird from 'bluebird';
|
||||
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 };
|
||||
});
|
||||
|
||||
const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL');
|
||||
/**
|
||||
* @apiDefine MessageNotFound
|
||||
* @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 = {};
|
||||
|
||||
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) {
|
||||
let bannedSlursMatched = getMatchesByWordArray(message, bannedSlurs);
|
||||
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} messageGroupChatFlagAlreadyReported The message has already been flagged
|
||||
*/
|
||||
api.flagChat = {
|
||||
api.flagGroupChat = {
|
||||
method: 'POST',
|
||||
url: '/groups/:groupId/chat/:chatId/flag',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let groupId = req.params.groupId;
|
||||
|
||||
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,
|
||||
});
|
||||
const chatReporter = chatReporterFactory('Group', req, res);
|
||||
const message = await chatReporter.flag();
|
||||
res.respond(200, 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);
|
||||
},
|
||||
};
|
||||
|
||||
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 ({
|
||||
buyer,
|
||||
recipient,
|
||||
@@ -137,5 +173,5 @@ function sendSlurNotification ({
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendFlagNotification, sendSubscriptionNotification, sendSlurNotification,
|
||||
sendFlagNotification, sendSubscriptionNotification, sendSlurNotification, sendInboxFlagNotification,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user