334 lines
13 KiB
JavaScript
334 lines
13 KiB
JavaScript
/*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 <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');
|
|
|
|
module.exports = class{
|
|
constructor(server, chanDB, channel){
|
|
//Set server
|
|
this.server = server
|
|
//Set channel
|
|
this.channel = channel;
|
|
}
|
|
|
|
defineListeners(socket){
|
|
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("renameChannelPlaylist", (data) => {this.renameChannelPlaylist(socket, data)});
|
|
socket.on("changeDefaultTitlesChannelPlaylist", (data) => {this.changeDefaultTitlesChannelPlaylist(socket, data)});
|
|
}
|
|
|
|
//--- USER-FACING PLAYLIST FUNCTIONS ---
|
|
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);
|
|
}
|
|
}
|
|
|
|
async createChannelPlaylist(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')){
|
|
//If the title is too long
|
|
if(!validator.isLength(data.playlist, {max:30})){
|
|
//Bitch, moan, complain...
|
|
loggerUtils.socketErrorHandler(socket, "Playlist name too long!", "validation");
|
|
//and ignore it!
|
|
return;
|
|
}
|
|
|
|
//Escape/trim the playlist name
|
|
const name = validator.escape(validator.trim(data.playlist));
|
|
|
|
//If the channel already exists
|
|
if(chanDB.getPlaylistByName(name) != null){
|
|
//Bitch, moan, complain...
|
|
loggerUtils.socketErrorHandler(socket, `Playlist named '${name}' already exists!`, "validation");
|
|
//and ignore it!
|
|
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
|
|
if(validator.isLength(title, {min:1, max:30})){
|
|
//Add it to the safe title list
|
|
safeTitles.push(validator.escape(validator.trim(title)))
|
|
}
|
|
}
|
|
|
|
//Add playlist to the channel doc
|
|
chanDB.media.playlists.push({
|
|
name,
|
|
defaultTitles: safeTitles
|
|
});
|
|
|
|
//Save the channel doc
|
|
await chanDB.save();
|
|
|
|
//Return playlists from channel doc
|
|
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
|
}
|
|
}catch(err){
|
|
return loggerUtils.socketExceptionHandler(socket, err);
|
|
}
|
|
}
|
|
|
|
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(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){
|
|
//Delete playlist name
|
|
await chanDB.deletePlaylistByName(data.playlist);
|
|
|
|
//Return playlists from channel doc
|
|
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
|
}
|
|
}catch(err){
|
|
return loggerUtils.socketExceptionHandler(socket, err);
|
|
}
|
|
}
|
|
|
|
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')){
|
|
let url = 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;
|
|
}
|
|
|
|
//Add media object to the given playlist
|
|
await chanDB.addToPlaylist(data.playlist, mediaList[0]);
|
|
|
|
//Return playlists from channel doc
|
|
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
|
}
|
|
}catch(err){
|
|
return loggerUtils.socketExceptionHandler(socket, err);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
//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.defaultTitles[Math.floor(Math.random() * playlist.defaultTitles.length)];
|
|
|
|
//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 renameChannelPlaylist(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')){
|
|
//If the title is too long
|
|
if(!validator.isLength(data.name, {max:30})){
|
|
//Bitch, moan, complain...
|
|
loggerUtils.socketErrorHandler(socket, "Playlist name too long!", "validation");
|
|
//and ignore it!
|
|
return;
|
|
}
|
|
|
|
//Escape/trim the playlist name
|
|
const name = validator.escape(validator.trim(data.name));
|
|
|
|
//If the channel 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);
|
|
|
|
//Change playlist name
|
|
chanDB.media.playlists[playlist.listIndex].name = name;
|
|
|
|
//Save channel document
|
|
await chanDB.save();
|
|
|
|
//Return playlists from channel doc
|
|
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
|
}
|
|
}catch(err){
|
|
return loggerUtils.socketExceptionHandler(socket, err);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
//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;
|
|
|
|
//Save channel document
|
|
await chanDB.save();
|
|
|
|
//Return playlists from channel doc
|
|
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
|
}
|
|
}catch(err){
|
|
return loggerUtils.socketExceptionHandler(socket, err);
|
|
}
|
|
}
|
|
|
|
async deleteChannelPlaylistMedia(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')){
|
|
//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;
|
|
}
|
|
|
|
//Delete media from channel playlist
|
|
chanDB.deletePlaylistMediaByUUID(data.playlist, data.uuid);
|
|
|
|
//Return playlists from channel doc
|
|
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
|
}
|
|
}catch(err){
|
|
return loggerUtils.socketExceptionHandler(socket, err);
|
|
}
|
|
}
|
|
} |