From 24bb1112a3ffcb7ad00ac014af34c788e4f18f41 Mon Sep 17 00:00:00 2001 From: Kan-A-Pesh Date: Tue, 27 Feb 2024 18:19:59 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20French=20badwords=20&=20imple?= =?UTF-8?q?ment=20broadcastMessage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/@types/french-badwords-list.d.ts | 4 + backend/controllers/Chat.ts | 110 ++++++++++++++++------- backend/package-lock.json | 32 +++++++ backend/package.json | 3 + backend/server/Websocket.ts | 5 ++ common/docs/socket-requests.md | 7 +- 6 files changed, 127 insertions(+), 34 deletions(-) create mode 100644 backend/@types/french-badwords-list.d.ts diff --git a/backend/@types/french-badwords-list.d.ts b/backend/@types/french-badwords-list.d.ts new file mode 100644 index 0000000..d7b5dd0 --- /dev/null +++ b/backend/@types/french-badwords-list.d.ts @@ -0,0 +1,4 @@ +declare module "french-badwords-list" { + const array: string[]; + export { array }; +} diff --git a/backend/controllers/Chat.ts b/backend/controllers/Chat.ts index 28a1ad6..fb7af5f 100644 --- a/backend/controllers/Chat.ts +++ b/backend/controllers/Chat.ts @@ -1,6 +1,14 @@ import type SocketIO from "socket.io"; -import type { Socket } from "socket.io"; -import ChatMessagePayload from "../../common/requests/ChatMessagePayload"; +import { PrismaClient } from "@prisma/client"; + +import leoProfanity from "leo-profanity"; +import frenchBadwordsList from "french-badwords-list"; +import WSS from "../server/Websocket"; + +leoProfanity.clearList(); +leoProfanity.add(frenchBadwordsList.array); + +const prisma = new PrismaClient(); class ChatController { /** @@ -10,35 +18,75 @@ class ChatController { * @param socket The client socket * @param data The payload */ - public static async broadcastMessage(socket: SocketIO.Socket, ...data: unknown[]) { - // TODO: Broadcast the message to all clients - /** - * VALIDATION - * * Validate the message data - * * Check if the user is logged in - * * Check if the user is not muted - * * Check if the user has sent more that 3 messages in the last 5 seconds - * * - Mute the user for 3 secondes (1st time) - * * - Mute the user for 10 secondes (2nd time) - * * - Mute the user for 1 minute (3rd time) - * * - Mute the user for 5 minutes (4th time) - * * - Mute the user for 30 minutes (5th time) - * * - Mute the user for 1 hour (6th time) - * * - Mute the user for 2 hours (7th time) - * * - Mute the user for 12 hours (8th time) - * * - Mute the user for 24 hours (9th time) - * * - Mute the user definitively (10th time) - * * Check if the message is not longer than 200 characters - * * Check if the message is not empty - * * Check if the message does not contain any bad words - * * Check if the message does not contain any links - * - * PROCESS - * * Log the message - * - * RESPONSE - * * Broadcast the message to all clients - */ + public static async broadcastMessage(socket: SocketIO.Socket, [message, callback]: [string, (success: boolean) => void]) { + if (!message || message.length < 1 || message.length > 200) { + callback(false); + return; + } + + // Check if the message contains bad words + const cleanMessage = leoProfanity.clean(message); + + if (!socket.data.email) { + callback(false); + return; + } + + const user = await prisma.account.findFirst({ + where: { + devinciEmail: socket.data.email, + }, + }); + + if (!user) { + callback(false); + return; + } + + if (user.isMuted) { + callback(false); + return; + } + + const now = new Date(); + + const lastTimestamps = ((user.lastSentMessageTimes as number[]) ?? []).filter((timestamp) => timestamp > now.getTime() - 5000); + if (lastTimestamps.length > 3) { + user.isMuted = true; + // Save the user + await prisma.account.update({ + where: { id: user.id }, + data: { isMuted: true }, + }); + + callback(false); + return; + } + + user.lastSentMessageTimes = [...lastTimestamps, now.getTime()]; + + // Save the user + await prisma.account.update({ + where: { id: user.id }, + data: { lastSentMessageTimes: user.lastSentMessageTimes }, + }); + + prisma.logEntry.create({ + data: { + devinciEmail: user.devinciEmail, + time: new Date().getTime(), + ip: socket.handshake.address, + action: { + type: "message", + originalContent: message, + content: cleanMessage, + }, + }, + }); + + WSS.broadcastMessage(user.devinciEmail, cleanMessage); + + callback(true); } } diff --git a/backend/package-lock.json b/backend/package-lock.json index da09644..7bc7877 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,7 +13,9 @@ "common": "file:../common", "dotenv": "^16.4.1", "express": "^4.18.2", + "french-badwords-list": "^1.0.7", "jsonwebtoken": "^9.0.2", + "leo-profanity": "^1.7.0", "mysql2": "^3.9.1", "nodemailer": "^6.9.9", "redis": "^4.6.13", @@ -23,6 +25,7 @@ "devDependencies": { "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.5", + "@types/leo-profanity": "^1.5.3", "@types/nodemailer": "^6.4.14", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -460,6 +463,12 @@ "@types/node": "*" } }, + "node_modules/@types/leo-profanity": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@types/leo-profanity/-/leo-profanity-1.5.3.tgz", + "integrity": "sha512-D+mBD0uBZJdMGPVFFLq2PK1tOdBccuftuAfxlmzJ6DXl+urtPGPDah3/OFM9ray+qCUEUyOBFpp4GGwUYr2idw==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -1613,6 +1622,11 @@ "node": ">= 0.6" } }, + "node_modules/french-badwords-list": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/french-badwords-list/-/french-badwords-list-1.0.7.tgz", + "integrity": "sha512-H1ziKs2PJh2+UXZ9oCGJ/rRQpsI9NBykGf2Sc7WaKaj1OnWFuBXfsvANTdRcfVmOghGQaUmRyZ1hJOPbDpy04Q==" + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -2040,6 +2054,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/leo-profanity": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/leo-profanity/-/leo-profanity-1.7.0.tgz", + "integrity": "sha512-88j1R08jrQzOib9Yxk4nxrzMlrHJi3DzFzAmv0L4APQ+ciGEfJ1rftVEvFjoqL0m+0KGFL3csQGRlxXGnYrA7w==", + "optionalDependencies": { + "french-badwords-list": "^1.0.6", + "russian-bad-words": "^0.5.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2644,6 +2667,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/russian-bad-words": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/russian-bad-words/-/russian-bad-words-0.5.0.tgz", + "integrity": "sha512-euNvEYki6iYYpkNbeudW+lEMMYGEmN7EBwVF8ezlbv0bZoQpVYB7W10cCeUIGV7Ed50sJynLQ0c559q5iI0ejQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/backend/package.json b/backend/package.json index ba53755..be53d15 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,6 +16,7 @@ "devDependencies": { "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.5", + "@types/leo-profanity": "^1.5.3", "@types/nodemailer": "^6.4.14", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -29,7 +30,9 @@ "common": "file:../common", "dotenv": "^16.4.1", "express": "^4.18.2", + "french-badwords-list": "^1.0.7", "jsonwebtoken": "^9.0.2", + "leo-profanity": "^1.7.0", "mysql2": "^3.9.1", "nodemailer": "^6.9.9", "redis": "^4.6.13", diff --git a/backend/server/Websocket.ts b/backend/server/Websocket.ts index 25a74dd..823c4c8 100644 --- a/backend/server/Websocket.ts +++ b/backend/server/Websocket.ts @@ -34,6 +34,11 @@ class WSS { if(!socket) this.io.emit("classementUpdate", classement); else socket.emit("classementUpdate", classement); } + + static async broadcastMessage(senderEmail: string, message: string) { + this.io.emit("message", senderEmail, message); + } + } export default WSS; diff --git a/common/docs/socket-requests.md b/common/docs/socket-requests.md index 42f3b59..89b2082 100644 --- a/common/docs/socket-requests.md +++ b/common/docs/socket-requests.md @@ -27,6 +27,7 @@ Place a pixel on the canvas Send a message to all players -| index | type | description | -| :---: | :----- | :---------- | -| 0 | string | The message | +| index | type | description | +| :---: | :---------------- | :----------- | +| 0 | string | The message | +| 1 | callback(boolean) | The callback |