From 23df4f88f92741d4a968471b2db90c6d536d17dc Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Mon, 16 Dec 2024 09:01:41 -0500 Subject: [PATCH] Server now seperates links and media. Client now embeds links, but not media. --- src/app/channel/chatHandler.js | 36 ++++---- src/app/channel/commandPreprocessor.js | 120 +++++++++++++++++-------- www/css/theme/movie-night.css | 4 + www/js/channel/chatPostprocessor.js | 39 +++++++- 4 files changed, 141 insertions(+), 58 deletions(-) diff --git a/src/app/channel/chatHandler.js b/src/app/channel/chatHandler.js index 688ee15..0940d39 100644 --- a/src/app/channel/chatHandler.js +++ b/src/app/channel/chatHandler.js @@ -79,45 +79,45 @@ module.exports = class{ } } - relayUserChat(socket, msg, type){ + relayUserChat(socket, msg, type, links){ const user = this.server.getSocketInfo(socket); - this.relayChat(user.user, user.flair, user.highLevel, msg, type, socket.chan) + this.relayChat(user.user, user.flair, user.highLevel, msg, type, socket.chan, links) } - relayChat(user, flair, highLevel, msg, type = 'chat', chan){ - this.server.io.in(chan).emit("chatMessage", {user, flair, highLevel, msg, type}); + relayChat(user, flair, highLevel, msg, type = 'chat', chan, links){ + this.server.io.in(chan).emit("chatMessage", {user, flair, highLevel, msg, type, links}); } - relayServerWisper(socket, user, flair, highLevel, msg, type){ - socket.emit("chatMessage", {user, flair, highLevel, msg, type}); + relayServerWisper(socket, user, flair, highLevel, msg, type, links){ + socket.emit("chatMessage", {user, flair, highLevel, msg, type, links}); } - relayGlobalChat(user, flair, highLevel, msg, type = 'chat'){ - this.server.io.emit("chatMessage", {user, flair, highLevel, msg, type}); + relayGlobalChat(user, flair, highLevel, msg, type = 'chat', links){ + this.server.io.emit("chatMessage", {user, flair, highLevel, msg, type, links}); } - relayTokeCallout(msg){ - this.relayGlobalChat("Tokebot", "", '∞', msg, "toke"); + relayTokeCallout(msg, links){ + this.relayGlobalChat("Tokebot", "", '∞', msg, "toke", links); } - relayTokeWhisper(socket, msg){ - this.relayServerWisper(socket, "Tokebot", "", '∞', msg, "tokewhisper"); + relayTokeWhisper(socket, msg, links){ + this.relayServerWisper(socket, "Tokebot", "", '∞', msg, "tokewhisper", links); } - relayGlobalTokeWhisper(msg){ - this.relayGlobalChat("Tokebot", "", '∞', msg, "tokewhisper"); + relayGlobalTokeWhisper(msg, links){ + this.relayGlobalChat("Tokebot", "", '∞', msg, "tokewhisper", links); } - relayServerAnnouncement(msg){ - this.relayGlobalChat("Server", "", '∞', msg, "announcement"); + relayServerAnnouncement(msg, links){ + this.relayGlobalChat("Server", "", '∞', msg, "announcement", links); } - relayChannelAnnouncement(chan, msg){ + relayChannelAnnouncement(chan, msg, links){ const activeChan = this.server.activeChannels.get(chan); //If channel isn't null if(activeChan != null){ - this.relayChat("Channel", "", '∞', msg, "announcement", chan); + this.relayChat("Channel", "", '∞', msg, "announcement", chan, links); } } diff --git a/src/app/channel/commandPreprocessor.js b/src/app/channel/commandPreprocessor.js index b46a38b..18d477c 100644 --- a/src/app/channel/commandPreprocessor.js +++ b/src/app/channel/commandPreprocessor.js @@ -35,6 +35,7 @@ module.exports = class commandPreprocessor{ this.socket = socket; this.sendFlag = true; this.rawData = data; + this.chatType = 'chat'; //If we don't pass sanatization/validation turn this car around if(!this.sanatizeCommand()){ @@ -45,8 +46,18 @@ module.exports = class commandPreprocessor{ this.splitCommand(); //Process the command await this.processServerCommand(); - //Send it as chat (assuming the flag isn't false) - this.sendChat(); + + //If we're going to relay this command as a message, continue on to chat processing + if(this.sendFlag){ + //Create message from commandArray + this.message = this.commandArray.join('').trimStart(); + + //Validate links and mark them by embed type + await this.markLinks(); + + //Send the chat + this.sendChat(); + } } sanatizeCommand(){ @@ -66,35 +77,72 @@ module.exports = class commandPreprocessor{ async processServerCommand(){ //If the raw message starts with '!' (skip commands that start with whitespace so people can send example commands in chat) if(this.rawData.msg[0] == '!'){ - //Create hash table to hold information about current command - const commandObj = { - socket: this.socket, - commandArray: this.commandArray, - argumentArray: this.argumentArray, - rawData: this.rawData - } - //if it isn't just an exclimation point, and we have a real command if(this.argumentArray != null){ if(this.commandProcessor[this.argumentArray[0].toLowerCase()] != null){ //Process the command and use the return value to set the sendflag (true if command valid) - this.sendFlag = await this.commandProcessor[this.argumentArray[0].toLowerCase()](commandObj); - + this.sendFlag = await this.commandProcessor[this.argumentArray[0].toLowerCase()](this); }else{ //Process as toke command if we didnt get a match from the standard server-side command processor - this.sendFlag = await this.tokebot.tokeProcessor(commandObj); + this.sendFlag = await this.tokebot.tokeProcessor(this); } } } } - sendChat(){ - //If the send flag is true - if(this.sendFlag){ - //FUCKIN' SEND IT! - this.chatHandler.relayUserChat(this.socket, this.commandArray.join('').trimStart()); + async markLinks(){ + const maxSize = 4000000; + //Empty out the links array + this.links = []; + + //For each link sent from the client + //this.rawData.links.forEach((link) => { + for (const link of this.rawData.links){ + //Set standard link type + var linkType = 'link'; + + //Pull content type + var response = await fetch(link,{ + method: "HEAD", + }); + + //Get file type from header + const fileType = response.headers.get('content-type'); + const fileSize = response.headers.get('content-length'); + + + //If they're reporting file types + if(fileType != null){ + //If we have an image + if(fileType.match('image/')){ + //If the file size is unreported OR it's smaller than 4MB (not all servers report this and images that big are pretty rare) + if(fileSize == null || fileSize <= maxSize){ + //Mark link as an image + linkType = 'image'; + } + //If it's a video + }else if(fileType.match('video/mp4' || 'video/webm')){ + //If the server is reporting file-size and it's reporting under 4MB (Reject unreported sizes to be on the safe side is video is huge) + if(fileSize != null && fileSize <= maxSize){ + //mark link as a video + linkType = 'video'; + } + } + } + + //Add a marked link object to our links array + this.links.push({ + link, + linkType + }); + } } + + sendChat(){ + //FUCKIN' SEND IT! + this.chatHandler.relayUserChat(this.socket, this.message, this.chatType, this.links); + } } class commandProcessor{ @@ -104,27 +152,27 @@ class commandProcessor{ } //Command keywords get run through .toLowerCase(), so we should use lowercase method names for command methods - whisper(commandObj){ + whisper(preprocessor){ //splice out our whisper - commandObj.commandArray.splice(0,2); + preprocessor.commandArray.splice(0,2); - //send it - this.chatHandler.relayUserChat(commandObj.socket, commandObj.commandArray.join(''), 'whisper'); + //Mark out the current message as a whisper + preprocessor.chatType = 'whisper'; //Make sure to throw the send flag - return false; + return true } - async announce(commandObj){ + async announce(preprocessor){ //Get the current channel from the database - const chanDB = await channelModel.findOne({name: commandObj.socket.chan}); + const chanDB = await channelModel.findOne({name: preprocessor.socket.chan}); //Check if the user has permission, and publicly shame them if they don't (lmao) - if(chanDB != null && await chanDB.permCheck(commandObj.socket.user, 'announce')){ + if(chanDB != null && await chanDB.permCheck(preprocessor.socket.user, 'announce')){ //splice out our whisper - commandObj.commandArray.splice(0,2); + preprocessor.commandArray.splice(0,2); //send it - this.chatHandler.relayChannelAnnouncement(commandObj.socket.chan, commandObj.commandArray.join('')); + this.chatHandler.relayChannelAnnouncement(preprocessor.socket.chan, preprocessor.commandArray.join('')); //throw send flag return false; } @@ -133,13 +181,13 @@ class commandProcessor{ return true; } - async serverannounce(commandObj){ + async serverannounce(preprocessor){ //Check if the user has permission, and publicly shame them if they don't (lmao) - if(await permissionModel.permCheck(commandObj.socket.user, 'announce')){ + if(await permissionModel.permCheck(preprocessor.socket.user, 'announce')){ //splice out our whisper - commandObj.commandArray.splice(0,2); + preprocessor.commandArray.splice(0,2); //send it - this.chatHandler.relayServerAnnouncement(commandObj.commandArray.join('')); + this.chatHandler.relayServerAnnouncement(preprocessor.commandArray.join('')); //throw send flag return false; } @@ -148,14 +196,14 @@ class commandProcessor{ return true; } - async clear(commandObj){ + async clear(preprocessor){ //Get the current channel from the database - const chanDB = await channelModel.findOne({name: commandObj.socket.chan}); + const chanDB = await channelModel.findOne({name: preprocessor.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')){ + if(await chanDB.permCheck(preprocessor.socket.user, 'clearChat')){ //Send off the command - this.chatHandler.clearChat(commandObj.socket.chan, commandObj.argumentArray[1]); + this.chatHandler.clearChat(preprocessor.socket.chan, preprocessor.argumentArray[1]); //throw send flag return false; } diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css index 894b0c9..ab0b1df 100644 --- a/www/css/theme/movie-night.css +++ b/www/css/theme/movie-night.css @@ -295,6 +295,10 @@ select.panel-head-element{ color: var(--accent1); } +.chat-link{ + color: var(--focus0); +} + /* popup */ .popup-backer{ diff --git a/www/js/channel/chatPostprocessor.js b/www/js/channel/chatPostprocessor.js index 8effde8..0619c17 100644 --- a/www/js/channel/chatPostprocessor.js +++ b/www/js/channel/chatPostprocessor.js @@ -11,6 +11,9 @@ class chatPostprocessor{ //We could pass this through arguments but these functions wont be very interoperable anyways since they expect a purpose-made hashtable this.splitBody(); + //Inject links into un-processed placeholders + this.processLinks(); + //Inject whitespace into un-processed words this.addWhitespace(); @@ -26,8 +29,8 @@ class chatPostprocessor{ splitBody(){ //Create an empty array to hold the body this.bodyArray = []; - //Split string by words - const splitString = this.chatBody.innerHTML.split(/\b/g); //Group words together + //Split string by words (except for file seperator to keep link placeholders in-tact) + const splitString = this.chatBody.innerHTML.split(/(? { @@ -48,7 +51,18 @@ class chatPostprocessor{ //Extract strings into the empty array this.bodyArray.forEach((wordObj) => { - stringArray.push(wordObj.string); + if(wordObj.type == 'word'){ + stringArray.push(wordObj.string); + }else if(wordObj.type == 'link'){ + //Create a link node from our link + const link = document.createElement('a'); + link.classList.add('chat-link'); + link.href = wordObj.link; + link.innerHTML = wordObj.link; + + //Inject it into the original string, and add it to string array + stringArray.push(wordObj.string.replace('␜',link.outerHTML)); + } }); //Join the strings to fill the chat entry @@ -62,7 +76,6 @@ class chatPostprocessor{ if(wordObj.type == "word"){ var wordArray = []; //Add invisible whitespace in-between characters to keep it from breaking page layout - //this.bodyArray[index].string = wordObj.string.split("").join("ㅤ"); this.bodyArray[wordIndex].string.split("").forEach((char, charIndex) => { wordArray.push(char); //After eight characters @@ -78,6 +91,24 @@ class chatPostprocessor{ }); } + processLinks(){ + this.rawData.links.forEach((link, linkIndex) => { + this.bodyArray.forEach((wordObj, wordIndex) => { + //Check current wordobj for link (placeholder may contain whitespace with it) + if(wordObj.string.match(`␜${linkIndex}␜`)){ + //Set current word object in the body array to the new link object + this.bodyArray[wordIndex] = { + //Don't want to use a numbered placeholder to make this easier during body injection + //but we also don't want to clobber any surrounding whitespace + string: wordObj.string.replace(`␜${linkIndex}␜`, '␜'), + link: link.link, + type: "link" + } + } + }) + }); + } + handleChatType(){ if(this.rawData.type == "whisper"){ //add whisper class