diff --git a/server/README.md b/server/README.md index c3057e69..bc1bf5aa 100644 --- a/server/README.md +++ b/server/README.md @@ -2,57 +2,197 @@ Under ``` /server/apps/ ```, there are two files, ``` admin.js ``` and ``` client.js ```.``` admin.js ``` are for the adminpanel, and ``` client.js ``` are for zoff itself. +## REST + +Add song + +``` +POST /api/list/:channel_name/:video_id + { + "title": TITLE, + "duration": END_TIME - START_TIME, + "end_time": END_TIME, + "start_time": START_TIME, + "adminpass": PASSWORD, + "userpass": USER_PASSWORD + } + +Returns 403 for bad authentication +Returns 409 if the song exists +Returns 200 and the added song object if successful +``` + +Delete song +``` +DELETE /api/list/:channel_name/:video_id + { + "adminpass": PASSWORD, + "userpass": USER_PASSWORD + } + +Returns 403 for bad authentication +Returns 404 if the song doesnt exist +Returns 200 if successful +``` + +Vote on song +``` +PUT /api/list/:channel_name/:video_id + { + "adminpass": PASSWORD, + "userpass": USER_PASSWORD + } + +Returns 403 for bad authentication +Returns 404 if the song doesnt exist +Returns 409 if you've already voted on that song +Returns 200 and the added song object if successful +``` + +Change channel configurations +``` +PUT /api/conf/:channel_name + { + "userpass": USER_PASSWORD, + "adminpass": PASSWORD, + "voting": BOOLEAN, + "addsongs": BOOLEAN, + "longsongs": BOOLEAN, + "frontpage": BOOLEAN (if you want to set userpassword, this MUST be false for it to work), + "allvideos": BOOLEAN, + "removeplay": BOOLEAN, + "skipping": BOOLEAN, + "shuffling": BOOLEAN, + "userpass_changed": BOOLEAN (this must be true if you want to keep the userpassword you're sending) + } + +Returns 403 for bad authentication +Returns 404 if the list doesn't exist +Returns 200 and the newly added configuration if successful +``` + +Still to come: SKIP and SHUFFLE RESTApi calls.. + + ## Events ### To server ``` -// Tells the server the song is clientside -'end', {id: video_id, channel: channel_name, pass: channel_pass} +// Tells the server the song is over +'end', { + id: video_id, + channel: channel_name, + pass: channel_pass +} // Asks server where in the song it should be -'pos', {channel: channel_name, pass: channel_pass} +'pos', { + channel: channel_name, + pass: channel_pass +} // Tells the server the client wants the list -'list', {channel: channel_name, pass: channel_pass, version: system_version (now 3)} +'list', { + channel: channel_name, + pass: channel_pass, + version: system_version (now 3) +} // Sends info about a song the client wants to add -'add', {id: VIDEO_ID, title: VIDEO_TITLE, adminpass: sha256(PASSWORD), duration: VIDEO_DURATION, list: channel_name, playlist: true_if_importing_playlist, num: current_number_of_sending_songs, total: total_number_of_sending_songs, pass: channel_pass} +'add', { + id: VIDEO_ID, + title: VIDEO_TITLE, + adminpass: AES-CBC-Pkcs7 with Base64 IV(PASSWORD), + duration: VIDEO_DURATION, + list: channel_name, + playlist: true_if_importing_playlist, + num: current_number_of_sending_songs, + total: total_number_of_sending_songs, + pass: channel_pass +} // Tells the server to disconnect the user from the current channel, is used for remote controlling on the host side -'change_channel', {channel: channel_name} +'change_channel', { + channel: channel_name +} // Sends chat text to all chat -'all,chat', {channel: channel_name, data: input} +'all,chat', { + channel: channel_name, + data: input +} // Sends chat text to channelchat -'chat',{channel: channel_name, data: input, pass: channel_pass} +'chat',{ + channel: channel_name, + data: input, + pass: channel_pass +} // Sends info about song the user wants to vote on. If VOTE_TYPE is del, its deleting the song, if its pos, its just voting -'vote', {channel: CHANNEL_NAME, id: VIDEO_ID, type: VOTE_TYPE, adminpass: PASSWORD} +'vote', { + channel: CHANNEL_NAME, + id: VIDEO_ID, + type: VOTE_TYPE, + adminpass: AES-CBC-Pkcs7 with Base64 IV(PASSWORD) +} // Sends skip message to server -'skip', {pass: adminpass, id:video_id, channel: chan, userpass: channel_pass} +'skip', { + pass: AES-CBC-Pkcs7 with Base64 IV(PASSWORD), + id:video_id, + channel: chan, + userpass: channel_pass +} // Sends password for instant log in to server -'password', {password: PASSWORD, channel: CHANNEL_NAME, oldpass: old_pass_if_changing_password} +'password', { + password: PASSWORD, + channel: CHANNEL_NAME, + oldpass: old_pass_if_changing_password +} // Sends message to the host channel for play -'id', {id: CHANNEL_ID, type: "play", value: "mock"} +'id', { + id: CHANNEL_ID, + type: "play", + value: "mock" +} // Sends message to the host channel for pause -'id', {id: CHANNEL_ID, type: "pause", value: "mock"} +'id', { + id: CHANNEL_ID, + type: "pause", + value: "mock" +} // Sends message to the host channel for skip -'id', {id: CHANNEL_ID, type: "skip", value: "mock"} +'id', { + id: CHANNEL_ID, + type: "skip", + value: "mock" +} // Sends message to the host channel to change volume -'id', {id: CHANNEL_ID, type: "volume", value: VALUE} +'id', { + id: CHANNEL_ID, + type: "volume", + value: VALUE +} // Sends message to the host channel to change channel -'id', {id: CHANNEL_ID, type: "channel", value: NEW_CHANNEL_NAME} +'id', { + id: CHANNEL_ID, + type: "channel", + value: NEW_CHANNEL_NAME +} // Sends a video that triggered an error -'error_video', {channel: CHANNE_NAME, id: VIDEO_ID, title: VIDEO_TITLE} +'error_video', { + channel: CHANNE_NAME, + id: VIDEO_ID, + title: VIDEO_TITLE +} ``` ### From server @@ -60,36 +200,65 @@ Under ``` /server/apps/ ```, there are two files, ``` admin.js ``` and ``` clien // Receives a string from server for what type of toast to be triggered 'toast', STRING -// Receives the password for the channel if the user sent the right in the first place -'pw', STRING +// Receives a boolean if the password was correct +'pw', BOOLEAN // Receives configuration array from server 'conf', [ARRAY] // Receives chat message from allchat -'chat.all', {from: name, msg: message, channel: channel, icon: icon_src} +'chat.all', { + from: name, + msg: message, + channel: channel, + icon: icon_src +} + +// Receives chat-history for all and for current channel +'chat_history', { + all: BOOLEAN (if true, it is for all-chat), + data: CHAT_HISTORY +} // Receives chat message from channelchat -'chat', {from: name, msg: message, icon: icon_src} +'chat', { + from: name, + msg: message, + icon: icon_src +} // Receives the ID of the current client, used for remote listening 'id', STRING // Receives the messages sent on CHANNEL_ID above -id, {type: STRING, value: VALUE} +id, { + type: STRING, + value: VALUE +} // Receives updates from channel. type is one of the following: list, added, deleted, vote, song_change, changed_values (see further down for better explanation here) -'channel', {type: TYPE, value: value, time: time_of_occurence} +'channel', { + type: TYPE, + value: value, + time: time_of_occurence +} // Receives message from the server that its ready to send the playlist and info 'get_list' // Receives array of now playing song. Is triggered on song-change -'np', {np: NOW_PLAYING, conf: CONFIGURATION, time: SERVER_TIME} +'np', { + np: NOW_PLAYING, + conf: CONFIGURATION, + time: SERVER_TIME +} // Receives number of viewers on the current channel 'viewers', VALUE // Receives a newly updated video, that was checked for errors (song_generated contains .id which is the current id of the video, and a .new_id for the new video to change the video to) -'channel', {type: "changed_values", value: song_generated} +'channel', { + type: "changed_values", + value: song_generated +} ``` diff --git a/server/handlers/frontpage.js b/server/handlers/frontpage.js index 174a343b..8c7e655c 100644 --- a/server/handlers/frontpage.js +++ b/server/handlers/frontpage.js @@ -10,12 +10,14 @@ function frontpage_lists(msg, socket) { }); } -function update_frontpage(coll, id, title) { +function update_frontpage(coll, id, title, callback) { db.collection("frontpage_lists").update({_id: coll}, {$set: { id: id, title: title, accessed: Functions.get_time()} - },{upsert: true}, function(err, returnDocs){}); + },{upsert: true}, function(err, returnDocs){ + if(typeof(callback) == "function") callback(); + }); } module.exports.frontpage_lists = frontpage_lists; diff --git a/server/handlers/list.js b/server/handlers/list.js index 2bcb7a93..37f3c7ba 100644 --- a/server/handlers/list.js +++ b/server/handlers/list.js @@ -74,6 +74,7 @@ function list(msg, guid, coll, offline, socket) { function skip(list, guid, coll, offline, socket) { var socketid = socket.zoff_id; + if(list !== undefined && list !== null && list !== "") { @@ -81,7 +82,11 @@ function skip(list, guid, coll, offline, socket) { socket.emit("update_required"); return; } - + if(typeof(list.pass) != "string" || typeof(list.id) != "string" || + typeof(list.channel) != "string" || typeof(list.userpass) != "string") { + socket.emit("toast", "update_required"); + return; + } db.collection(coll + "_settings").find(function(err, docs){ if(docs.length > 0 && (docs[0].userpass == undefined || docs[0].userpass == "" || (list.hasOwnProperty('userpass') && docs[0].userpass == Functions.decrypt_string(socketid, list.userpass)))) { @@ -401,6 +406,7 @@ function end(obj, coll, guid, offline, socket) { return; } id = obj.id; + if(id !== undefined && id !== null && id !== "") { if(coll == "" || coll == undefined || coll == null) { @@ -408,6 +414,12 @@ function end(obj, coll, guid, offline, socket) { return; } + if(typeof(obj.id) != "string" || typeof(obj.channel) != "string" || + typeof(obj.pass) != "string") { + socket.emit("toast", "update_required"); + return; + } + db.collection(coll + "_settings").find(function(err, docs){ if(docs.length > 0 && (docs[0].userpass == undefined || docs[0].userpass == "" || (obj.hasOwnProperty('pass') && docs[0].userpass == Functions.decrypt_string(socketid, obj.pass)))) { @@ -491,7 +503,7 @@ function sendColor(coll, socket, id) { }); } -function getNextSong(coll) { +function getNextSong(coll, callback) { db.collection(coll).aggregate([{ $match:{ views:{ @@ -514,6 +526,7 @@ function getNextSong(coll) { if(doc.length == 1) { io.to(coll).emit("next_song", {videoId: doc[0].id, title: doc[0].title}); } + if(typeof(callback) == "function") callback(); }); } diff --git a/server/handlers/list_change.js b/server/handlers/list_change.js index 89649c2c..005a8a5a 100644 --- a/server/handlers/list_change.js +++ b/server/handlers/list_change.js @@ -27,6 +27,17 @@ function add_function(arr, coll, guid, offline, socket) { return; } + + if(typeof(arr.id) != "string" || typeof(arr.start) != "number" || + typeof(arr.end) != "number" || typeof(arr.title) != "string" || + typeof(arr.list) != "string" || typeof(arr.duration) != "number" || + typeof(arr.playlist) != "boolean" || typeof(arr.num) != "number" || + typeof(arr.total) != "number" || typeof(arr.pass) != "string" || + typeof(arr.adminpass) != "string") { + socket.emit("toast", "update_required"); + return; + } + db.collection(coll + "_settings").find(function(err, docs){ if(docs.length > 0 && (docs[0].userpass == undefined || docs[0].userpass == "" || (arr.hasOwnProperty('pass') && docs[0].userpass == Functions.decrypt_string(socketid, arr.pass)))) { @@ -179,6 +190,13 @@ function voteUndecided(msg, coll, guid, offline, socket) { return; } + if(typeof(msg.channel) != "string" || typeof(msg.id) != "string" || + typeof(msg.type) != "string" || typeof(msg.adminpass) != "string" || + typeof(msg.pass) != "string") { + socket.emit("toast", "update_required"); + return; + } + db.collection(coll + "_settings").find(function(err, docs){ if(docs.length > 0 && (docs[0].userpass == undefined || docs[0].userpass == "" || (msg.hasOwnProperty('pass') && docs[0].userpass == Functions.decrypt_string(socketid, msg.pass)))) { @@ -218,6 +236,12 @@ function shuffle(msg, coll, guid, offline, socket) { return; } + if(typeof(msg.adminpass) != "string" || typeof(msg.channel) != "string" || + typeof(msg.pass) != "string") { + socket.emit("toast", "update_required"); + return; + } + Functions.check_inlist(coll, guid, socket, offline); var hash; if(msg.adminpass === "") hash = msg.adminpass; @@ -290,6 +314,11 @@ function delete_all(msg, coll, guid, offline, socket) { var hash = Functions.hash_pass(Functions.decrypt_string(socketid, msg.adminpass)); var hash_userpass = Functions.decrypt_string(socketid, msg.pass); + if(typeof(msg.channel) != "string" || typeof(msg.adminpass) != "string" || + typeof(msg.pass) != "string") { + socket.emit("toast", "update_required"); + return; + } db.collection(coll + "_settings").find(function(err, conf) { if(conf.length == 1 && conf) { conf = conf[0]; diff --git a/server/handlers/list_settings.js b/server/handlers/list_settings.js index 6896af1e..d7a3d5d9 100644 --- a/server/handlers/list_settings.js +++ b/server/handlers/list_settings.js @@ -21,7 +21,6 @@ function password(inp, coll, guid, offline, socket) { uncrypted = pw; pw = Functions.decrypt_string(socket.zoff_id, pw); - Functions.check_inlist(coll, guid, socket, offline); if(inp.oldpass) @@ -44,6 +43,7 @@ function password(inp, coll, guid, offline, socket) { }); }else socket.emit("toast", "wrongpass"); + socket.emit("pw", false); } }); } else { @@ -94,6 +94,15 @@ function conf_function(params, coll, guid, offline, socket) { var skipping = params.skipping; var shuffling = params.shuffling; var userpass = Functions.decrypt_string(socket.zoff_id, params.userpass); + if(typeof(userpass) != "string" || typeof(adminpass) != "string" || + typeof(voting) != "boolean" || typeof(addsongs) != "boolean" || + typeof(longsongs) != "boolean" || typeof(frontpage) != "boolean" || + typeof(allvideos) != "boolean" || typeof(removeplay) != "boolean" || + typeof(skipping) != "boolean" || typeof(shuffling) != "boolean" || + typeof(params.userpass_changed) != "boolean") { + socket.emit("toast", "wrongpass"); + return; + } if((!params.userpass_changed && frontpage) || (params.userpass_changed && userpass == "")) { userpass = ""; diff --git a/server/handlers/suggestions.js b/server/handlers/suggestions.js index 093c7e0e..f9a10bb0 100644 --- a/server/handlers/suggestions.js +++ b/server/handlers/suggestions.js @@ -1,5 +1,10 @@ function thumbnail(msg, coll, guid, offline, socket) { if(msg.thumbnail && msg.channel && msg.adminpass && msg.thumbnail.indexOf("i.imgur.com") > -1){ + if(typeof(msg.channel) != "string" || typeof(msg.thumbnail) != "string" || + typeof(msg.adminpass) != "string" || typeof(msg.pass) != "string") { + socket.emit("toast", "update_required"); + return; + } msg.thumbnail = msg.thumbnail.replace(/^https?\:\/\//i, ""); if(msg.thumbnail.substring(0,2) != "//") msg.thumbnail = "//" + msg.thumbnail; var channel = msg.channel.toLowerCase(); @@ -23,6 +28,11 @@ function thumbnail(msg, coll, guid, offline, socket) { function description(msg, coll, guid, offline, socket) { if(msg.description && msg.channel && msg.adminpass && msg.description.length < 100){ + if(typeof(msg.channel) != "string" || typeof(msg.description) != "string" || + typeof(msg.adminpass) != "string" || typeof(msg.pass) != "string") { + socket.emit("toast", "update_required"); + return; + } var channel = msg.channel.toLowerCase(); var hash = Functions.hash_pass(Functions.decrypt_string(socket.zoff_id, msg.adminpass)); db.collection(channel + "_settings").update({views: {$exists: true}}, function(err, docs){ diff --git a/server/routing/client/api.js b/server/routing/client/api.js index be976d77..f9cbeeab 100644 --- a/server/routing/client/api.js +++ b/server/routing/client/api.js @@ -19,6 +19,332 @@ router.route('/api/generate_name').get(function(req, res) { Functions.generate_channel_name(res); }); +router.route('/api/list/:channel_name/:video_id').delete(function(req, res) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + if(!req.body.hasOwnProperty('adminpass') || !req.body.hasOwnProperty('userpass') || + !req.params.hasOwnProperty('channel_name') || !req.params.hasOwnProperty('video_id')) { + res.sendStatus(400); + return; + } + try { + var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; + var guid = Functions.hash_pass(req.get('User-Agent') + ip + req.headers["accept-language"]); + var adminpass = req.body.adminpass == "" ? "" : Functions.hash_pass(crypto.createHash('sha256').update(req.body.adminpass, 'utf8').digest("hex")); + req.body.userpass = crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex"); + var userpass = req.body.userpass; + var channel_name = cleanChannelName(req.params.channel_name); + var video_id = req.params.video_id; + if(typeof(userpass) != "string" || typeof(adminpass) != "string") { + throw "Wrong format"; + } + } catch(e) { + res.sendStatus(400); + return; + } + + validateLogin(adminpass, userpass, channel_name, "delete", res, function(exists) { + if(!exists) { + res.sendStatus(404); + return; + } + db.collection(channel_name).find({id:video_id, now_playing: false}, function(err, docs){ + if(docs.length == 0) { + res.sendStatus(404); + return; + } + dont_increment = true; + if(docs[0]){ + if(docs[0].type == "suggested"){ + dont_increment = false; + } + db.collection(channel_name).remove({id:video_id}, function(err, docs){ + io.to(channel_name).emit("channel", {type:"deleted", value: video_id}); + if(dont_increment) { + db.collection("frontpage_lists").update({_id: channel_name, count: {$gt: 0}}, {$inc: {count: -1}, $set:{accessed: Functions.get_time()}}, {upsert: true}, function(err, docs){ + res.sendStatus(200); + return; + }); + } else { + res.sendStatus(200); + return; + } + }); + } + }); + }); +}); + +function cleanChannelName(channel_name) { + var coll = emojiStrip(channel_name).toLowerCase(); + coll = coll.replace("_", ""); + coll = encodeURIComponent(coll).replace(/\W/g, ''); + coll = filter.clean(coll); + return coll; +} + +function validateLogin(adminpass, userpass, channel_name, type, res, callback) { + db.collection(channel_name + "_settings").find({views: {$exists: true}}, function(err, conf) { + var exists = false; + if(conf.length > 0 && ((conf[0].userpass == undefined || conf[0].userpass == "" || conf[0].userpass == userpass))) { + exists = true; + } else if(conf.length > 0) { + res.sendStatus(403); + return; + } + if( + (type == "add" && ((conf[0].addsongs && (conf[0].adminpass == "" || conf[0].adminpass == undefined || conf[0].adminpass == adminpass)) || !conf[0].addsongs)) || + (type == "delete" && (conf[0].adminpass == "" || conf[0].adminpass == undefined || conf[0].adminpass == adminpass)) || + (type == "vote" && ((conf[0].vote && (conf[0].adminpass == "" || conf[0].adminpass == undefined || conf[0].adminpass == adminpass)) || !conf[0].vote)) || + (type == "config" && (conf[0].adminpass == "" || conf[0].adminpass == undefined || conf[0].adminpass == adminpass)) + ) { + callback(exists); + } else { + res.sendStatus(403); + return; + } + }); +} + +router.route('/api/conf/:channel_name').put(function(req, res) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + if(!req.body.hasOwnProperty('adminpass') || !req.body.hasOwnProperty('userpass') || + !req.params.hasOwnProperty('channel_name') || !req.body.hasOwnProperty('voting') || + !req.body.hasOwnProperty('addsongs') || !req.body.hasOwnProperty('longsongs') || + !req.body.hasOwnProperty('frontpage') || !req.body.hasOwnProperty('allvideos') || + !req.body.hasOwnProperty('skipping') || !req.body.hasOwnProperty('shuffling') || + !req.body.hasOwnProperty('userpass_changed')) { + res.sendStatus(400); + return; + } + try { + var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; + var guid = Functions.hash_pass(req.get('User-Agent') + ip + req.headers["accept-language"]); + var adminpass = req.body.adminpass == "" ? "" : Functions.hash_pass(crypto.createHash('sha256').update(req.body.adminpass, 'utf8').digest("hex")); + req.body.userpass = crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex"); + var userpass = req.body.userpass; + var voting = req.body.voting; + var addsongs = req.body.addsongs; + var longsongs = req.body.longsongs; + var frontpage = req.body.frontpage; + var allvideos = req.body.allvideos; + var removeplay = req.body.removeplay; + var skipping = req.body.skipping; + var shuffling = req.body.shuffling; + var userpass_changed = req.body.userpass_changed; + var channel_name = cleanChannelName(req.params.channel_name); + if(typeof(userpass) != "string" || typeof(adminpass) != "string" || + typeof(voting) != "boolean" || typeof(addsongs) != "boolean" || + typeof(longsongs) != "boolean" || typeof(frontpage) != "boolean" || + typeof(allvideos) != "boolean" || typeof(removeplay) != "boolean" || + typeof(skipping) != "boolean" || typeof(shuffling) != "boolean" || + typeof(userpass_changed) != "boolean") { + throw "Wrong format"; + } + } catch(e) { + res.send(e); + res.sendStatus(400); + return; + } + + validateLogin(adminpass, userpass, channel_name, "config", res, function(exists) { + if(!exists) { + res.sendStatus(404); + return; + } + + if((!userpass_changed && frontpage) || (userpass_changed && userpass == "")) { + userpass = ""; + } else if(userpass_changed && userpass != "") { + frontpage = false; + } + var description = ""; + + var obj = { + addsongs:addsongs, + allvideos:allvideos, + frontpage:frontpage, + skip:skipping, + vote:voting, + removeplay:removeplay, + shuffle:shuffling, + longsongs:longsongs, + adminpass:adminpass, + desc: description, + }; + if(userpass_changed) { + obj["userpass"] = userpass; + } else if (frontpage) { + obj["userpass"] = ""; + } + db.collection(channel_name + "_settings").update({views:{$exists:true}}, { + $set:obj + }, function(err, docs){ + + if(obj.adminpass !== "") obj.adminpass = true; + if(obj.hasOwnProperty("userpass") && obj.userpass != "") obj.userpass = true; + else obj.userpass = false; + io.to(channel_name).emit("conf", [obj]); + + db.collection("frontpage_lists").update({_id: channel_name}, {$set:{ + frontpage:frontpage, accessed: Functions.get_time()} + }, + {upsert:true}, function(err, docs){ + res.header({'Content-Type': 'application/json'}); + res.status(200).send(JSON.stringify(obj)); + }); + }); + }); +}) + +router.route('/api/list/:channel_name/:video_id').put(function(req,res) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + if(!req.body.hasOwnProperty('adminpass') || !req.body.hasOwnProperty('userpass') || + !req.params.hasOwnProperty('channel_name') || !req.params.hasOwnProperty('video_id')) { + res.sendStatus(400); + return; + } + try { + var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; + var guid = Functions.hash_pass(req.get('User-Agent') + ip + req.headers["accept-language"]); + var adminpass = req.body.adminpass == "" ? "" : Functions.hash_pass(crypto.createHash('sha256').update(req.body.adminpass, 'utf8').digest("hex")); + req.body.userpass = crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex"); + var userpass = req.body.userpass; + var channel_name = cleanChannelName(req.params.channel_name); + var video_id = req.params.video_id; + if(typeof(userpass) != "string" || typeof(adminpass) != "string") { + throw "Wrong format"; + } + } catch(e) { + res.send(e); + res.sendStatus(400); + return; + } + + validateLogin(adminpass, userpass, channel_name, "vote", res, function(exists) { + if(!exists) { + res.sendStatus(404); + return; + } + db.collection(channel_name).find({id: video_id, now_playing: false}, function(err, song) { + if(song.length == 0) { + res.sendStatus(404); + return; + } else if(song[0].guids.indexOf(guid) > -1) { + res.sendStatus(409); + return; + } else { + song[0].votes += 1; + song[0].guids.push(guid); + db.collection(channel_name).update({id: video_id}, {$inc:{votes:1}, $set:{added:Functions.get_time()}, $push :{guids: guid}}, function(err, success) { + io.to(channel_name).emit("channel", {type: "vote", value: video_id, time: Functions.get_time()}); + List.getNextSong(channel_name, function() { + res.header({'Content-Type': 'application/json'}); + res.status(200).send(JSON.stringify(song[0])); + return; + }); + }); + } + }) + }); +}); + +router.route('/api/list/:channel_name/:video_id').post(function(req,res) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + if(!req.body.hasOwnProperty('adminpass') || !req.body.hasOwnProperty('userpass') || + !req.params.hasOwnProperty('channel_name') || !req.params.hasOwnProperty('video_id') || + !req.body.hasOwnProperty('duration') || !req.body.hasOwnProperty('start_time') || + !req.body.hasOwnProperty('end_time') || !req.body.hasOwnProperty('title')) { + res.sendStatus(400); + return; + } + try { + var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; + var guid = Functions.hash_pass(req.get('User-Agent') + ip + req.headers["accept-language"]); + var adminpass = req.body.adminpass == "" ? "" : Functions.hash_pass(crypto.createHash('sha256').update(req.body.adminpass, 'utf8').digest("hex")); + req.body.userpass = crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex"); + var userpass = req.body.userpass; + var channel_name = cleanChannelName(req.params.channel_name); + var video_id = req.params.video_id; + var duration = parseInt(req.body.duration); + var start_time = parseInt(req.body.start_time); + var end_time = parseInt(req.body.end_time); + if(duration != end_time - start_time) duration = end_time - start_time; + var title = req.body.title; + if(typeof(userpass) != "string" || typeof(adminpass) != "string" || + typeof(title) != "string") { + throw "Wrong format"; + } + } catch(e) { + res.send(e); + res.sendStatus(400); + return; + } + + validateLogin(adminpass, userpass, channel_name, "add", res, function(exists) { + db.collection(channel_name).find({id: video_id}, function(err, result) { + if(result.length == 0) { + db.collection(channel_name).find({now_playing: true}, function(err, now_playing) { + var set_np = false; + if(now_playing.length == 0) { + set_np = true; + } + var new_song = {"added": Functions.get_time(),"guids":[guid],"id":video_id,"now_playing":set_np,"title":title,"votes":1, "duration":duration, "start": parseInt(start_time), "end": parseInt(end_time)}; + db.collection("frontpage_lists").find({"_id": channel_name}, function(err, count) { + var create_frontpage_lists = false; + if(count.length == 0) { + create_frontpage_lists = true; + } + if(!exists) { + var configs = {"addsongs":false, "adminpass":"", "allvideos":true, "frontpage":true, "longsongs":false, "removeplay": false, "shuffle": true, "skip": false, "skips": [], "startTime":Functions.get_time(), "views": [], "vote": false, "desc": ""}; + db.collection(channel_name + "_settings").insert(configs, function(err, docs){ + io.to(channel_name).emit("conf", configs); + }); + } + db.collection(channel_name).insert(new_song, function(err, success) { + if(create_frontpage_lists) { + db.collection("frontpage_lists").insert({"_id": channel_name, "count" : 1, "frontpage": true, "accessed": Functions.get_time(), "viewers": 1}, function(err, docs) { + io.to(channel_name).emit("conf", configs); + io.to(channel_name).emit("channel", {type: "added", value: new_song}); + List.getNextSong(channel_name, function() { + res.header({'Content-Type': 'application/json'}); + res.status(200).send(JSON.stringify(new_song)); + return; + }); + }); + } else if(set_np) { + Frontpage.update_frontpage(channel_name, video_id, title, function() { + io.to(channel_name).emit("np", new_song); + List.getNextSong(channel_name, function() { + res.header({'Content-Type': 'application/json'}); + res.status(200).send(JSON.stringify(new_song)); + return; + }); + }); + } else { + db.collection("frontpage_lists").update({"_id": channel_name}, {$inc: {count: 1}}, function(err, docs) { + io.to(channel_name).emit("channel", {type: "added", value: new_song}); + List.getNextSong(channel_name, function() { + res.header({'Content-Type': 'application/json'}); + res.status(200).send(JSON.stringify(new_song)); + return; + }); + }); + } + }); + }) + }) + } else { + res.sendStatus(409); + return; + } + }); + }); +}); + router.route('/api/list/:channel_name').get(function(req, res) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");