diff --git a/src/app/channel/media/playlistHandler.js b/src/app/channel/media/playlistHandler.js index c2c6bfb..0f7ec1b 100644 --- a/src/app/channel/media/playlistHandler.js +++ b/src/app/channel/media/playlistHandler.js @@ -37,18 +37,140 @@ module.exports = class{ socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)}); socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)}); socket.on("deleteChannelPlaylist", (data) => {this.deleteChannelPlaylist(socket, data)}); - socket.on("deleteChannelPlaylistMedia", (data) => {this.deleteChannelPlaylistMedia(socket, data)}); socket.on("addToChannelPlaylist", (data) => {this.addToChannelPlaylist(socket, data)}); socket.on("queueChannelPlaylist", (data) => {this.queueChannelPlaylist(socket, data)}); - socket.on("queueRandomFromChannelPlaylist", (data) => {this.queueRandomFromChannelPlaylist(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 + 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 + } + } + + 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; + } + + 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) + } + + 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)); + } + + 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; + } + + 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 @@ -83,38 +205,17 @@ module.exports = class{ } //Create playlist functions - createPlaylistValidator(socket, data){ - //Create empty array to hold titles - const safeTitles = []; - - //If the title is too long - if(!validator.isLength(data.playlist, {min: 1, max:30})){ - //Bitch, moan, complain... - loggerUtils.socketErrorHandler(socket, "Playlist name too long!", "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(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 - } - } - 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 @@ -122,15 +223,6 @@ module.exports = class{ } if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ - //Validate Data - const validData = this.createPlaylistValidator(socket, data); - - //If we got bad data - if(validData == null){ - //Do nothing - return; - } - //If the channel already exists if(chanDB.getPlaylistByName(validData.playlist) != null){ //Bitch, moan, complain... @@ -158,12 +250,6 @@ module.exports = class{ async createUserPlaylist(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}); - } - //Validate Data const validData = this.createPlaylistValidator(socket, data); @@ -173,6 +259,12 @@ module.exports = class{ 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... @@ -262,35 +354,19 @@ module.exports = class{ } if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ - let url = data.url + //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 where given a bad URL - if(!validator.isURL(url)){ - //Attempt to fix the situation by encoding it - url = encodeURI(url); - - //If it's still bad - if(!validator.isURL(url)){ - //Bitch, moan, complain... - loggerUtils.socketErrorHandler(socket, "Bad URL!", "validation"); - //and ignore it! - return; - } - } - - //Pull media metadata - let 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; + //If we encountered an error during validation + if(mediaList == null){ + //Fuck off and die + return; } //Find the playlist - let playlist = chanDB.getPlaylistByName(data.playlist); + const playlist = chanDB.getPlaylistByName(data.playlist); //If we didn't find a real playlist if(playlist == null){ @@ -314,6 +390,47 @@ module.exports = class{ } } + 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 async queueChannelPlaylist(socket, data, chanDB){ try{ @@ -348,7 +465,60 @@ module.exports = class{ let mediaObj = item.rehydrate(); //Set media title from default titles - mediaObj.title = playlist.pickDefaultTitle(); + 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); + } + } + + 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); @@ -364,6 +534,15 @@ module.exports = class{ 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 @@ -372,9 +551,6 @@ module.exports = class{ //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); @@ -384,26 +560,67 @@ module.exports = class{ loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; - } - - //If we don't have a valid UUID - if(!validator.isUUID(data.uuid)){ - //Bitch, moan, complain... - loggerUtils.socketErrorHandler(socket, `'${data.uuid}' is not a valid UUID!`, "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 = playlist.pickDefaultTitle(media.title); //Queue found media this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray([media], start), socket, chanDB); } + }catch(err){ + return loggerUtils.socketExceptionHandler(socket, err); + } + } + 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); } @@ -440,19 +657,17 @@ module.exports = class{ const foundMedia = playlist.media[foundID].rehydrate(); //Set title from default titles - foundMedia.title = playlist.pickDefaultTitle(); + 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 Channel Playlist - async renameChannelPlaylist(socket, data, chanDB){ + async queueRandomFromUserPlaylist(socket, data, userDB, chanDB){ try{ //if we wherent handed a channel document if(chanDB == null){ @@ -460,19 +675,65 @@ module.exports = class{ chanDB = await channelModel.findOne({name: this.channel.name}); } - if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ - //If the title is too long - if(!validator.isLength(data.name, {max:30})){ + //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 name too long!", "validation"); + loggerUtils.socketErrorHandler(socket, "Playlist not found!", "validation"); //and ignore it! return; } - //Escape/trim the playlist name - const name = validator.escape(validator.trim(data.name)); + //Pick a random video ID based on the playlist's length + const foundID = Math.round(Math.random() * (playlist.media.length - 1)); - //If the channel already exists + //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 + 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"); @@ -505,7 +766,56 @@ module.exports = class{ } } - //Change Default Title Functions + 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 async changeDefaultTitlesChannelPlaylist(socket, data, chanDB){ try{ //if we wherent handed a channel document @@ -526,20 +836,8 @@ module.exports = class{ return; } - //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(validator.isLength(title, {min: 1, max:30})){ - //Add it to the safe title list - safeTitles.push(validator.escape(validator.trim(title))) - } - } - - //Change playlist name - chanDB.media.playlists[playlist.listIndex].defaultTitles = safeTitles; + //Keep valid default titles + chanDB.media.playlists[playlist.listIndex].defaultTitles = this.changeDefaultTitlesValidator(data); //Save channel document await chanDB.save(); @@ -552,9 +850,51 @@ module.exports = class{ } } - //Delete Playlist Functions + 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 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 @@ -562,14 +902,6 @@ module.exports = class{ } if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ - //If we don't have a valid UUID - if(!validator.isUUID(data.uuid)){ - //Bitch, moan, complain... - loggerUtils.socketErrorHandler(socket, `'${data.uuid}' is not a valid UUID!`, "validation"); - //and ignore it! - return; - } - //Find the playlist let playlist = chanDB.getPlaylistByName(data.playlist); @@ -594,4 +926,45 @@ module.exports = class{ return loggerUtils.socketExceptionHandler(socket, err); } } + + 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); + } + } } \ No newline at end of file diff --git a/src/schemas/channel/media/playlistSchema.js b/src/schemas/channel/media/playlistSchema.js index 274633a..38103d2 100644 --- a/src/schemas/channel/media/playlistSchema.js +++ b/src/schemas/channel/media/playlistSchema.js @@ -91,9 +91,19 @@ playlistSchema.methods.deleteMedia = function(uuid){ this.media = keptMedia; } -playlistSchema.methods.pickDefaultTitle = function(){ - //Grab a random default title and return it - return this.defaultTitles[Math.floor(Math.random() * this.defaultTitles.length)]; +playlistSchema.methods.pickDefaultTitle = function(title){ + //If we don't have default titles in this playlist + if(this.defaultTitles.length <= 0){ + //If we wheren't handed an original title + if(title == null || title == ''){ + return 'Unnamed Media' + }else{ + return title; + } + }else{ + //Grab a random default title and return it + return this.defaultTitles[Math.floor(Math.random() * this.defaultTitles.length)]; + } } module.exports = playlistSchema; \ No newline at end of file