/*Canopy - The next generation of stoner streaming software Copyright (C) 2024-2026 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 .*/ //local imports const connectedUser = require('./connectedUser'); const chatBuffer = require('./chatBuffer'); const queue = require('./media/queue'); const channelModel = require('../../schemas/channel/channelSchema'); const playlistHandler = require('./media/playlistHandler') /** * Class representing a single active channel */ class activeChannel{ /** * Instantiates an activeChannel object * @param {channelManager} server - Parent Server Object * @param {Mongoose.Document} chanDB - chanDB to rehydrate buffer from */ constructor(server, chanDB){ /** * Parent Server Object */ this.server = server; /** * Current Channel Name */ this.name = chanDB.name; /** * List of channel-wide toke commands */ this.tokeCommands = chanDB.tokeCommands; /** * Internal Database Identifier for the channel */ this._id = chanDB._id; /** * List of connected users */ this.userList = new Map(); /** * Child Queue Object */ this.queue = new queue(server, chanDB, this); /** * Child Playlist Handler Object */ this.playlistHandler = new playlistHandler(server, this); /** * Child Chat Buffer Object */ this.chatBuffer = new chatBuffer(server, chanDB, this); } /** * Handles server-side initialization for new connections to the channel * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access * @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access * @param {Socket} socket - Requesting Socket * @returns {activeUser} active user object generated by the new connection */ async handleConnection(userDB, chanDB, socket){ //get current user object from the userlist var userObj = this.userList.get(userDB.user); //get channel rank for current user const chanRank = await chanDB.getChannelRankByUserDoc(userDB); //If user is already connected if(userObj){ //Add this socket on to the userobject userObj.sockets.push(socket.id); //If the user is joining the channel }else{ //Grab flair await userDB.populate('flair'); //Set user object userObj = new connectedUser(userDB, chanRank, this, socket); } //Set user entry in userlist this.userList.set(userDB.user, userObj); //if everything looks good, admit the connection to the channel socket.join(socket.chan); //Define per-channel event listeners this.queue.defineListeners(socket); this.playlistHandler.defineListeners(socket); //Hand off the connection initiation to it's user object const activeUser = await userObj.handleConnection(userDB, chanDB, socket) //Send out the userlist this.broadcastUserList(socket.chan); //Return active user connection object for use by the channelManager object return activeUser; } /** * Handles server-side initialization for disconnecting from the channel * @param {Socket} socket - Requesting Socket */ handleDisconnect(socket){ //temporarily store userObj let userObj = this.userList.get(socket.user.user); //If we have more than one active connection if(userObj.sockets.length > 1){ //Filter out disconnecting socket from socket list, and set as current socket list for user userObj.sockets = userObj.sockets.filter((id) => { return id != socket.id; }); //Update the userlist this.userList.set(socket.user.user, userObj); //If this is the last one }else{ //Tell the server to handle the disconnection of this user object this.server.handleUserDisconnect(userObj); //If this is the last connection for this user, remove them from the userlist this.userList.delete(socket.user.user); } //and send out the filtered list this.broadcastUserList(socket.chan); } /** * Broadcasts user list to all users */ broadcastUserList(){ //Create a userlist object with the tokebot user pre-loaded var userList = [{ user: "Tokebot", flair: "classic", highLevel: "∞", }]; this.userList.forEach((userObj, user) => { userList.push({ user: user, flair: userObj.flair, highLevel: userObj.highLevel }); }); this.server.io.in(this.name).emit("userList", userList); } /** * Broadcasts channel emote list to connected users * @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access */ async broadcastChanEmotes(chanDB){ //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.name}); } //Get emote list from channel document const emoteList = chanDB.getEmotes(); //Broadcast that sumbitch this.server.io.in(this.name).emit('chanEmotes', emoteList); } } module.exports = activeChannel;