canopy/www/doc/server/app_channel_media_playlistHandler.js.html

1190 lines
49 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/media/playlistHandler.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/media/playlistHandler.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
//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
*/
class playlistHandler{
/**
* 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 {Array} Array of strings containing valid titles from the output
*/
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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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);
}
}
}
module.exports = playlistHandler;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Sep 05 2025 05:55:07 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>