Started work on implementing user playlists

This commit is contained in:
rainbow napkin 2025-04-06 00:24:05 -04:00
parent e629c63b2c
commit aefc2dc1bd
7 changed files with 228 additions and 33 deletions

View file

@ -22,6 +22,7 @@ const queuedMedia = require('./queuedMedia');
const loggerUtils = require('../../../utils/loggerUtils'); const loggerUtils = require('../../../utils/loggerUtils');
const yanker = require('../../../utils/media/yanker'); const yanker = require('../../../utils/media/yanker');
const channelModel = require('../../../schemas/channel/channelSchema'); const channelModel = require('../../../schemas/channel/channelSchema');
const { userModel } = require('../../../schemas/user/userSchema');
module.exports = class{ module.exports = class{
constructor(server, chanDB, channel){ constructor(server, chanDB, channel){
@ -32,6 +33,7 @@ module.exports = class{
} }
defineListeners(socket){ defineListeners(socket){
//Channel Playlist Listeners
socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)}); socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)});
socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)}); socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)});
socket.on("deleteChannelPlaylist", (data) => {this.deleteChannelPlaylist(socket, data)}); socket.on("deleteChannelPlaylist", (data) => {this.deleteChannelPlaylist(socket, data)});
@ -42,9 +44,14 @@ module.exports = class{
socket.on("queueFromChannelPlaylist", (data) => {this.queueFromChannelPlaylist(socket, data)}); socket.on("queueFromChannelPlaylist", (data) => {this.queueFromChannelPlaylist(socket, data)});
socket.on("renameChannelPlaylist", (data) => {this.renameChannelPlaylist(socket, data)}); socket.on("renameChannelPlaylist", (data) => {this.renameChannelPlaylist(socket, data)});
socket.on("changeDefaultTitlesChannelPlaylist", (data) => {this.changeDefaultTitlesChannelPlaylist(socket, data)}); socket.on("changeDefaultTitlesChannelPlaylist", (data) => {this.changeDefaultTitlesChannelPlaylist(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)});
} }
//--- USER-FACING PLAYLIST FUNCTIONS --- //Get playlist functions
async getChannelPlaylists(socket, chanDB){ async getChannelPlaylists(socket, chanDB){
try{ try{
//if we wherent handed a channel document //if we wherent handed a channel document
@ -60,6 +67,52 @@ module.exports = class{
} }
} }
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
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){ async createChannelPlaylist(socket, data, chanDB){
try{ try{
//if we wherent handed a channel document //if we wherent handed a channel document
@ -69,41 +122,27 @@ module.exports = class{
} }
if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){ if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){
//If the title is too long //Validate Data
if(!validator.isLength(data.playlist, {max:30})){ const validData = this.createPlaylistValidator(socket, data);
//Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, "Playlist name too long!", "validation"); //If we got bad data
//and ignore it! if(validData == null){
//Do nothing
return; return;
} }
//Escape/trim the playlist name
const name = validator.escape(validator.trim(data.playlist));
//If the channel already exists //If the channel already exists
if(chanDB.getPlaylistByName(name) != null){ if(chanDB.getPlaylistByName(validData.playlist) != null){
//Bitch, moan, complain... //Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, `Playlist named '${name}' already exists!`, "validation"); loggerUtils.socketErrorHandler(socket, `Playlist named '${validData.playlist}' already exists!`, "validation");
//and ignore it! //and ignore it!
return; 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 //Add playlist to the channel doc
chanDB.media.playlists.push({ chanDB.media.playlists.push({
name, name: validData.playlist,
defaultTitles: safeTitles defaultTitles: validData.defaultTitles
}); });
//Save the channel doc //Save the channel doc
@ -117,6 +156,48 @@ 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);
//If we got bad data
if(validData == null){
//Do nothing
return;
}
//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
async deleteChannelPlaylist(socket, data, chanDB){ async deleteChannelPlaylist(socket, data, chanDB){
try{ try{
//if we wherent handed a channel document //if we wherent handed a channel document
@ -125,6 +206,14 @@ module.exports = class{
chanDB = await channelModel.findOne({name: this.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')){ if(await chanDB.permCheck(socket.user, 'editChannelPlaylists')){
//Delete playlist name //Delete playlist name
await chanDB.deletePlaylistByName(data.playlist); await chanDB.deletePlaylistByName(data.playlist);
@ -137,6 +226,33 @@ module.exports = class{
} }
} }
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
async addToChannelPlaylist(socket, data, chanDB){ async addToChannelPlaylist(socket, data, chanDB){
try{ try{
//if we wherent handed a channel document //if we wherent handed a channel document
@ -198,6 +314,7 @@ module.exports = class{
} }
} }
//Queuing Functions
async queueChannelPlaylist(socket, data, chanDB){ async queueChannelPlaylist(socket, data, chanDB){
try{ try{
//if we wherent handed a channel document //if we wherent handed a channel document
@ -334,6 +451,7 @@ module.exports = class{
} }
} }
//Rename Channel Playlist
async renameChannelPlaylist(socket, data, chanDB){ async renameChannelPlaylist(socket, data, chanDB){
try{ try{
//if we wherent handed a channel document //if we wherent handed a channel document
@ -387,6 +505,7 @@ module.exports = class{
} }
} }
//Change Default Title Functions
async changeDefaultTitlesChannelPlaylist(socket, data, chanDB){ async changeDefaultTitlesChannelPlaylist(socket, data, chanDB){
try{ try{
//if we wherent handed a channel document //if we wherent handed a channel document
@ -433,6 +552,7 @@ module.exports = class{
} }
} }
//Delete Playlist Functions
async deleteChannelPlaylistMedia(socket, data, chanDB){ async deleteChannelPlaylistMedia(socket, data, chanDB){
try{ try{
//if we wherent handed a channel document //if we wherent handed a channel document

View file

@ -160,10 +160,6 @@ module.exports = class tokebot{
//we need to wait for this so we don't send used tokes pre-maturely //we need to wait for this so we don't send used tokes pre-maturely
await userModel.tattooToke(tokers); await userModel.tattooToke(tokers);
} }
cooldown(){ cooldown(){

View file

@ -556,7 +556,6 @@ channelSchema.methods.getPlaylists = function(){
const playlists = []; const playlists = [];
//For each channel emote //For each channel emote
//this.media.playlists.forEach((playlist) => {
for(let playlist of this.media.playlists){ for(let playlist of this.media.playlists){
//Push an object with select information from the emote to the emote list //Push an object with select information from the emote to the emote list
playlists.push(playlist.dehydrate()); playlists.push(playlist.dehydrate());

View file

@ -29,6 +29,7 @@ const flairModel = require('../flairSchema');
const permissionModel = require('../permissionSchema'); const permissionModel = require('../permissionSchema');
const emoteModel = require('../emoteSchema'); const emoteModel = require('../emoteSchema');
const emailChangeModel = require('./emailChangeSchema'); const emailChangeModel = require('./emailChangeSchema');
const playlistSchema = require('../channel/media/playlistSchema');
//Utils //Utils
const hashUtil = require('../../utils/hashUtils'); const hashUtil = require('../../utils/hashUtils');
const mailUtil = require('../../utils/mailUtils'); const mailUtil = require('../../utils/mailUtils');
@ -141,7 +142,8 @@ const userSchema = new mongoose.Schema({
alts:[{ alts:[{
type: mongoose.SchemaTypes.ObjectID, type: mongoose.SchemaTypes.ObjectID,
ref: "user" ref: "user"
}] }],
playlists: [playlistSchema]
}); });
//This is one of those places where you really DON'T want to use an arrow function over an anonymous one! //This is one of those places where you really DON'T want to use an arrow function over an anonymous one!
@ -535,6 +537,60 @@ userSchema.methods.deleteEmote = async function(name){
} }
} }
userSchema.methods.getPlaylists = function(){
//Create an empty array to hold our emote list
const playlists = [];
//For each channel emote
for(let playlist of this.playlists){
//Push an object with select information from the emote to the emote list
playlists.push(playlist.dehydrate());
}
//return the emote list
return playlists;
}
userSchema.methods.playlistCrawl = function(cb){
for(let listIndex in this.playlists){
//Grab the associated playlist
playlist = this.playlists[listIndex];
//Call the callback with the playlist and list index as arguments
cb(playlist, listIndex);
}
}
userSchema.methods.getPlaylistByName = function(name){
//Create null value to hold our found playlist
let foundPlaylist = null;
//Crawl through active playlists
this.playlistCrawl((playlist, listIndex) => {
//If we found a match based on name
if(playlist.name == name){
//Keep it
foundPlaylist = playlist;
//Pass down the list index
foundPlaylist.listIndex = listIndex;
}
});
//return the given playlist
return foundPlaylist;
}
userSchema.methods.deletePlaylistByName = async function(name){
//Find the playlist
const playlist = this.getPlaylistByName(name);
//splice out the given playlist
this.playlists.splice(playlist.listIndex, 1);
//save the channel document
await this.save();
}
userSchema.methods.tattooIPRecord = async function(ip){ userSchema.methods.tattooIPRecord = async function(ip){
//Hash the users ip //Hash the users ip
const ipHash = hashUtil.hashIP(ip); const ipHash = hashUtil.hashIP(ip);

View file

@ -49,8 +49,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<script src="/js/channel/cpanel.js"></script> <script src="/js/channel/cpanel.js"></script>
<%# panels %> <%# panels %>
<script src="/js/channel/panels/emotePanel.js"></script> <script src="/js/channel/panels/emotePanel.js"></script>
<script src="/js/channel/panels/queuePanel.js"></script>
<script src="/js/channel/panels/queuePanel/playlistManager.js"></script> <script src="/js/channel/panels/queuePanel/playlistManager.js"></script>
<script src="/js/channel/panels/queuePanel/queuePanel.js"></script>
<%# main client %> <%# main client %>
<script src="/js/channel/channel.js"></script> <script src="/js/channel/channel.js"></script>
</footer> </footer>

View file

@ -33,11 +33,14 @@ class playlistManager{
docSwitch(){ docSwitch(){
//Grab menus //Grab menus
this.channelPlaylistDiv = this.panelDocument.querySelector("#queue-channel-playlist-div"); this.channelPlaylistDiv = this.panelDocument.querySelector("#queue-channel-playlist-div");
this.userPlaylistDiv = this.panelDocument.querySelector("#queue-user-playlist-div");
//Grab controls //Grab controls
this.createPlaylistSpan = this.panelDocument.querySelector('#queue-add-playlist-span'); this.createPlaylistSpan = this.panelDocument.querySelector('#queue-add-playlist-span');
this.channelPlaylistLabel = this.panelDocument.querySelector('#queue-channel-playlist-span'); this.channelPlaylistLabel = this.panelDocument.querySelector('#queue-channel-playlist-span');
this.channelPlaylistCaret = this.panelDocument.querySelector('#queue-channel-playlist-toggle'); this.channelPlaylistCaret = this.panelDocument.querySelector('#queue-channel-playlist-toggle');
this.userPlaylistLabel = this.panelDocument.querySelector('#queue-user-playlist-span');
this.userPlaylistCaret = this.panelDocument.querySelector('#queue-user-playlist-toggle');
//Force playlist re-render to fix controls //Force playlist re-render to fix controls
this.client.socket.emit('getChannelPlaylists'); this.client.socket.emit('getChannelPlaylists');
@ -49,6 +52,7 @@ class playlistManager{
setupInput(){ setupInput(){
this.createPlaylistSpan.addEventListener('click', (event)=>{new newPlaylistPopup(event, this.client, this.queuePanel.ownerDoc)}) this.createPlaylistSpan.addEventListener('click', (event)=>{new newPlaylistPopup(event, this.client, this.queuePanel.ownerDoc)})
this.channelPlaylistLabel.addEventListener('click', this.toggleChannelPlaylists.bind(this)); this.channelPlaylistLabel.addEventListener('click', this.toggleChannelPlaylists.bind(this));
this.userPlaylistLabel.addEventListener('click', this.toggleUserPlaylists.bind(this));
} }
/* queue control button functions */ /* queue control button functions */
@ -71,6 +75,26 @@ class playlistManager{
} }
} }
/* queue control button functions */
toggleUserPlaylists(event){
//If the div is hidden
if(this.userPlaylistDiv.style.display == 'none'){
//Light up the button
this.userPlaylistLabel.classList.add('positive');
//Flip the caret
this.userPlaylistCaret.classList.replace('bi-caret-right-fill', 'bi-caret-down-fill');
//Show the div
this.userPlaylistDiv.style.display = '';
}else{
//Unlight the button
this.userPlaylistLabel.classList.remove('positive');
//Flip the caret
this.userPlaylistCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
//Hide the div
this.userPlaylistDiv.style.display = 'none';
}
}
checkOpenPlaylists(){ checkOpenPlaylists(){
//If open map is a string, indicating we just renamed a playlist with it's media open //If open map is a string, indicating we just renamed a playlist with it's media open
if(typeof this.openMap == 'string'){ if(typeof this.openMap == 'string'){