From 72a89ae5ffbb92f86bbdb3fb4903e6ebbc5631f7 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Sun, 23 Mar 2025 22:30:10 -0400 Subject: [PATCH] Started work on channel-wide playlists. --- src/app/channel/activeChannel.js | 5 +- src/app/channel/media/playlistHandler.js | 87 +++++++++++++++++++ src/schemas/channel/channelSchema.js | 69 ++++++++++++++- src/schemas/channel/media/playlistSchema.js | 2 +- .../channel/media/queuedMediaSchema.js | 3 +- 5 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 src/app/channel/media/playlistHandler.js diff --git a/src/app/channel/activeChannel.js b/src/app/channel/activeChannel.js index 62f03f4..7539561 100644 --- a/src/app/channel/activeChannel.js +++ b/src/app/channel/activeChannel.js @@ -17,9 +17,8 @@ along with this program. If not, see .*/ //local imports const connectedUser = require('./connectedUser'); const queue = require('./media/queue'); -const flairModel = require('../../schemas/flairSchema'); -const permissionModel = require('../../schemas/permissionSchema'); const channelModel = require('../../schemas/channel/channelSchema'); +const playlistHandler = require('./media/playlistHandler') module.exports = class{ constructor(server, chanDB){ @@ -29,6 +28,7 @@ module.exports = class{ //Keeping these in a map was originally a vestige but it's more preformant than an array or object so :P this.userList = new Map(); this.queue = new queue(server, chanDB, this); + this.playlistHandler = new playlistHandler(server, chanDB, this); } async handleConnection(userDB, chanDB, socket){ @@ -57,6 +57,7 @@ module.exports = class{ //Define per-channel event listeners this.queue.defineListeners(socket); + this.playlistHandler.defineListeners(socket); //Hand off the connection initiation to it's user object await userObj.handleConnection(userDB, chanDB, socket) diff --git a/src/app/channel/media/playlistHandler.js b/src/app/channel/media/playlistHandler.js new file mode 100644 index 0000000..8749f50 --- /dev/null +++ b/src/app/channel/media/playlistHandler.js @@ -0,0 +1,87 @@ +/*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 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("queue", (data) => {this.queueURL(socket, data)}); + socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)}); + socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)}); + socket.on("addToChannelPlaylist", (data) => {this.addToChannelPlaylist(socket, data)}); + } + + async getChannelPlaylists(socket, chanDB){ + //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()); + } + + async createChannelPlaylist(socket, data, chanDB){ + //if we wherent handed a channel document + if(chanDB == null){ + //Pull it based on channel name + chanDB = await channelModel.findOne({name: this.channel.name}); + } + + //Add playlist to the channel doc + chanDB.media.playlists.push({ + name: data.name, + defaultTitles: data.defaultTitles + }); + + //Save the channel doc + await chanDB.save(); + + //Return playlists from channel doc + socket.emit('chanPlaylists', chanDB.getPlaylists()); + } + + async addToChannelPlaylist(socket, data, chanDB){ + //if we wherent handed a channel document + if(chanDB == null){ + //Pull it based on channel name + chanDB = await channelModel.findOne({name: this.channel.name}); + } + + //Pull media metadata + let mediaList = await yanker.yankMedia(data.url); + + //Add media object to the given playlist + await chanDB.addToPlaylist(data.playlist, mediaList[0]); + + //Return playlists from channel doc + socket.emit('chanPlaylists', chanDB.getPlaylists()); + } +} \ No newline at end of file diff --git a/src/schemas/channel/channelSchema.js b/src/schemas/channel/channelSchema.js index 99b0e34..6d95023 100644 --- a/src/schemas/channel/channelSchema.js +++ b/src/schemas/channel/channelSchema.js @@ -103,7 +103,7 @@ const channelSchema = new mongoose.Schema({ media: { nowPlaying: queuedMediaSchema, scheduled: [queuedMediaSchema], - //We should consider moving archived media and channel playlists to their own collections/models for preformance sake + //We should consider moving archived media and channel playlists to their own collections/models for preformances sake archived: [queuedMediaSchema], playlists: [playlistSchema] }, @@ -551,6 +551,73 @@ channelSchema.methods.getEmotes = function(){ return emoteList; } +channelSchema.methods.getPlaylists = function(){ + //Create an empty array to hold our emote list + const playlists = []; + + //For each channel emote + //this.media.playlists.forEach((playlist) => { + for(let playlist of this.media.playlists){ + //Push an object with select information from the emote to the emote list + playlists.push({ + name: playlist.name, + count: playlist.media.length + }); + } + + //return the emote list + return playlists; +} + +channelSchema.methods.playlistCrawl = function(cb){ + for(let listIndex in this.media.playlists){ + //Grab the associated playlist + playlist = this.media.playlists[listIndex]; + + //Call the callback with the playlist and list index as arguments + cb(playlist, listIndex); + } +} + +channelSchema.methods.getPlaylistByName = function(name){ + //Create null value to hold our found playlist + let foundPlaylist = null; + + //Crawl through active playlists + this.playlistCrawl((playlist) => { + //If we found a match based on name + if(playlist.name == name){ + //Keep it + foundPlaylist = playlist; + } + }); + + //return the given playlist + return foundPlaylist; +} + +channelSchema.methods.addToPlaylist = async function(name, media){ + //Create variable to hold found index + let foundIndex = null + + //Crawl through active playlists + this.playlistCrawl((playlist, listIndex) => { + //If the playlist name matches + if(playlist.name == name){ + //Push the given media into the found playlist + //this.media.playlists[listIndex].push(media); + + //Make note of the found index + foundIndex = listIndex + } + }); + + this.media.playlists[foundIndex].media.push(media); + + //Save the changes made to the chan doc + await this.save(); +} + channelSchema.methods.getChanBans = async function(){ //Create an empty list to hold our found bans var banList = []; diff --git a/src/schemas/channel/media/playlistSchema.js b/src/schemas/channel/media/playlistSchema.js index 2dc6d0b..5644c5b 100644 --- a/src/schemas/channel/media/playlistSchema.js +++ b/src/schemas/channel/media/playlistSchema.js @@ -31,4 +31,4 @@ module.exports = new mongoose.Schema({ required: true, default: [] }] -}); +}); \ No newline at end of file diff --git a/src/schemas/channel/media/queuedMediaSchema.js b/src/schemas/channel/media/queuedMediaSchema.js index 85a3d88..976fb4e 100644 --- a/src/schemas/channel/media/queuedMediaSchema.js +++ b/src/schemas/channel/media/queuedMediaSchema.js @@ -44,7 +44,7 @@ const queuedProperties = new mongoose.Schema({ discriminatorKey: 'status' }); -//methods +//Methods //Rehydrate to a full phat queued media object queuedProperties.methods.rehydrate = function(){ return new queuedMedia( @@ -61,4 +61,5 @@ queuedProperties.methods.rehydrate = function(){ ); } +//Export schema under the 'queued' discriminator of mediaSchema module.exports = mediaSchema.discriminator('queued', queuedProperties); \ No newline at end of file