Server now seperates links and media. Client now embeds links, but not media.

This commit is contained in:
rainbow napkin 2024-12-16 09:01:41 -05:00
parent a83dbdcb7f
commit 23df4f88f9
4 changed files with 141 additions and 58 deletions

View file

@ -79,45 +79,45 @@ module.exports = class{
} }
} }
relayUserChat(socket, msg, type){ relayUserChat(socket, msg, type, links){
const user = this.server.getSocketInfo(socket); 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){ relayChat(user, flair, highLevel, msg, type = 'chat', chan, links){
this.server.io.in(chan).emit("chatMessage", {user, flair, highLevel, msg, type}); this.server.io.in(chan).emit("chatMessage", {user, flair, highLevel, msg, type, links});
} }
relayServerWisper(socket, user, flair, highLevel, msg, type){ relayServerWisper(socket, user, flair, highLevel, msg, type, links){
socket.emit("chatMessage", {user, flair, highLevel, msg, type}); socket.emit("chatMessage", {user, flair, highLevel, msg, type, links});
} }
relayGlobalChat(user, flair, highLevel, msg, type = 'chat'){ relayGlobalChat(user, flair, highLevel, msg, type = 'chat', links){
this.server.io.emit("chatMessage", {user, flair, highLevel, msg, type}); this.server.io.emit("chatMessage", {user, flair, highLevel, msg, type, links});
} }
relayTokeCallout(msg){ relayTokeCallout(msg, links){
this.relayGlobalChat("Tokebot", "", '∞', msg, "toke"); this.relayGlobalChat("Tokebot", "", '∞', msg, "toke", links);
} }
relayTokeWhisper(socket, msg){ relayTokeWhisper(socket, msg, links){
this.relayServerWisper(socket, "Tokebot", "", '∞', msg, "tokewhisper"); this.relayServerWisper(socket, "Tokebot", "", '∞', msg, "tokewhisper", links);
} }
relayGlobalTokeWhisper(msg){ relayGlobalTokeWhisper(msg, links){
this.relayGlobalChat("Tokebot", "", '∞', msg, "tokewhisper"); this.relayGlobalChat("Tokebot", "", '∞', msg, "tokewhisper", links);
} }
relayServerAnnouncement(msg){ relayServerAnnouncement(msg, links){
this.relayGlobalChat("Server", "", '∞', msg, "announcement"); this.relayGlobalChat("Server", "", '∞', msg, "announcement", links);
} }
relayChannelAnnouncement(chan, msg){ relayChannelAnnouncement(chan, msg, links){
const activeChan = this.server.activeChannels.get(chan); const activeChan = this.server.activeChannels.get(chan);
//If channel isn't null //If channel isn't null
if(activeChan != null){ if(activeChan != null){
this.relayChat("Channel", "", '∞', msg, "announcement", chan); this.relayChat("Channel", "", '∞', msg, "announcement", chan, links);
} }
} }

View file

@ -35,6 +35,7 @@ module.exports = class commandPreprocessor{
this.socket = socket; this.socket = socket;
this.sendFlag = true; this.sendFlag = true;
this.rawData = data; this.rawData = data;
this.chatType = 'chat';
//If we don't pass sanatization/validation turn this car around //If we don't pass sanatization/validation turn this car around
if(!this.sanatizeCommand()){ if(!this.sanatizeCommand()){
@ -45,9 +46,19 @@ module.exports = class commandPreprocessor{
this.splitCommand(); this.splitCommand();
//Process the command //Process the command
await this.processServerCommand(); await this.processServerCommand();
//Send it as chat (assuming the flag isn't false)
//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(); this.sendChat();
} }
}
sanatizeCommand(){ sanatizeCommand(){
//Trim and Sanatize for XSS //Trim and Sanatize for XSS
@ -66,34 +77,71 @@ module.exports = class commandPreprocessor{
async processServerCommand(){ async processServerCommand(){
//If the raw message starts with '!' (skip commands that start with whitespace so people can send example commands in chat) //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] == '!'){ 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 it isn't just an exclimation point, and we have a real command
if(this.argumentArray != null){ if(this.argumentArray != null){
if(this.commandProcessor[this.argumentArray[0].toLowerCase()] != 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) //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{ }else{
//Process as toke command if we didnt get a match from the standard server-side command processor //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);
} }
} }
} }
} }
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(){ sendChat(){
//If the send flag is true
if(this.sendFlag){
//FUCKIN' SEND IT! //FUCKIN' SEND IT!
this.chatHandler.relayUserChat(this.socket, this.commandArray.join('').trimStart()); this.chatHandler.relayUserChat(this.socket, this.message, this.chatType, this.links);
}
} }
} }
@ -104,27 +152,27 @@ class commandProcessor{
} }
//Command keywords get run through .toLowerCase(), so we should use lowercase method names for command methods //Command keywords get run through .toLowerCase(), so we should use lowercase method names for command methods
whisper(commandObj){ whisper(preprocessor){
//splice out our whisper //splice out our whisper
commandObj.commandArray.splice(0,2); preprocessor.commandArray.splice(0,2);
//send it //Mark out the current message as a whisper
this.chatHandler.relayUserChat(commandObj.socket, commandObj.commandArray.join(''), 'whisper'); preprocessor.chatType = 'whisper';
//Make sure to throw the send flag //Make sure to throw the send flag
return false; return true
} }
async announce(commandObj){ async announce(preprocessor){
//Get the current channel from the database //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) //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 //splice out our whisper
commandObj.commandArray.splice(0,2); preprocessor.commandArray.splice(0,2);
//send it //send it
this.chatHandler.relayChannelAnnouncement(commandObj.socket.chan, commandObj.commandArray.join('')); this.chatHandler.relayChannelAnnouncement(preprocessor.socket.chan, preprocessor.commandArray.join(''));
//throw send flag //throw send flag
return false; return false;
} }
@ -133,13 +181,13 @@ class commandProcessor{
return true; return true;
} }
async serverannounce(commandObj){ async serverannounce(preprocessor){
//Check if the user has permission, and publicly shame them if they don't (lmao) //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 //splice out our whisper
commandObj.commandArray.splice(0,2); preprocessor.commandArray.splice(0,2);
//send it //send it
this.chatHandler.relayServerAnnouncement(commandObj.commandArray.join('')); this.chatHandler.relayServerAnnouncement(preprocessor.commandArray.join(''));
//throw send flag //throw send flag
return false; return false;
} }
@ -148,14 +196,14 @@ class commandProcessor{
return true; return true;
} }
async clear(commandObj){ async clear(preprocessor){
//Get the current channel from the database //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) //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 //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 //throw send flag
return false; return false;
} }

View file

@ -295,6 +295,10 @@ select.panel-head-element{
color: var(--accent1); color: var(--accent1);
} }
.chat-link{
color: var(--focus0);
}
/* popup */ /* popup */
.popup-backer{ .popup-backer{

View file

@ -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 //We could pass this through arguments but these functions wont be very interoperable anyways since they expect a purpose-made hashtable
this.splitBody(); this.splitBody();
//Inject links into un-processed placeholders
this.processLinks();
//Inject whitespace into un-processed words //Inject whitespace into un-processed words
this.addWhitespace(); this.addWhitespace();
@ -26,8 +29,8 @@ class chatPostprocessor{
splitBody(){ splitBody(){
//Create an empty array to hold the body //Create an empty array to hold the body
this.bodyArray = []; this.bodyArray = [];
//Split string by words //Split string by words (except for file seperator to keep link placeholders in-tact)
const splitString = this.chatBody.innerHTML.split(/\b/g); //Group words together const splitString = this.chatBody.innerHTML.split(/(?<!␜)\b(?!␜)/g); //Group words together
//for each word in the splitstring //for each word in the splitstring
splitString.forEach((string) => { splitString.forEach((string) => {
@ -48,7 +51,18 @@ class chatPostprocessor{
//Extract strings into the empty array //Extract strings into the empty array
this.bodyArray.forEach((wordObj) => { this.bodyArray.forEach((wordObj) => {
if(wordObj.type == 'word'){
stringArray.push(wordObj.string); 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 //Join the strings to fill the chat entry
@ -62,7 +76,6 @@ class chatPostprocessor{
if(wordObj.type == "word"){ if(wordObj.type == "word"){
var wordArray = []; var wordArray = [];
//Add invisible whitespace in-between characters to keep it from breaking page layout //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) => { this.bodyArray[wordIndex].string.split("").forEach((char, charIndex) => {
wordArray.push(char); wordArray.push(char);
//After eight characters //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(){ handleChatType(){
if(this.rawData.type == "whisper"){ if(this.rawData.type == "whisper"){
//add whisper class //add whisper class