/*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 for handling incoming Private Messages for the entire client */ class pmHandler{ /** * Instantiates a new Private Message Handler object * @param {channel} client - Parent client Management Object */ constructor(client){ /** * Parent client management object */ this.client = client; /** * PM Icon in the main chat bar */ this.pmIcon = document.querySelector('#chat-panel-pm-icon'); /** * List of PM Sessions */ this.seshList = new Map(); /** * List of open PM Panels */ this.panelList = new Map(); /** * PM RX Sound */ this.rxSound = '/nonfree/imrecv.ogg'; /** * Open Sesh Sound */ this.openSeshSound = '/nonfree/opensesh.ogg'; /** * End Sesh Sound */ this.endSeshSound = '/nonfree/closesesh.ogg'; this.defineListeners(); this.setupInput(); } /** * Defines network related event listeners for PM Handler */ defineListeners(){ this.client.pmSocket.on("message", this.handlePM.bind(this)); this.client.pmSocket.on("sent", this.handlePM.bind(this)); } /** * Defines inpet related event listeners for PM handler */ setupInput(){ this.pmIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new pmPanel(client))}); } /** * Handles received Private Messages from the PM service on the server, organizing it into the proper session from the sesh list * Or creating a new sesh where a matching one does not a exist * @param {object} data - Private Message data from the server */ handlePM(data){ //Store whether or not current message has been consumed by an existing sesh let consumed = false; //Store whether or not we have this sesh open in an existing PMPanel let displayed = false; //Pull session name from static generation method const nameObj = pmHandler.genSeshName(data); //For each existing sesh for(const seshEntry of this.seshList){ //Pull sesh object from map entry const sesh = seshEntry[1]; //If currently checked sesh ID matches calculated message sesh id if(sesh.id == nameObj.name){ //Dump collected message into the matching session sesh.messages.push(data); //Set sesh to unread sesh.unread = true; //For each open PM Panel for(const panel of this.panelList){ //If sesh ID matches an open sesh in an existing panel if(sesh.id == panel[1]){ //Set sesh to read sesh.unread = false; } } //If the message was unread if(sesh.unread){ //Notify user of new message/sesh this.handlePing(); } //Add sesh to sesh map this.seshList.set(sesh.id, sesh); //Let the rest of the method know that we've consumed this message consumed = true; } } //If we made it through the loop without consuming the message if(!consumed){ //Generate a new sesh const sesh = new pmSesh(data, client); //Notify user of new message/sesh this.handlePing((data.msg == '' || data.msg == null)); //Add it to the sesh list this.seshList.set(sesh.id, sesh); } //If this isn't an empty message (sesh-starter), and PM's always make noise, and we didn't send the message if(data.msg != '' && data.msg != null && localStorage.getItem('rxPMSound') == 'all' && data.user != this.client.user.user){ //make sum noize! utils.ux.playSound(this.rxSound); } } handlePing(newSesh = false){ //Light up the icon this.pmIcon.classList.add('positive-low'); if(newSesh && (localStorage.getItem('newSeshSound') == 'true')){ utils.ux.playSound(this.openSeshSound); }else if(localStorage.getItem('rxPMSound') == 'unread'){ utils.ux.playSound(this.rxSound); } } //Handles UI updates after reading all messages checkAllRead(){ //For each sesh for(const sesh of this.seshList){ //If a sesh is unread if(sesh.unread){ //LOOK OUT BOYS, THIS ONE'S BEEN READ! CHEESE IT! return; } } //Unlight the icon this.pmIcon.classList.remove('positive-low'); } readSesh(panelID, seshID){ //Set current sesh for panel this.panelList.set(panelID, seshID); //Get requested session const sesh = this.seshList.get(seshID); //Set it to unread sesh.unread = false; //Commit sesh back to sesh list this.seshList.set(sesh.id, sesh); //Check if all messages are read and handle em' if they are this.checkAllRead(); } static genSeshName(message){ const recipients = []; //Manually iterate through recipients for(const member of message.recipients){ //check to make sure we're not adding ourselves if(member != client.user.user){ //Copy relevant array members by value instead of reference recipients.push(member); } } //If this wasn't our message if(message.user != client.user.user){ //Push sender onto members list recipients.push(message.user); } //Sort recipients recipients.sort(); return { name: recipients.join(', '), recipients } } } /** * Class which represents an existing Private Messaging session between two or more users */ class pmSesh{ /** * Instatiates a new pmSession object * @param {Object} message - Initial Private Message object from server that initiated the session */ constructor(message, client){ /** * Parent client management object */ this.client = client; const nameObj = pmHandler.genSeshName(message); /** * Members of session excluding the currently logged in user */ this.recipients = nameObj.recipients /** * Name of the chat sesh, named after out-going recipients */ this.id = nameObj.name; /** * Whether or not we have unread messages within the session */ this.unread = true; /** * Array containing all session messages */ this.messages = (message.msg == "" || message.msg == null) ? [] : [message]; } }