From 2feea726944aa3b95438777283769b5430443a92 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Fri, 3 Oct 2025 08:58:43 -0400 Subject: [PATCH] Finished up work on advanced formatted private messaging. --- src/app/channel/chat.js | 38 ++++--------------- src/app/chatMetadata.js | 57 +++++++++++++++++++++++++++++ src/app/pm/message.js | 32 +++++----------- src/app/pm/pmHandler.js | 40 +++++++++++++++----- src/server.js | 2 +- www/css/channel.css | 8 ++-- www/css/panel/pm.css | 43 ++++++++++++++++++---- www/css/theme/movie-night.css | 7 ++-- www/js/channel/chatPostprocessor.js | 25 +++++++------ www/js/channel/panels/pmPanel.js | 2 +- www/js/channel/pmHandler.js | 4 +- 11 files changed, 164 insertions(+), 94 deletions(-) create mode 100644 src/app/chatMetadata.js diff --git a/src/app/channel/chat.js b/src/app/channel/chat.js index b26fc17..c9c5853 100644 --- a/src/app/channel/chat.js +++ b/src/app/channel/chat.js @@ -14,49 +14,25 @@ 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 .*/ +//local imports +const chatMetadata = require("../chatMetadata"); + /** * Class representing a single chat message */ -class chat{ +class chat extends chatMetadata{ /** * Instantiates a chat message object * @param {connectedUser} user - User who sent the message - * @param {String} flair - Flair ID String for the flair used to send the message - * @param {Number} highLevel - Number representing current high level - * @param {String} msg - Contents of the message, with links replaced with numbered file-seperator markers - * @param {String} type - Message Type Identifier, used for client-side processing. - * @param {Array} links - Array of URLs/Links included in the message. */ constructor(user, flair, highLevel, msg, type, links){ + //Call derived constructor + super(flair, highLevel, msg, type, links); + /** * User who sent the message */ this.user = user; - - /** - * Flair ID String for the flair used to send the message - */ - this.flair = flair; - - /** - * Number representing current high level - */ - this.highLevel = highLevel; - - /** - * COntents of the message, with links replaced with numbered file-seperator marks - */ - this.msg = msg; - - /** - * Message Type Identifier, used for client-side processing. - */ - this.type = type; - - /** - * Array of URLs/Links included in the message. - */ - this.links = links; } } diff --git a/src/app/chatMetadata.js b/src/app/chatMetadata.js new file mode 100644 index 0000000..df5fbd6 --- /dev/null +++ b/src/app/chatMetadata.js @@ -0,0 +1,57 @@ +/*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 .*/ + +/** + * Class representing a the metadata of a single message + */ +class chatMetadata{ + /** + * Instantiates a chat metadata object + * @param {String} flair - Flair ID String for the flair used to send the message + * @param {Number} highLevel - Number representing current high level + * @param {String} msg - Contents of the message, with links replaced with numbered file-seperator markers + * @param {String} type - Message Type Identifier, used for client-side processing. + * @param {Array} links - Array of URLs/Links included in the message. + */ + constructor(flair, highLevel, msg, type, links){ + /** + * Flair ID String for the flair used to send the message + */ + this.flair = flair; + + /** + * Number representing current high level + */ + this.highLevel = highLevel; + + /** + * COntents of the message, with links replaced with numbered file-seperator marks + */ + this.msg = msg; + + /** + * Message Type Identifier, used for client-side processing. + */ + this.type = type; + + /** + * Array of URLs/Links included in the message. + */ + this.links = links; + } +} + +module.exports = chatMetadata; \ No newline at end of file diff --git a/src/app/pm/message.js b/src/app/pm/message.js index 1ccb88d..28dc46f 100644 --- a/src/app/pm/message.js +++ b/src/app/pm/message.js @@ -14,44 +14,30 @@ 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 .*/ +//localImports +const chatMetadata = require("../chatMetadata"); + /** * Class representing a single chat message */ -class message{ +class message extends chatMetadata{ /** - * Instantiates a chat message object - * @param {String} sender - Name of user who sent the message + * @param {String} user - Name of user who sent the message * @param {Array} recipients - Array of usernames who are supposed to receive the message - * @param {String} msg - Contents of the message, with links replaced with numbered file-seperator markers - * @param {String} type - Message Type Identifier, used for client-side processing. - * @param {Array} links - Array of URLs/Links included in the message. */ - constructor(sender, recipients, msg, type, links){ + constructor(user, recipients, flair, highLevel, msg, type, links){ + //Call derived constructor + super(flair, highLevel, msg, type, links); /** * Name of user who sent the message */ - this.sender = sender; + this.user = user; /** * Array of usernames who are supposed to receive the message */ this.recipients = recipients; - - /** - * Contenst of the messages, with links replaced with numbered file-seperator markers - */ - this.msg = msg; - - /** - * Message Type Identifier, used for client-side processing. - */ - this.type = type; - - /** - * Array of URLs/Links included in the message. - */ - this.links = links; } } diff --git a/src/app/pm/pmHandler.js b/src/app/pm/pmHandler.js index 1414914..b783be8 100644 --- a/src/app/pm/pmHandler.js +++ b/src/app/pm/pmHandler.js @@ -30,13 +30,19 @@ class pmHandler{ /** * Instantiates object containing global server-side private message relay logic * @param {Socket.io} io - Socket.io server instanced passed down from server.js + * @param {channelManager} chanServer - Sister channel management server object */ - constructor(io){ + constructor(io, chanServer){ /** * Socket.io server instance passed down from server.js */ this.io = io; + /** + * Sister channel management server object + */ + this.chanServer = chanServer; + /** * Socket.io server namespace for handling messaging */ @@ -107,14 +113,28 @@ class pmHandler{ //preprocess message const preprocessedMessage = await this.chatPreprocessor.preprocess(socket, data); - //Create message object and relay it off to the recipients - this.relayPMObj(new message( - socket.user.user, - recipients, - preprocessedMessage.message, - preprocessedMessage.chatType, - preprocessedMessage.links - )); + //If the send flag wasnt thrown false + if(preprocessedMessage != false){ + //Pull an active user profile from the first channel that gives it in the chan server + const senderProfile = this.chanServer.activeUsers.get(socket.user.user); + + //If user isn't actively connected to a channel + if(senderProfile == null || senderProfile.length == 0){ + //They don't get to send shit lol + return; + } + + //Create message object and relay it off to the recipients + this.relayPMObj(new message( + socket.user.user, + recipients, + senderProfile[0].flair, + senderProfile[0].highLevel, + preprocessedMessage.message, + preprocessedMessage.chatType, + preprocessedMessage.links + )); + } //If something fucked up }catch(err){ @@ -131,7 +151,7 @@ class pmHandler{ } //Acknowledge the sent message - this.namespace.to(msg.sender).emit("sent", msg); + this.namespace.to(msg.user).emit("sent", msg); } /** diff --git a/src/server.js b/src/server.js index 6005d30..091bbe0 100644 --- a/src/server.js +++ b/src/server.js @@ -197,7 +197,7 @@ scheduler.kickoff(); //Hand over general-namespace socket.io connections to the channel manager module.exports.channelManager = new channelManager(io) -module.exports.pmHandler = new pmHandler(io) +module.exports.pmHandler = new pmHandler(io, module.exports.channelManager); //Listen Function webServer.listen(port, () => { diff --git a/www/css/channel.css b/www/css/channel.css index e7644c4..a5fb710 100644 --- a/www/css/channel.css +++ b/www/css/channel.css @@ -162,18 +162,18 @@ p.panel-head-element{ height: 1.5em; } -.chat-entry{ +.chat-entry, .pm-panel-sesh-entry{ display: flex; align-content: center; font-size: 10pt; } -.chat-entry-username{ +.chat-entry-username, .pm-panel-sesh-entry-username{ margin: auto 0.2em auto 0; text-wrap: nowrap; } -.chat-entry-body{ +.chat-entry-body, .pm-panel-sesh-entry-body{ margin: 0.2em; align-content: center; } @@ -182,7 +182,7 @@ p.panel-head-element{ margin: auto; } -.chat-entry-high-level{ +.chat-entry-high-level, .pm-panel-sesh-entry-high-level{ margin: auto 0 auto 0.2em; } diff --git a/www/css/panel/pm.css b/www/css/panel/pm.css index 2e6663c..54a5e9c 100644 --- a/www/css/panel/pm.css +++ b/www/css/panel/pm.css @@ -17,13 +17,14 @@ along with this program. If not, see .*/ display: flex; flex-direction: horizontal; height: 100%; + overflow: hidden; } #pm-panel-sesh-list-container{ flex: 1; max-width: 10em; - width: calc(100% - 1.25em); margin-left: 0.25em; + overflow-y: auto; } #pm-panel-sesh-container{ @@ -32,6 +33,7 @@ along with this program. If not, see .*/ flex: 1; height: calc(100% - 0.25em); margin-top: 0; + margin-right: 0.25em; } #pm-panel-start-sesh{ @@ -49,6 +51,7 @@ along with this program. If not, see .*/ #pm-panel-sesh-buffer{ flex: 1; + overflow-y: auto; } #pm-panel-sesh-control-div{ @@ -72,12 +75,6 @@ div.pm-panel-sesh-list-entry, div.pm-panel-sesh-list-entry p{ text-align: center; } -#pm-panel-sesh-buffer span{ - display: flex; - flex-direction: row; - margin: 0; -} - .pm-panel-sesh-message-sender, .pm-panel-sesh-message-content{ margin: 0; font-size: 10pt; @@ -89,4 +86,36 @@ div.pm-panel-sesh-list-entry, div.pm-panel-sesh-list-entry p{ justify-content: center; text-align: center; height: 100%; +} + +.pm-panel-sesh-entry{ + display: flex; + align-content: center; + font-size: 10pt; +} + +.pm-panel-sesh-entry-username{ + margin: auto 0.2em auto 0; + text-wrap: nowrap; +} + +.pm-panel-sesh-entry-body{ + margin: 0.2em; + align-content: center; +} + +.pm-panel-sesh-entry-high-level{ + margin: auto 0 auto 0.2em; +} + +.high-level{ + z-index: 2; + background-image: url("/img/sweet_leaf_simple.png"); + background-size: 1.3em; + background-repeat: no-repeat; + background-position-x: center; + background-position-y: top; + width: 1.5em; + text-align: center; + flex-shrink: 0; } \ No newline at end of file diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css index fa3d836..5ce7db6 100644 --- a/www/css/theme/movie-night.css +++ b/www/css/theme/movie-night.css @@ -623,13 +623,12 @@ div.archived p{ border-left: 1px solid var(--accent0); } -#pm-panel-start-sesh{ +#pm-panel-start-sesh, div.pm-panel-sesh-list-entry{ border-bottom: 1px solid var(--accent0); } - -div.pm-panel-sesh-list-entry{ - border-bottom: 1px solid var(--accent0); +span.pm-panel-sesh-entry{ + border-bottom: 1px solid var(--accent1-alt1); } /* altcha theming*/ diff --git a/www/js/channel/chatPostprocessor.js b/www/js/channel/chatPostprocessor.js index 80d29a7..e6daf7b 100644 --- a/www/js/channel/chatPostprocessor.js +++ b/www/js/channel/chatPostprocessor.js @@ -35,14 +35,13 @@ class chatPostprocessor{ * @param {Object} rawData - Raw data from server * @returns {Node} Post-Processed Chat Entry */ - postprocess(rawData){ + postprocess(rawData, pm = false){ //Create empty array to hold filter spans this.filterSpans = []; //Set raw message data this.rawData = rawData; //Set current chat nodes - this.buildEntry(); - this.chatBody = this.chatEntry.querySelector(".chat-entry-body"); + this.buildEntry(pm); //Split the chat message into an array of objects representing each word/chunk this.splitMessage(); @@ -87,14 +86,16 @@ class chatPostprocessor{ return this.chatEntry; } - buildEntry(){ + buildEntry(pm){ + const classSuffix = pm ? 'pm-panel-sesh' : 'chat'; + const classSuffixAlt = pm ? classSuffix : 'chat-panel'; //Create chat-entry span this.chatEntry = document.createElement('span'); - this.chatEntry.classList.add("chat-panel-buffer","chat-entry",`chat-entry-${this.rawData.user}`); + this.chatEntry.classList.add(`${classSuffixAlt}-buffer`,`${classSuffix}-entry`,`${classSuffix}-entry-${this.rawData.user}`); //Create high-level label var highLevel = document.createElement('p'); - highLevel.classList.add("chat-panel-buffer","chat-entry-high-level","high-level"); + highLevel.classList.add(`${classSuffixAlt}-buffer`,`${classSuffix}-entry-high-level`,"high-level"); highLevel.textContent = utils.unescapeEntities(`${this.rawData.highLevel}`); this.chatEntry.appendChild(highLevel); @@ -110,11 +111,11 @@ class chatPostprocessor{ //Create username label var userLabel = document.createElement('p'); - userLabel.classList.add("chat-panel-buffer", "chat-entry-username", ); + userLabel.classList.add(`${classSuffixAlt}-buffer`, `${classSuffix}-entry-username`, ); //Create color span var flairSpan = document.createElement('span'); - flairSpan.classList.add("chat-entry-flair-span", flair); + flairSpan.classList.add(`${classSuffix}-entry-flair-span`, flair); flairSpan.innerHTML = this.rawData.user; //Inject flair span into user label before the colon @@ -124,9 +125,11 @@ class chatPostprocessor{ this.chatEntry.appendChild(userLabel); //Create chat body - var chatBody = document.createElement('p'); - chatBody.classList.add("chat-panel-buffer","chat-entry-body"); - this.chatEntry.appendChild(chatBody); + this.chatBody = document.createElement('p'); + this.chatBody.classList.add(`${classSuffixAlt}-buffer`,`${classSuffix}-entry-body`); + + //Append chat body to chat entry + this.chatEntry.appendChild(this.chatBody); } /** diff --git a/www/js/channel/panels/pmPanel.js b/www/js/channel/panels/pmPanel.js index 13d5349..4af334f 100644 --- a/www/js/channel/panels/pmPanel.js +++ b/www/js/channel/panels/pmPanel.js @@ -229,7 +229,7 @@ class pmPanel extends panelObj{ */ renderMessage(message){ //Run postprocessing functions on chat message - const postprocessedMessage = client.chatBox.chatPostprocessor.postprocess(message); + const postprocessedMessage = client.chatBox.chatPostprocessor.postprocess(message, true); //Append message to buffer this.seshBuffer.appendChild(postprocessedMessage); diff --git a/www/js/channel/pmHandler.js b/www/js/channel/pmHandler.js index c3562f5..ac5417b 100644 --- a/www/js/channel/pmHandler.js +++ b/www/js/channel/pmHandler.js @@ -112,9 +112,9 @@ class pmHandler{ } //If this wasn't our message - if(message.sender != client.user.user){ + if(message.user != client.user.user){ //Push sender onto members list - recipients.push(message.sender); + recipients.push(message.user); } //Sort recipients