Skip to content

Commit

Permalink
Merge pull request #86 from La-404-Devinci/dev
Browse files Browse the repository at this point in the history
Dev
Kan-A-Pesh authored Mar 27, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents b8153fc + 9f5a5ed commit b339d00
Showing 8 changed files with 203 additions and 79 deletions.
2 changes: 1 addition & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ EMAIL_PASS="password"
EMAIL_FROM="email@sender.com"
EMAIL_SECURE="true"

DISCORD_HOOK_URL="https://discordapp.com/api/webhooks/..."
DISCORD_WEBHOOK_URL="https://discordapp.com/api/webhooks/..."

FRONTEND_URL="http://localhost:3000"

160 changes: 82 additions & 78 deletions backend/controllers/Chat.ts
Original file line number Diff line number Diff line change
@@ -22,100 +22,104 @@ class ChatController {
* @param data The payload
*/
public static async broadcastMessage(socket: SocketIO.Socket, [message, callback]: [string, (success: boolean) => void]) {
if (!message || message.length < 1 || message.length > 200) {
console.log("Message is empty or too long");
callback(false);
return;
}
try {
if (!message || message.length < 1 || message.length > 200) {
console.log("Message is empty or too long");
callback(false);
return;
}

// Check if the message contains bad words
const cleanMessage = leoProfanity.clean(message);

if (!socket.data.email) {
console.log("User is not authenticated");
callback(false);
return;
}

const user = await prisma.account.findFirst({
where: {
devinciEmail: socket.data.email,
},
});

// Check if the message contains bad words
const cleanMessage = leoProfanity.clean(message);
if (!user) {
console.log("User not found");
callback(false);
return;
}

if (!socket.data.email) {
console.log("User is not authenticated");
callback(false);
return;
}
if (user.isMuted) {
console.log("User is muted");
callback(false);
return;
}

const user = await prisma.account.findFirst({
where: {
devinciEmail: socket.data.email,
},
});
const now = new Date();

if (!user) {
console.log("User not found");
callback(false);
return;
}
const lastTimestamps = ((user.lastSentMessageTimes as number[]) ?? []).filter((timestamp) => timestamp > now.getTime() - 5000);
if (lastTimestamps.length > 4) {
user.isMuted = true;
// Save the user
await prisma.account.update({
where: { id: user.id },
data: { isMuted: true },
});

if (user.isMuted) {
console.log("User is muted");
callback(false);
return;
}
callback(false);
return;
}

const now = new Date();
user.lastSentMessageTimes = [...lastTimestamps, now.getTime()];

const lastTimestamps = ((user.lastSentMessageTimes as number[]) ?? []).filter((timestamp) => timestamp > now.getTime() - 5000);
if (lastTimestamps.length > 4) {
user.isMuted = true;
// Save the user
await prisma.account.update({
where: { id: user.id },
data: { isMuted: true },
data: { lastSentMessageTimes: user.lastSentMessageTimes, messagesSent: user.messagesSent + 1 },
});

callback(false);
return;
}

user.lastSentMessageTimes = [...lastTimestamps, now.getTime()];

// Save the user
await prisma.account.update({
where: { id: user.id },
data: { lastSentMessageTimes: user.lastSentMessageTimes, messagesSent: user.messagesSent + 1 },
});

await prisma.logEntry.create({
data: {
devinciEmail: user.devinciEmail,
time: new Date().getTime(),
ip: socket.handshake.address,
action: {
type: "message",
originalContent: message,
content: cleanMessage,
},
},
});

// Add the message to the history
ChatController._messageHistory.push([user.devinciEmail, cleanMessage]);
if (ChatController._messageHistory.length > 30) ChatController._messageHistory.shift();

WSS.broadcastMessage(user.devinciEmail, cleanMessage);
WSS.updateUserData(socket, user);

// Send message to discord (using the webhook)
try {
await fetch(process.env.DISCORD_WEBHOOK_URL as string, {
method: "POST",
headers: {
"Content-Type": "application/json",
await prisma.logEntry.create({
data: {
devinciEmail: user.devinciEmail,
time: new Date().getTime(),
ip: socket.handshake.address,
action: {
type: "message",
originalContent: message,
content: cleanMessage,
},
},
body: JSON.stringify({
avatar_url: `https://ui-avatars.com/api/?name=${user.devinciEmail}&background=0D8ABC&color=fff`,
username: user.devinciEmail,
content: cleanMessage,
}),
});

// Add the message to the history
ChatController._messageHistory.push([user.devinciEmail, cleanMessage]);
if (ChatController._messageHistory.length > 30) ChatController._messageHistory.shift();

WSS.broadcastMessage(user.devinciEmail, cleanMessage);
WSS.updateUserData(socket, user);

// Send message to discord (using the webhook)
try {
await fetch(process.env.DISCORD_WEBHOOK_URL as string, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
avatar_url: `https://ui-avatars.com/api/?name=${user.devinciEmail}&background=0D8ABC&color=fff`,
username: user.devinciEmail,
content: cleanMessage,
}),
});
} catch (error) {
console.error("Error sending message to Discord", error);
}

callback(true);
} catch (error) {
console.error("Error sending message to Discord", error);
console.error("Error broadcasting message", error);
}

callback(true);
}

/**
40 changes: 40 additions & 0 deletions backend/controllers/Goofy.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import WSS from "../server/Websocket";
class GoofyController {
private static _tracks: string[] = [];
private static _banned: string[] = [];
private static _toast?: string = undefined;

/**
* Get the admin page
@@ -111,6 +112,45 @@ class GoofyController {
public static isBanned(ip: string): boolean {
return GoofyController._banned.includes(ip);
}

/**
* Edit the toast message
* @server HTTP
*
* @param req The Express request object
* @param res The Express response object
* @param next The Express next function
*/
public static async sendToast(req: express.Request, res: express.Response, next: express.NextFunction) {
try {
const { toast } = req.body;

GoofyController._toast = toast;
WSS.broadcastToast(toast);

res.status(200).send("Toast sent");
} catch (error) {
next(error);
}
}

/**
* Get the toast message
* @server HTTP
*
* @param req The Express request object
* @param res The Express response object
* @param next The Express next function
*/
public static async getToast(req: express.Request, res: express.Response, next: express.NextFunction) {
try {
res.status(200).json({
toast: GoofyController._toast || null,
});
} catch (error) {
next(error);
}
}
}

export default GoofyController;
2 changes: 2 additions & 0 deletions backend/index.ts
Original file line number Diff line number Diff line change
@@ -116,6 +116,7 @@ app.get("/auth/login", AccountController.login);
app.get("/canvas/image", CanvasController.getCanvasImage);
app.get("/canvas/palette", CanvasController.getCanvasPalette);
app.get("/canvas/history", CanvasController.getPixelHistory);
app.get("/toast", GoofyController.getToast);

// Asso routes
if (process.env.DEMO !== "true") {
@@ -136,6 +137,7 @@ router.get("/banip", GoofyController.getBannedIPs);
router.post("/banip", GoofyController.banIP);
router.post("/refresh", GoofyController.forceRefresh);
router.post("/clearchat", ChatController.clearMessages);
router.post("/toast", GoofyController.sendToast);

router.post("/auth/ban", AccountController.banUser);
router.post("/auth/mute", AccountController.muteUser);
8 changes: 8 additions & 0 deletions backend/server/Websocket.ts
Original file line number Diff line number Diff line change
@@ -70,6 +70,14 @@ class WSS {
this.io.emit("message", senderEmail, message);
}

/**
* Broadcast a toast change to everyone
* @param toast The new toast message
*/
static async broadcastToast(toast: string) {
this.io.emit("toast", toast);
}

/**
* Sends an 'updateCanvasPixel' event to all connected clients.
* @param x The x coordinate of the pixel
3 changes: 3 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import AssoModal from "./components/modal/asso";
import API from "./utils/api";

import iconUserLarge from "./assets/user-large.svg";
import ToastComponent from "./components/toast";

function App() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -150,6 +151,8 @@ function App() {
<Timer time={time} />
</div>

<ToastComponent />

<div className={styles.navbarContainer}>
<LeaderboardComponent />
<ModalReward />
24 changes: 24 additions & 0 deletions frontend/src/components/toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect, useState } from "react";
import { socket } from "../socket";
import API from "../utils/api";
import styles from "../styles/toast.module.css";

export default function ToastComponent() {
const [toast, setToast] = useState<string | null>(null);

useEffect(() => {
socket.on("toast", (toast: string) => {
setToast(toast);
});

API.GET("/toast").then((res) => {
setToast(res.toast);
});

return () => {
socket.off("toast");
};
}, []);

return toast ? <div className={styles.toast} key={toast} dangerouslySetInnerHTML={{ __html: toast }}></div> : null;
}
43 changes: 43 additions & 0 deletions frontend/src/styles/toast.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.toast {
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
gap: 4px;
position: fixed;
top: 32px;
left: 50%;
transform: translateX(-50%);
border-radius: 8px;
padding: 8px 16px;
background: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
animation: slideIn 2.5s forwards, fadeOut 0.5s 5s forwards;
}

@keyframes slideIn {
0% {
top: -100px;
}
50% {
top: 32px;
}
60% {
background: white;
}
60.01% {
background: red;
}
100% {
background: white;
}
}

@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}

0 comments on commit b339d00

Please sign in to comment.