canopy/www/js/channel/pmHandler.js

258 lines
7.7 KiB
JavaScript

/*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 <https://www.gnu.org/licenses/>.*/
/**
* 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-inverse');
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[1].unread){
//LOOK OUT BOYS, THIS ONE'S BEEN READ! CHEESE IT!
return;
}
}
//Unlight the icon
this.pmIcon.classList.remove('positive-inverse');
}
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];
}
}