/*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 .*/ //NPM imports const validator = require('validator'); //Local imports const queuedMedia = require('./queuedMedia'); const loggerUtils = require('../../../utils/loggerUtils'); const yanker = require('../../../utils/media/yanker'); const channelModel = require('../../../schemas/channel/channelSchema'); const { userModel } = require('../../../schemas/user/userSchema'); /** * Class containing playlist management logic for a single channel */ module.exports = class{ /** * Instantiates a new object to handle playlist management for a single channel * @param {channelManager} server - Parent server object * @param {activeChannel} channel - Parent Channel object for desired channel queue */ constructor(server, channel){ //Set server this.server = server //Set channel this.channel = channel; } /** * Defines server-side socket.io listeners for newly connected sockets * @param {Socket} socket - Newly connected socket to define listeners against */ defineListeners(socket){ //Channel Playlist Listeners socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)}); socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)}); socket.on("deleteChannelPlaylist", (data) => {this.deleteChannelPlaylist(socket, data)}); socket.on("addToChannelPlaylist", (data) => {this.addToChannelPlaylist(socket, data)}); socket.on("queueChannelPlaylist", (data) => {this.queueChannelPlaylist(socket, data)}); socket.on("queueFromChannelPlaylist", (data) => {this.queueFromChannelPlaylist(socket, data)}); socket.on("queueRandomFromChannelPlaylist", (data) => {this.queueRandomFromChannelPlaylist(socket, data)}); socket.on("renameChannelPlaylist", (data) => {this.renameChannelPlaylist(socket, data)}); socket.on("changeDefaultTitlesChannelPlaylist", (data) => {this.changeDefaultTitlesChannelPlaylist(socket, data)}); socket.on("deleteChannelPlaylistMedia", (data) => {this.deleteChannelPlaylistMedia(socket, data)}); //User Playlist Listeners socket.on("getUserPlaylists", () => {this.getUserPlaylists(socket)}); socket.on("createUserPlaylist", (data) => {this.createUserPlaylist(socket, data)}); socket.on("deleteUserPlaylist", (data) => {this.deleteUserPlaylist(socket, data)}); socket.on("addToUserPlaylist", (data) => {this.addToUserPlaylist(socket, data)}); socket.on("queueUserPlaylist", (data) => {this.queueUserPlaylist(socket, data)}); socket.on("queueFromUserPlaylist", (data) => {this.queueFromUserPlaylist(socket, data)}); socket.on("queueRandomFromUserPlaylist", (data) => {this.queueRandomFromUserPlaylist(socket, data)}); socket.on("renameUserPlaylist", (data) => {this.renameUserPlaylist(socket, data)}); socket.on("changeDefaultTitlesUserPlaylist", (data) => {this.changeDefaultTitlesUserPlaylist(socket, data)}); socket.on("deleteUserPlaylistMedia", (data) => {this.deleteUserPlaylistMedia(socket, data)}); } //Validation/Sanatization functions /** * Validates client requests to create a playlist * @param {Socket} socket - Newly connected socket to define listeners against * @param {Object} data - Data handed over from the client * @returns {Object} returns validated titles */ createPlaylistValidator(socket, data){ //Create empty array to hold titles const safeTitles = []; //If the title is too long if(typeof data.playlist != 'string' || !validator.isLength(data.playlist, {min: 1, max:30})){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Invalid Playlist Name!", "validation"); //and ignore it! return; } if(data.defaultTitles != null){ //For each default title passed by the data for(let title of data.defaultTitles){ //If the title isn't too long if(typeof title != 'string' || validator.isLength(title, {min:1, max:30})){ //Add it to the safe title list safeTitles.push(validator.escape(validator.trim(title))) } } } //Escape/trim the playlist name return { playlist: validator.escape(validator.trim(data.playlist)), defaultTitles: safeTitles } } /** * Validates client requests to add media to a playlist * @param {Socket} socket - Newly connected socket to define listeners against * @param {String} URL - URL String handed over from the client * @returns {Array} List of media objects which where added */ async addToPlaylistValidator(socket, url){ //If we where given a bad URL if(typeof url != 'string' || !validator.isURL(url)){ //Attempt to fix the situation by encoding it url = encodeURI(url); //If it's still bad if(typeof url != 'string' || !validator.isURL(url)){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Bad URL!", "validation"); //and ignore it! return; } } //Pull media metadata const mediaList = await yanker.yankMedia(url); //If we didn't get any media if(mediaList.length == 0 || mediaList == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "No media found!", "queue"); //and ignore it! return; } return mediaList; } /** * Validates client requests to queue media from a playlist * @param {Socket} socket - Newly connected socket to define listeners against * @param {Object} data - Data handed over from the client * @returns {Number} returns validated start time on success */ queueFromChannelPlaylistValidator(socket, data){ //Validate UUID if(typeof data.uuid != 'string' || !validator.isUUID(data.uuid)){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, `'${data.uuid}' is not a valid UUID!`, "validation"); //and ignore it! return; } //The UUID is only validated, not processed so we just return the new time :P return this.channel.queue.getStart(data.start) } /** * Validates client requests to rename the playlist validator * @param {Socket} socket - Newly connected socket to define listeners against * @param {Object} data - Data handed over from the client * @returns {String} returns escaped/trimmed name upon success */ renameChannelPlaylistValidator(socket, data){ //If the title is too long if(typeof data.name != 'string' || !validator.isLength(data.name, {min: 1, max:30})){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Invalid playlist name!", "validation"); //and ignore it! return; } //Escape/trim the playlist name return validator.escape(validator.trim(data.name)); } /** * Validates client requests to change default titles for a given playlist * @param {Object} data - Data handed over from the client * @returns */ changeDefaultTitlesValidator(data){ //Create empty array to hold titles const safeTitles = []; //For each default title passed by the data for(let title of data.defaultTitles){ //If the title isn't too long or too short if(typeof title != 'string' || validator.isLength(title, {min: 1, max:30})){ //Add it to the safe title list safeTitles.push(validator.escape(validator.trim(title))) } } //return safe titles return safeTitles; } /** * Validates client requests to rename the playlist validator * @param {Socket} socket - Newly connected socket to define listeners against * @param {Object} data - Data handed over from the client */ deletePlaylistMediaValidator(socket, data){ //If we don't have a valid UUID if(typeof data.uuid != 'string' || !validator.isUUID(data.uuid)){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, `'${data.uuid}' is not a valid UUID!`, "validation"); //and ignore it! return; } return data.uuid; } //Get playlist functions /** * Sends channel playlist data to a requesting socket * @param {Socket} socket - Newly connected socket to define listeners against * @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access */ async getChannelPlaylists(socket, chanDB){ try{ //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } //Return playlists socket.emit('chanPlaylists', chanDB.getPlaylists()); }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Sends user playlist data to a requesting socket * @param {Socket} socket - Newly connected socket to define listeners against * @param {Mongoose.Document} userDB - Channnel Document Passthrough to save on DB Access */ async getUserPlaylists(socket, userDB){ try{ //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //Return playlists socket.emit('userPlaylists', userDB.getPlaylists()); }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } //Create playlist functions /** * Creates a new channel playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access */ async createChannelPlaylist(socket, data, chanDB){ try{ //Validate Data const validData = this.createPlaylistValidator(socket, data); //If we got bad data if(validData == null){ //Do nothing return; } //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ //If the channel already exists if(chanDB.getPlaylistByName(validData.playlist) != null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, `Playlist named '${validData.playlist}' already exists!`, "validation"); //and ignore it! return; } //Add playlist to the channel doc chanDB.media.playlists.push({ name: validData.playlist, defaultTitles: validData.defaultTitles }); //Save the channel doc await chanDB.save(); //Return playlists from channel doc this.getChannelPlaylists(socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Creates a new user playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access */ async createUserPlaylist(socket, data, userDB){ try{ //Validate Data const validData = this.createPlaylistValidator(socket, data); //If we got bad data if(validData == null){ //Do nothing return; } //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //If the channel already exists if(userDB.getPlaylistByName(validData.playlist) != null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, `Playlist named '${validData.playlist}' already exists!`, "validation"); //and ignore it! return; } //Add playlist to the channel doc userDB.playlists.push({ name: validData.playlist, defaultTitles: validData.defaultTitles }); //Save the channel doc await userDB.save(); //Return playlists from channel doc this.getUserPlaylists(socket, userDB); }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } //Delete playlist functions /** * Deletes a user playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access */ async deleteChannelPlaylist(socket, data, chanDB){ try{ //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } //If the channel doesn't exist if(chanDB.getPlaylistByName(data.playlist) == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, `Playlist named '${data.playlist}' doesn't exist!`, "validation"); //and ignore it! return; } if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ //Delete playlist name await chanDB.deletePlaylistByName(data.playlist); //Return playlists from channel doc this.getChannelPlaylists(socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Deletes a Channel playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access */ async deleteUserPlaylist(socket, data, userDB){ try{ //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //If the channel doesn't exist if(userDB.getPlaylistByName(data.playlist) == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, `Playlist named '${data.playlist}' doesn't exist!`, "validation"); //and ignore it! return; } //Delete playlist name await userDB.deletePlaylistByName(data.playlist); //Return playlists from channel doc this.getUserPlaylists(socket, userDB); }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } //Add Media Functions /** * Adds media to channel playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access */ async addToChannelPlaylist(socket, data, chanDB){ try{ //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ //Normally I put validators before the DB call //But this reqs a server-side fetch, so I'll do the perm check first :P //Validate URL and pull media const mediaList = await this.addToPlaylistValidator(socket, data.url); //If we encountered an error during validation if(mediaList == null){ //Fuck off and die return; } //Find the playlist const playlist = chanDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //delete media from playlist chanDB.media.playlists[playlist.listIndex].addMedia(mediaList); //save the channel document await chanDB.save(); //Return playlists from channel doc this.getChannelPlaylists(socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Adds media to user playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access */ async addToUserPlaylist(socket, data, userDB){ try{ //Validate URL and pull media const mediaList = await this.addToPlaylistValidator(socket, data.url); //If we encountered an error during validation if(mediaList == null){ //Fuck off and die return; } //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //Find the playlist const playlist = userDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //delete media from playlist userDB.playlists[playlist.listIndex].addMedia(mediaList); //save the channel document await userDB.save(); //Return playlists from channel doc this.getUserPlaylists(socket, userDB); }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } //Queuing Functions /** * Queues an entire channel playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access */ async queueChannelPlaylist(socket, data, chanDB){ try{ //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } //Permcheck to make sure the user can fuck w/ the queue if((!this.channel.queue.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin')){ //Pull a valid start time from input, or make one up if we can't let start = this.channel.queue.getStart(data.start); //Grab playlist from the DB let playlist = chanDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Create an empty array to hold our media list const mediaList = []; //Iterate through playlist media for(let item of playlist.media){ //Rehydrate a full phat media object from the flat DB entry let mediaObj = item.rehydrate(); //Set media title from default titles mediaObj.title = playlist.pickDefaultTitle(mediaObj.title); //Push rehydrated item on to the mediaList mediaList.push(mediaObj); } //Convert array of standard media objects to queued media objects, and push to schedule this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray(mediaList, start), socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Queues an entire user playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access * @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access */ async queueUserPlaylist(socket, data, userDB, chanDB){ try{ //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } //Permcheck to make sure the user can fuck w/ the queue if((!this.channel.queue.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin')){ //Pull a valid start time from input, or make one up if we can't let start = this.channel.queue.getStart(data.start); //Grab playlist from the DB let playlist = userDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Create an empty array to hold our media list const mediaList = []; //Iterate through playlist media for(let item of playlist.media){ //Rehydrate a full phat media object from the flat DB entry let mediaObj = item.rehydrate(); //Set media title from default titles mediaObj.title = playlist.pickDefaultTitle(mediaObj.title); //Push rehydrated item on to the mediaList mediaList.push(mediaObj); } //Convert array of standard media objects to queued media objects, and push to schedule this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray(mediaList, start), socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Queues media from a given channel playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access */ async queueFromChannelPlaylist(socket, data, chanDB){ try{ //Validate data const start = this.queueFromChannelPlaylistValidator(socket, data); //If we had a validation issue if(start == null){ //Fuck off and die return; } //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } //Permcheck the user if((!this.channel.queue.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin')){ //Grab playlist const playlist = chanDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Pull and rehydrate media from playlist const media = playlist.findMediaByUUID(data.uuid).rehydrate(); //Set title from default titles media.title = playlist.pickDefaultTitle(media.title); //Queue found media this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray([media], start), socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Queues media from a given user playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access * @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access */ async queueFromUserPlaylist(socket, data, userDB, chanDB){ try{ //Validate data const start = this.queueFromChannelPlaylistValidator(socket, data); //If we had a validation issue if(start == null){ //Fuck off and die return; } //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } //Permcheck the user if((!this.channel.queue.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin')){ //Grab playlist const playlist = userDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Pull and rehydrate media from playlist const media = playlist.findMediaByUUID(data.uuid).rehydrate(); //Set title from default titles media.title = playlist.pickDefaultTitle(media.title); //Queue found media this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray([media], start), socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Queues random media from a given channel playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access */ async queueRandomFromChannelPlaylist(socket, data, chanDB){ try{ //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } //Permcheck the user if((!this.channel.queue.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin')){ //Pull a valid start time from input, or make one up if we can't let start = this.channel.queue.getStart(data.start); //Grab playlist const playlist = chanDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Pick a random video ID based on the playlist's length const foundID = Math.round(Math.random() * (playlist.media.length - 1)); //Pull and rehydrate media from playlist const foundMedia = playlist.media[foundID].rehydrate(); //Set title from default titles foundMedia.title = playlist.pickDefaultTitle(foundMedia.title); //Queue found media this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray([foundMedia], start), socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Queues random media from a given user playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access * @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access */ async queueRandomFromUserPlaylist(socket, data, userDB, chanDB){ try{ //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //Permcheck the user if((!this.channel.queue.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin')){ //Pull a valid start time from input, or make one up if we can't let start = this.channel.queue.getStart(data.start); //Grab playlist const playlist = userDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Pick a random video ID based on the playlist's length const foundID = Math.round(Math.random() * (playlist.media.length - 1)); //Pull and rehydrate media from playlist const foundMedia = playlist.media[foundID].rehydrate(); //Set title from default titles foundMedia.title = playlist.pickDefaultTitle(foundMedia.title); //Queue found media this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray([foundMedia], start), socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } //Rename playlist functions /** * Renames a channel playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access */ async renameChannelPlaylist(socket, data, chanDB){ try{ //Validate and Sanatize name const name = this.renameChannelPlaylistValidator(socket, data); //If validation fucked up if(name == null){ //STOP return; } //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ //If the new name already exists if(chanDB.getPlaylistByName(name) != null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, `Playlist named '${name}' already exists!`, "validation"); //and ignore it! return; } //Find playlist let playlist = chanDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Change playlist name chanDB.media.playlists[playlist.listIndex].name = name; //Save channel document await chanDB.save(); //Return playlists from channel doc this.getChannelPlaylists(socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Renames a user playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access */ async renameUserPlaylist(socket, data, userDB){ try{ //Validate and Sanatize name const name = this.renameChannelPlaylistValidator(socket, data); //If validation fucked up if(name == null){ //STOP return; } //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //If the new name already exists if(userDB.getPlaylistByName(name) != null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, `Playlist named '${name}' already exists!`, "validation"); //and ignore it! return; } //Find playlist let playlist = userDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Change playlist name userDB.playlists[playlist.listIndex].name = name; //Save channel document await userDB.save(); //Return playlists from channel doc this.getUserPlaylists(socket, userDB); }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } //Change default title list functions /** * Changes default titles for a given channel playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access */ async changeDefaultTitlesChannelPlaylist(socket, data, chanDB){ try{ //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ //Find playlist let playlist = chanDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Keep valid default titles chanDB.media.playlists[playlist.listIndex].defaultTitles = this.changeDefaultTitlesValidator(data); //Save channel document await chanDB.save(); //Return playlists from channel doc this.getChannelPlaylists(socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Changes default titles for a given user playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access */ async changeDefaultTitlesUserPlaylist(socket, data, userDB){ try{ //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //Find playlist let playlist = userDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //Keep valid de.mediafault titles userDB.playlists[playlist.listIndex].defaultTitles = this.changeDefaultTitlesValidator(data); //Save user document await userDB.save(); //Return playlists from user doc this.getUserPlaylists(socket, userDB); }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } //Delete playlist media functions /** * Deletes media from a given channel playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access */ async deleteChannelPlaylistMedia(socket, data, chanDB){ try{ //Validate UUID const uuid = this.deletePlaylistMediaValidator(socket, data); //If we failed validation if(uuid == null){ //fuck off return; } //if we wherent handed a channel document if(chanDB == null){ //Pull it based on channel name chanDB = await channelModel.findOne({name: this.channel.name}); } if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ //Find the playlist let playlist = chanDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //delete media from playlist chanDB.media.playlists[playlist.listIndex].deleteMedia(data.uuid); //save the channel document await chanDB.save(); //Return playlists from channel doc this.getChannelPlaylists(socket, chanDB); } }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } /** * Deletes media from a given user playlist * @param {Socket} socket - Requesting socket * @param {Object} data - Data handed over from the client * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access */ async deleteUserPlaylistMedia(socket, data, userDB){ try{ //Validate UUID const uuid = this.deletePlaylistMediaValidator(socket, data); //If we failed validation if(uuid == null){ //fuck off return; } //if we wherent handed a user document if(userDB == null){ //Find the user in the Database userDB = await userModel.findOne({user: socket.request.session.user.user}); } //Find the playlist let playlist = userDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ //Bitch, moan, complain... loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } //delete media from playlist userDB.playlists[playlist.listIndex].deleteMedia(data.uuid); //save the user document await userDB.save(); //Return playlists from user doc this.getUserPlaylists(socket, userDB); }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } }