/*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 .*/ //config imports const config = require('../../../config.json'); //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'); const webRTCHandler = require('./media/webRTCHandler'); /** * 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 WebRTC Handler Object */ this.webRTCHandler = new webRTCHandler(server, this); /** * Child Chat Buffer Object */ this.chatBuffer = new chatBuffer(server, chanDB, this); this.userAnnouncements = { join: [ "%UNAME% joined.", "%UNAME% arrived.", "%UNAME% appeared.", "%UNAME% hopped in.", "%UNAME% checked in.", "%UNAME% checked in to see what condition their condition was in.", "%UNAME% logged in.", "%UNAME% turned on, tuned in, and dropped out.", `%UNAME% is now using ${config.instanceName}.`, `%UNAME% is now watching #${this.name}.`, "%UNAME% tuned in.", "%UNAME% is ready to sparkem.", "%UNAME% connected.", "%UNAME% joins the battle.", "%UNAME% hopped on.", "%UNAME% logged on.", `${config.instanceName}, population: %UNAME%`, "Welcome, %UNAME%.", "Salutations, %UNAME%.", "Hello, %UNAME%.", "Greetings, %UNAME%.", "Sup, %UNAME%.", "I AM THE GOD OF HELLFIRE, AND I BRING YOU: %UNAME%!", "A wild %UNAME% has appeared!" ], leave: [ "%UNAME% left.", "%UNAME% dropped out.", "%UNAME% checked out.", "%UNAME% quit.", "%UNAME% is no longer with us.", `%UNAME% is no longer using ${config.instanceName}.`, "%UNAME% dipped.", "%UNAME% booked it.", "%UNAME% cheesed it.", "%UNAME% vanished.", "%UNAME% said dueces.", "%UNAME% has left the building.", "%UNAME% bounced.", "%UNAME% is beyond the horizon.", "%UNAME% has drifted into space.", "%UNAME% is outskies.", "%UNAME% went to the shadow realm.", "Goodbye, %UNAME%.", "Dueces, %UNAME%.", "Bye, %UNAME%.", "Farewell, %UNAME%.", "l8r %UNAME%.", "That'll do, %UNAME%." ] } } /** * 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); this.webRTCHandler.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); //Broadcast the un-welcome wagon this.welcomeWagon(socket.user.user, true); } //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); } /** * Basic function to welcome/send off connecting/disconnecting users * @param {string} user - user to welcome * @param {boolean} leaving - true if user is leaving instead of joining */ async welcomeWagon(user, leaving = false){ //Grab the applicable list depending on if the user is leaving or not const activeList = leaving ? this.userAnnouncements.leave : this.userAnnouncements.join; //Randomly yoink the applicable message const msg = activeList[Math.floor(Math.random() * activeList.length)]; this.server.chatHandler.relayChat(user, "", 0, msg.replace("%UNAME%", user), "serverwhisper", this.name, []); } } module.exports = activeChannel;