canopy/www/doc/server/app_channel_commandPreprocessor.js.html

483 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/commandPreprocessor.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/commandPreprocessor.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
//NPM Imports
const validator = require('validator');//No express here, so regular validator it is!
//Local Imports
const tokebot = require('./tokebot');
const linkUtils = require('../../utils/linkUtils');
const permissionModel = require('../../schemas/permissionSchema');
const channelModel = require('../../schemas/channel/channelSchema');
/**
* Class containing global server-side chat/command pre-processing logic
*/
class commandPreprocessor{
/**
* Instantiates a commandPreprocessor object
* @param {channelManager} server - Parent Server Object
* @param {chatHandler} chatHandler - Parent Chat Handler Object
*/
constructor(server, chatHandler){
this.server = server;
this.chatHandler = chatHandler;
this.commandProcessor = new commandProcessor(server, chatHandler);
this.tokebot = new tokebot(server, chatHandler);
}
/**
* 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
this.sendChat(commandObj);
}
}
/**
* 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[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{
//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
//this.rawData.links.forEach((link) => {
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);
}
/**
* Relays chat to channel via parent Chat Handler object
* @param {Object} commandObj - Object representing a single given command/chat request
*/
sendChat(commandObj){
//FUCKIN' SEND IT!
this.chatHandler.relayUserChat(commandObj.socket, commandObj.message, commandObj.chatType, commandObj.links);
}
}
/**
* Class representing global server-side chat/command processing logic
*/
class commandProcessor{
/**
* Instantiates a commandProcessor object
* @param {channelManager} server - Parent Server Object
* @param {chatHandler} chatHandler - Parent Chat Handler Object
*/
constructor(server, chatHandler){
this.server = server;
this.chatHandler = chatHandler;
}
//Command keywords get run through .toLowerCase(), so we should use lowercase method names for command methods
/**
* Command Processor method to handle the '!whisper' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
whisper(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a whisper
commandObj.chatType = 'whisper';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!spoiler' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
spoiler(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a spoiler
commandObj.chatType = 'spoiler';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!strikethrough' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
strikethrough(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a spoiler
commandObj.chatType = 'strikethrough';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!bold' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
bold(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a spoiler
commandObj.chatType = 'bold';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!italics' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
italics(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a spoiler
commandObj.chatType = 'italics';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!announce' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async announce(commandObj, preprocessor){
//Get the current channel from the database
const chanDB = await channelModel.findOne({name: commandObj.socket.chan});
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(chanDB != null &amp;&amp; await chanDB.permCheck(commandObj.socket.user, 'announce')){
//splice out our command
commandObj.commandArray.splice(0,2);
//Prep the message using pre-processor functions chat-handling
await preprocessor.prepMessage(commandObj);
//send it
this.chatHandler.relayChannelAnnouncement(commandObj.socket.chan, commandObj.message, commandObj.links);
//throw send flag
return false;
}
//throw send flag
return true;
}
/**
* Command Processor method to handle the '!serverannounce' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async serverannounce(commandObj, preprocessor){
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(await permissionModel.permCheck(commandObj.socket.user, 'announce')){
//splice out our command
commandObj.commandArray.splice(0,2);
//Prep the message using pre-processor functions for chat-handling
await preprocessor.prepMessage(commandObj);
//send it
this.chatHandler.relayServerAnnouncement(commandObj.message, commandObj.links);
//disble send flag
return false;
}
//throw send flag
return true;
}
/**
* Command Processor method to handle the '!resettoke' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async resettoke(commandObj, preprocessor){
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(await permissionModel.permCheck(commandObj.socket.user, 'resetToke')){
//Acknowledge command
this.chatHandler.relayTokeWhisper(commandObj.socket, 'Toke cooldown reset.');
//Tell tokebot to reset the toke
preprocessor.tokebot.resetToke();
//disable send flag
return false;
}
//throw send flag
return true;
}
/**
* Command Processor method to handle the '!clear' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async clear(commandObj){
//Get the current channel from the database
const chanDB = await channelModel.findOne({name: commandObj.socket.chan});
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(await chanDB.permCheck(commandObj.socket.user, 'clearChat')){
//Send off the command
this.chatHandler.clearChat(commandObj.socket.chan, commandObj.argumentArray[1]);
//disable send flag
return false;
}
//throw send flag
return true;
}
/**
* Command Processor method to handle the '!kick' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async kick(commandObj){
//Get the current channel from the database
const chanDB = await channelModel.findOne({name: commandObj.socket.chan});
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(await chanDB.permCheck(commandObj.socket.user, 'kickUser')){
//Get username from argument array
const username = commandObj.argumentArray[1];
//Get channel
const channel = this.server.activeChannels.get(commandObj.socket.chan);
//get initiator and target user objects
const initiator = channel.userList.get(commandObj.socket.user.user);
const target = channel.userList.get(username);
//get initiator and target override abilities
const override = await permissionModel.overrideCheck(commandObj.socket.user, 'kickUser');
const targetOverride = await permissionModel.overrideCheck(target, 'kickUser');
//If there is no user
if(target == null){
//silently drop the command
return false;
}
//If the user is capable of overriding this permission based on site permissions
if(override || targetOverride){
//If the site rank is equal
if(permissionModel.rankToNum(initiator.rank) == permissionModel.rankToNum(target.rank)){
//compare chan rank
if(permissionModel.rankToNum(initiator.chanRank) &lt;= permissionModel.rankToNum(target.chanRank)){
//shame the person running it
return true;
}
//otherwise
}else{
//compare site rank
if(permissionModel.rankToNum(initiator.rank) &lt;= permissionModel.rankToNum(target.rank)){
//shame the person running it
return true;
}
}
}else{
//If the target has a higher chan rank than the initiator
if(permissionModel.rankToNum(initiator.chanRank) &lt;= permissionModel.rankToNum(target.chanRank)){
//shame the person running it
return true;
}
}
//Splice out kick
commandObj.commandArray.splice(0,4)
//Get collect reason
var reason = commandObj.commandArray.join('');
//If no reason was given
if(reason == ''){
//Fill in a generic reason
reason = "You have been kicked from the channel!";
}
//Kick the user
target.disconnect(reason);
//throw send flag
return false;
}
//throw send flag
return true;
}
}
module.exports = commandPreprocessor;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Sep 05 2025 05:55:07 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>