/*Canopy - The next generation of stoner streaming software Copyright (C) 2024-2025 Rainbownapkin and the TTN Community This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ //NPM Imports const validator = require('validator');//No express here, so regular validator it is! //Local Imports const linkUtils = require('../utils/linkUtils'); const commandProcessor = require('./channel/commandProcessor'); /** * Class containing global server-side chat/command pre-processing logic */ class chatPreprocessor{ /** * Instantiates a commandPreprocessor object * @param {commandProcessor} - Child Command Processor Object. Contains functions named after commands. */ constructor(commandProcessor, tokebot){ /** * Child Command Processor Object. Contains functions named after commands. */ this.commandProcessor = commandProcessor; /** * Child Tokebot Object */ this.tokebot = tokebot; } /** * Ingests a command/chat request from Chat Handler and pre-processes and processes it accordingly * @param {Socket} socket - Socket we're receiving the request from * @param {Object} data - Event payload */ async preprocess(socket, data){ //Set command object const commandObj = { socket, sendFlag: true, rawData: data, chatType: 'chat' } //If we don't pass sanatization/validation turn this car around if(!this.sanatizeCommand(commandObj)){ return; } //split the command this.splitCommand(commandObj); //Process the command await this.processServerCommand(commandObj); //If we're going to relay this command as a message, continue on to chat processing if(commandObj.sendFlag){ //Prep the message await this.prepMessage(commandObj); //Send the chat return commandObj; } return false; } /** * Sanatizes and Validates a single user chat message/command * @param {Object} commandObj - Object representing a single given command/chat request * @returns {Boolean} false if Command/Message is too long to send */ sanatizeCommand(commandObj){ //Trim and Sanatize for XSS commandObj.command = validator.trim(validator.escape(commandObj.rawData.msg)); //Return whether or not the shit was long enough return (validator.isLength(commandObj.rawData.msg, {min: 1, max: 255})); } /** * Splits raw chat/command data into seperate arrays, one by word-borders and words surrounded by word-borders * These arrays are used to handle further command/chat processing * @param {Object} commandObj - Object representing a single given command/chat request */ splitCommand(commandObj){ //Split string by words commandObj.commandArray = commandObj.command.split(/\b/g);//Split by word-borders commandObj.argumentArray = commandObj.command.match(/\b\w+\b/g);//Match by words surrounded by borders } /** * Uses the server's Command Processor object to process the chat/command request. * @param {Object} commandObj - Object representing a single given command/chat request */ async processServerCommand(commandObj){ //If the raw message starts with '!' (skip commands that start with whitespace so people can send example commands in chat) if(commandObj.rawData.msg[0] == '!'){ //if it isn't just an exclimation point, and we have a real command if(commandObj.argumentArray != null){ //If the command processor knows what to do with whatever the fuck the user sent us if(this.commandProcessor != null && this.commandProcessor[commandObj.argumentArray[0].toLowerCase()] != null){ //Process the command and use the return value to set the sendflag (true if command valid) commandObj.sendFlag = await this.commandProcessor[commandObj.argumentArray[0].toLowerCase()](commandObj, this); }else if(this.tokebot != null){ //Process as toke command if we didnt get a match from the standard server-side command processor commandObj.sendFlag = await this.tokebot.tokeProcessor(commandObj); } } } } /** * Iterates through links in message and marks them by link type for later use by client-side post-processing * @param {Object} commandObj - Object representing a single given command/chat request */ async markLinks(commandObj){ //Setup the links array commandObj.links = []; //For each link sent from the client for (const link of commandObj.rawData.links){ //Add a marked link object to our links array commandObj.links.push(await linkUtils.markLink(link)); } } /** * Re-creates message string from processed Command Array * @param {Object} commandObj - Object representing a single given command/chat request */ async prepMessage(commandObj){ //Create message from commandArray commandObj.message = commandObj.commandArray.join('').trimStart(); //Validate links and mark them by embed type await this.markLinks(commandObj); } } module.exports = chatPreprocessor;