Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement automated content filtering (User Story #1) #47

Merged
merged 13 commits into from
Oct 20, 2024
25 changes: 25 additions & 0 deletions inappropriate-words.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[
"verybadword",
"idiot",
"moron",
"stupid",
"dumb",
"loser",
"worthless",
"lame",
"pathetic",
"fool",
"trash",
"hell",
"lmao",
"lmfao",
"beer",
"dammit",
"kill",
"nigga",
"wtf",
"shit",
"weed",
"sadist",
"goddamn"
]
13 changes: 13 additions & 0 deletions src/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict';

const inappropriateWordsCache = require('../inappropriate-words.json');

/**
* Returns the cached array of inappropriate words.
* @returns {string[]} An array of inappropriate words.
*/
function getInappropriateWords() {
return inappropriateWordsCache;
}

module.exports = { getInappropriateWords };
20 changes: 18 additions & 2 deletions src/topics/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const posts = require('../posts');
const privileges = require('../privileges');
const categories = require('../categories');
const translator = require('../translator');
const filteringSys = require('../filters');

module.exports = function (Topics) {
Topics.create = async function (data) {
Expand Down Expand Up @@ -91,10 +92,17 @@ module.exports = function (Topics) {
data.title = String(data.title).trim();
data.tags = data.tags || [];
data.content = String(data.content || '').trimEnd();

if (filterInappropriateWords(data.title)) {
throw new Error('Your title contains inappropriate words, please update it accordingly');
}
if (filterInappropriateWords(data.content)) {
throw new Error('Your post contains inappropriate words, please update it accordingly');
}

if (!isAdmin) {
Topics.checkTitle(data.title);
}

await Topics.validateTags(data.tags, data.cid, uid);
data.tags = await Topics.filterTags(data.tags, data.cid);
if (!data.fromQueue && !isAdmin) {
Expand Down Expand Up @@ -183,14 +191,17 @@ module.exports = function (Topics) {
await guestHandleValid(data);
data.content = String(data.content || '').trimEnd();

if (filterInappropriateWords(data.content)) {
throw new Error('Your reply contains inappropriate words, please update it accordingly');
}

if (!data.fromQueue && !isAdmin) {
await user.isReadyToPost(uid, data.cid);
Topics.checkContent(data.content);
if (!await posts.canUserPostContentWithLinks(uid, data.content)) {
throw new Error(`[[error:not-enough-reputation-to-post-links, ${meta.config['min:rep:post-links']}]]`);
}
}

// For replies to scheduled topics, don't have a timestamp older than topic's itself
if (topicData.scheduled) {
data.timestamp = topicData.lastposttime + 1;
Expand Down Expand Up @@ -259,6 +270,11 @@ module.exports = function (Topics) {
return postData;
}

function filterInappropriateWords(content) {
const inappropriateWords = filteringSys.getInappropriateWords();
return inappropriateWords.some(word => content.toLowerCase().includes(word));
}

Topics.checkTitle = function (title) {
check(title, meta.config.minimumTitleLength, meta.config.maximumTitleLength, 'title-too-short', 'title-too-long');
};
Expand Down
31 changes: 31 additions & 0 deletions test/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,37 @@ describe('Post\'s', () => {
assert(false);
});

// adding test cases for inappropriate words

it('should error if content contains inappropriate words', async () => {
try {
await apiPosts.edit({ uid: voterUid }, { pid: pid, content: 'This post contains an inappropriate word like stupid' });
} catch (err) {
return assert.equal(err.message, '[[error:inappropriate-words]]');
}
assert(true);
});

it('should error if title contains inappropriate words', async () => {
try {
await apiPosts.edit({ uid: voterUid }, { pid: pid, content: 'Normal content', title: 'This title contains an inappropriate word like stupid' });
} catch (err) {
return assert.equal(err.message, '[[error:inappropriate-words]]');
}
assert(true);
});

it('should allow edit if content and title do not contain inappropriate words', async () => {
const data = await apiPosts.edit({ uid: voterUid }, {
pid: pid,
content: 'Valid content',
title: 'Valid title',
tags: ['nodebb'],
});
assert.strictEqual(data.content, 'Valid content');
assert.strictEqual(data.topic.title, 'Valid title');
});

it('should error with too few tags', async () => {
const oldValue = meta.config.minimumTagsPerTopic;
meta.config.minimumTagsPerTopic = 1;
Expand Down