Added block when user types a swear word listed in banned words (#8197)

* Added block when user types a swear word listed in banned words

* Moved banned words check to server

* Removed unused code

* Moved banned words to separate file and fixed grammar.

* Updated chat test

* Changed error to BadRequest

* Fixed regex matching

* Updated test banned word

* Moved banned words and cached regex

* Updated banned word message

* Add ban filter only for tavern

* Added tavern id constant

* Added more tests for banned words

* Added warning to banned words

* Added alert

* Added new regex to capture markdown

* Fixed lint, spelling and importing
This commit is contained in:
Keith Holliday
2017-04-24 07:55:42 -06:00
committed by GitHub
parent 7d3213fd66
commit d438990d18
7 changed files with 159 additions and 4 deletions

View File

@@ -9,6 +9,7 @@ import { v4 as generateUUID } from 'uuid';
describe('POST /chat', () => {
let user, groupWithChat, userWithChatRevoked, member;
let testMessage = 'Test Message';
let testBannedWordMessage = 'TEST_PLACEHOLDER_SWEAR_WORD_HERE';
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
@@ -69,6 +70,96 @@ describe('POST /chat', () => {
});
});
context('banned word', () => {
it('returns an error when chat message contains a banned word in tavern', async () => {
await expect(user.post('/groups/habitrpg/chat', { message: testBannedWordMessage}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('bannedWordUsed'),
});
});
it('errors when word is part of a phrase', async () => {
let wordInPhrase = `phrase ${testBannedWordMessage} end`;
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('bannedWordUsed'),
});
});
it('errors when word is surrounded by non alphabet characters', async () => {
let wordInPhrase = `_!${testBannedWordMessage}@_`;
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('bannedWordUsed'),
});
});
it('does not error when bad word is suffix of a word', async () => {
let wordAsSuffix = `prefix${testBannedWordMessage}`;
let message = await user.post('/groups/habitrpg/chat', { message: wordAsSuffix});
expect(message.message.id).to.exist;
});
it('does not error when bad word is prefix of a word', async () => {
let wordAsPrefix = `${testBannedWordMessage}suffix`;
let message = await user.post('/groups/habitrpg/chat', { message: wordAsPrefix});
expect(message.message.id).to.exist;
});
it('does not error when sending a chat message containing a banned word to a party', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Party',
type: 'party',
privacy: 'private',
},
members: 1,
});
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
expect(message.message.id).to.exist;
});
it('does not error when sending a chat message containing a banned word to a public guild', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'public guild',
type: 'guild',
privacy: 'public',
},
members: 1,
});
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
expect(message.message.id).to.exist;
});
it('does not error when sending a chat message containing a banned word to a private guild', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'private guild',
type: 'guild',
privacy: 'private',
},
members: 1,
});
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
expect(message.message.id).to.exist;
});
});
it('does not error when sending a message to a private guild with a user with revoked chat', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {

View File

@@ -1,7 +1,7 @@
'use strict';
habitrpg.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', 'Analytics',
function($scope, Groups, Chat, User, $http, ApiUrl, Notification, Members, $rootScope, Analytics){
function($scope, Groups, Chat, User, $http, ApiUrl, Notification, Members, $rootScope, Analytics) {
$scope.message = {content:''};
$scope._sending = false;
@@ -23,7 +23,7 @@ habitrpg.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'A
return message.highlight;
}
$scope.postChat = function(group, message){
$scope.postChat = function(group, message) {
if (_.isEmpty(message) || $scope._sending) return;
$scope._sending = true;
@@ -55,8 +55,12 @@ habitrpg.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'A
} else {
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy});
}
}, function(err){
})
.catch(function (err) {
$scope._sending = false;
if (err.data.message === env.t('bannedWordUsed')) {
alert(err.data.message);
}
});
}

View File

@@ -21,6 +21,7 @@
"communityGuidelines": "Community Guidelines",
"communityGuidelinesRead1": "Please read our",
"communityGuidelinesRead2": "before chatting.",
"bannedWordUsed": "Oops! Looks like this post contains a swearword, religious oath, or reference to an addictive substance or adult topic. Habitica has users from all backgrounds, so we keep our chat very clean. Feel free to edit your message so you can post it!",
"party": "Party",
"createAParty": "Create A Party",
"updatedParty": "Party settings updated.",

View File

@@ -60,5 +60,6 @@
"messageUserOperationProtected": "path `<%= operation %>` was not saved, as it's a protected path.",
"messageUserOperationNotFound": "<%= operation %> operation not found",
"messageNotificationNotFound": "Notification not found.",
"notificationsRequired": "Notification ids are required."
}

View File

@@ -2,6 +2,7 @@ import { authWithHeaders } from '../../middlewares/auth';
import { model as Group } from '../../models/group';
import { model as User } from '../../models/user';
import {
BadRequest,
NotFound,
NotAuthorized,
} from '../../libs/errors';
@@ -12,6 +13,8 @@ import slack from '../../libs/slack';
import pusher from '../../libs/pusher';
import nconf from 'nconf';
import Bluebird from 'bluebird';
import bannedWords from '../../libs/bannedWords';
import { TAVERN_ID } from '../../models/group';
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => {
return { email, canSend: true };
@@ -82,6 +85,28 @@ api.getChat = {
},
};
// @TODO: Probably move this to a library
function matchExact (r, str) {
let match = str.match(r);
return match !== null && match[0] !== null;
}
let bannedWordRegexs = [];
for (let i = 0; i < bannedWords.length; i += 1) {
let word = bannedWords[i];
let regEx = new RegExp(`\\b([^a-z]+)?${word.toLowerCase()}([^a-z]+)?\\b`);
bannedWordRegexs.push(regEx);
}
function textContainsBannedWords (message) {
for (let i = 0; i < bannedWordRegexs.length; i += 1) {
let regEx = bannedWordRegexs[i];
if (matchExact(regEx, message.toLowerCase())) return true;
}
return false;
}
/**
* @api {post} /api/v3/groups/:groupId/chat Post chat message to a group
* @apiName PostChat
@@ -121,6 +146,10 @@ api.postChat = {
throw new NotFound('Your chat privileges have been revoked.');
}
if (group._id === TAVERN_ID && textContainsBannedWords(req.body.message)) {
throw new BadRequest(res.t('bannedWordUsed'));
}
let lastClientMsg = req.query.previousMsg;
chatUpdated = lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg ? true : false;

View File

@@ -0,0 +1,30 @@
/* eslint-disable no-multiple-empty-lines */
// CONTENT WARNING:
// This file contains slurs, swear words, religious oaths, and words related to addictive substance and adult topics.
// Do not read this file if you do not want to be exposed to those words.
// The words are stored in an array called `bannedWords` which is then exported with `module.exports = bannedWords;`
// This file does not contain any other code.
let bannedWords = ['TEST_PLACEHOLDER_SWEAR_WORD_HERE'];
module.exports = bannedWords;

View File

@@ -24,4 +24,3 @@ form.chat-form(ng-if='user.flags.communityGuidelinesAccepted' ng-submit='postCha
label
input(type='checkbox', ng-model='user.preferences.reverseChatOrder', ng-change='set({"preferences.reverseChatOrder": user.preferences.reverseChatOrder?true: false})')
span=env.t('reverseChatOrder')