From 92a7c88e4266b1f00d53a6265a4f6e5c3bc39e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Rynning-T=C3=B8nnesen?= Date: Thu, 1 Mar 2018 19:11:20 +0100 Subject: [PATCH] More RESTApi explanation in README and more endpoints --- server/README.md | 98 +++++- server/handlers/search.js | 8 +- server/routing/client/api.js | 564 ++++++++++++++++++++++------------- 3 files changed, 454 insertions(+), 216 deletions(-) diff --git a/server/README.md b/server/README.md index 1534ecff..017b7234 100644 --- a/server/README.md +++ b/server/README.md @@ -4,7 +4,7 @@ Under ``` /server/apps/ ```, there are two files, ``` admin.js ``` and ``` clien ## REST -All PUT, DELETE and POST endpoints have a 2-second waitlimit for each command per client. You'll get a response with Retry-After header for how long you have to wait. Shuffling in a player has a 5-second waitlimit, but per channel instead of per client. +All PUT, DELETE and POST endpoints have a 2-second waitlimit for each command per client. You'll get a response with Retry-After header for how long you have to wait. Shuffling in a player has a 5-second waitlimit, but per channel instead of per client. Add song @@ -19,6 +19,7 @@ POST /api/list/:channel_name/:video_id "userpass": USER_PASSWORD } +Returns 400 for bad request Returns 403 for bad authentication Returns 409 if the song exists Returns 429 if you're doing too much of this request, with a Retry-After int value in the header. @@ -33,8 +34,9 @@ DELETE /api/list/:channel_name/:video_id "userpass": USER_PASSWORD } +Returns 400 for bad request Returns 403 for bad authentication -Returns 404 if the song doesnt exist +Returns 404 if the song doesnt exist or is the currently playing song Returns 429 if you're doing too much of this request, with a Retry-After int value in the header. Returns 200 if successful ``` @@ -47,6 +49,7 @@ PUT /api/list/:channel_name/:video_id "userpass": USER_PASSWORD } +Returns 400 for bad request Returns 403 for bad authentication Returns 404 if the song doesnt exist Returns 409 if you've already voted on that song @@ -71,12 +74,103 @@ PUT /api/conf/:channel_name "userpass_changed": BOOLEAN (this must be true if you want to keep the userpassword you're sending) } +Returns 400 for bad request Returns 403 for bad authentication Returns 404 if the list doesn't exist Returns 429 if you're doing too much of this request, with a Retry-After int value in the header. Returns 200 and the newly added configuration if successful ``` +Get channelsettings +``` +GET /api/conf/:channel_name/ + +Returns 403 for bad authentication (if you get this, try POST with userpassword attached) +Returns 404 if the channel doesn't exist +Returns 200 and the settings-object +``` + +Get channelsettings (protected) +``` +POST /api/conf/:channel_name/ + { + "userpass": USERPASS + } + +Returns 400 for bad request +Returns 403 for bad authentication +Returns 404 if the channel doesn't exist +Returns 200 and the settings-object +``` + +Get song in channel +``` +GET /api/list/:channel_name/ + +Returns 403 for bad authentication (if you get this, the channel is protected, try getting the full channel with POST, and search through the object) +Returns 404 if the song doesn't exist +Returns 200 and the song +``` + +Get song in channel (protected) +``` +// Important fetch_song is present, or else the request will try to add a song to the channel +POST /api/list/:channel_name/ + { + "fetch_song": ANYTHING_HERE, + "userpass": USERPASS + } + +Returns 403 for bad authentication +Returns 404 if the song doesn't exist +Returns 200 and the song +``` + +Get channelsettings +``` +GET /api/list/:channel_name/ + +Returns 403 for bad authentication (if you get this, try POST with userpassword attached) +Returns 404 if the channel doesn't exist +Returns 200 and the objects in the channel +``` + +Get channelsettings (protected) +``` +POST /api/list/:channel_name/ + { + "userpass": USERPASS + } + +Returns 400 for bad request +Returns 403 for bad authentication +Returns 404 if the channel doesn't exist +Returns 200 and the objects in the channel +``` + +Get now playing song +``` +GET /api/list/:channel_name/__np__ + +Returns 400 for bad request +Returns 403 for bad authentication (if you get this, try POST with userpassword attached) +Returns 404 if the channel doesn't exist +Returns 200 and the now playing object +``` + +Get now playing song (protected) +``` +POST /api/list/:channel_name/__np__ + { + "userpass": USERPASS + } + +Returns 400 for bad request +Returns 403 for bad authentication (if you get this, try POST with userpassword attached) +Returns 404 if the channel doesn't exist +Returns 200 and the now playing object +``` + Still to come: SKIP and SHUFFLE RESTApi calls.. diff --git a/server/handlers/search.js b/server/handlers/search.js index fe601d9d..8d90d42a 100644 --- a/server/handlers/search.js +++ b/server/handlers/search.js @@ -8,12 +8,12 @@ try { process.exit(); } -function get_correct_info(song_generated, channel, broadcast) { +function get_correct_info(song_generated, channel, broadcast, callback) { request({ type: "GET", url: "https://www.googleapis.com/youtube/v3/videos?part=contentDetails,snippet,id&key="+key+"&id=" + song_generated.id, - }, function(error, response, body, callback) { + }, function(error, response, body) { try { var resp = JSON.parse(body); if(resp.items.length == 1) { @@ -39,9 +39,11 @@ function get_correct_info(song_generated, channel, broadcast) { if(broadcast && docs.nModified == 1) { song_generated.new_id = song_generated.id; io.to(channel).emit("channel", {type: "changed_values", value: song_generated}); - if(callback) { + if(typeof(callback) == "function") { callback(); } + } else { + callback(); } }); } diff --git a/server/routing/client/api.js b/server/routing/client/api.js index 34d4e64f..dcde6934 100644 --- a/server/routing/client/api.js +++ b/server/routing/client/api.js @@ -3,6 +3,32 @@ var router = express.Router(); var path = require('path'); var mongojs = require('mongojs'); var ObjectId = mongojs.ObjectId; +var toShowChannel = { + start: 1, + end: 1, + added: 1, + id: 1, + title: 1, + votes: 1, + duration: 1, + type: 1, + _id: 0, + now_playing: 1, +}; +var toShowConfig = { + addsongs: 1, + adminpass: 1, + allvideos: 1, + frontpage: 1, + longsongs: 1, + removeplay: 1, + shuffle: 1, + skip: 1, + startTime: 1, + userpass: 1, + vote: 1, + _id: 0 +}; router.use(function(req, res, next) { next(); // make sure we go to the next routes and don't stop here @@ -17,7 +43,8 @@ router.route('/api/frontpages').get(function(req, res) { db.collection("frontpage_lists").find({frontpage: true, count: {$gt: 0}}, function(err, docs) { db.collection("connected_users").find({"_id": "total_users"}, function(err, tot) { res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify({channels: docs, viewers: tot[0].total_users.length})); + res.status(200).send(JSON.stringify({channels: docs, viewers: tot[0].total_users.length})); + return; }); }); }); @@ -38,7 +65,7 @@ router.route('/api/list/:channel_name/:video_id').delete(function(req, res) { 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"); + req.body.userpass = 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; @@ -50,21 +77,7 @@ router.route('/api/list/:channel_name/:video_id').delete(function(req, res) { return; } - db.collection("timeout_api").find({ - type: "DELETE", - guid: guid, - }, function(err, docs) { - if(docs.length > 0) { - var date = new Date(docs[0].createdAt); - date.setSeconds(date.getSeconds() + 2); - var now = new Date(); - var retry_in = (date.getTime() - now.getTime()) / 1000; - if(retry_in > 0) { - res.header({'Retry-After': retry_in}); - res.sendStatus(429); - return; - } - } + checkTimeout(guid, res, "DELETE", function() { validateLogin(adminpass, userpass, channel_name, "delete", res, function(exists) { if(!exists) { res.sendStatus(404); @@ -84,25 +97,13 @@ router.route('/api/list/:channel_name/:video_id').delete(function(req, res) { 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){ - db.collection("timeout_api").update({type: "DELETE", guid: guid}, { - $set: { - "createdAt": new Date(), - type: "DELETE", - guid: guid, - }, - }, {upsert: true}, function(err, docs) { + updateTimeout(guid, res, "DELETE", function(err, docs) { res.sendStatus(200); return; }); }); } else { - db.collection("timeout_api").update({type: "DELETE", guid: guid}, { - $set: { - "createdAt": new Date(), - type: "DELETE", - guid: guid, - }, - }, {upsert: true}, function(err, docs) { + updateTimeout(guid, res, "DELETE", function(err, docs) { res.sendStatus(200); return; }); @@ -114,45 +115,14 @@ router.route('/api/list/:channel_name/:video_id').delete(function(req, res) { }); }); -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.params.hasOwnProperty('channel_name') || !req.body.hasOwnProperty('vote') || !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('skip') || !req.body.hasOwnProperty('shuffle') || !req.body.hasOwnProperty('userpass_changed')) { res.sendStatus(400); return; @@ -161,16 +131,16 @@ router.route('/api/conf/:channel_name').put(function(req, res) { 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"); + req.body.userpass = req.body.userpass == "" ? "" : crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex"); var userpass = req.body.userpass; - var voting = req.body.voting; + var voting = req.body.vote; 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 skipping = req.body.skip; + var shuffling = req.body.shuffle; var userpass_changed = req.body.userpass_changed; var channel_name = cleanChannelName(req.params.channel_name); if(typeof(userpass) != "string" || typeof(adminpass) != "string" || @@ -182,27 +152,12 @@ router.route('/api/conf/:channel_name').put(function(req, res) { throw "Wrong format"; } } catch(e) { - res.send(e); res.sendStatus(400); return; } - db.collection("timeout_api").find({ - type: "CONFIG", - guid: guid, - }, function(err, docs) { - if(docs.length > 0) { - var date = new Date(docs[0].createdAt); - date.setSeconds(date.getSeconds() + 2); - var now = new Date(); - var retry_in = (date.getTime() - now.getTime()) / 1000; - if(retry_in > 0) { - res.header({'Retry-After': retry_in}); - res.sendStatus(429); - return; - } - } - validateLogin(adminpass, userpass, channel_name, "config", res, function(exists) { - if(!exists) { + checkTimeout(guid, res, "CONFIG", function() { + validateLogin(adminpass, userpass, channel_name, "config", res, function(exists, conf) { + if(!exists && conf.length == 0) { res.sendStatus(404); return; } @@ -244,15 +199,10 @@ router.route('/api/conf/:channel_name').put(function(req, res) { frontpage:frontpage, accessed: Functions.get_time()} }, {upsert:true}, function(err, docs){ - db.collection("timeout_api").update({type: "CONFIG", guid: guid}, { - $set: { - "createdAt": new Date(), - type: "CONFIG", - guid: guid, - }, - }, {upsert: true}, function(err, docs) { + updateTimeout(guid, res, "CONFIG", function(err, docs) { res.header({'Content-Type': 'application/json'}); res.status(200).send(JSON.stringify(obj)); + return; }); }); }); @@ -273,7 +223,7 @@ router.route('/api/list/:channel_name/:video_id').put(function(req,res) { 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"); + req.body.userpass = 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; @@ -281,27 +231,11 @@ router.route('/api/list/:channel_name/:video_id').put(function(req,res) { throw "Wrong format"; } } catch(e) { - res.send(e); res.sendStatus(400); return; } - db.collection("timeout_api").find({ - type: "PUT", - guid: guid, - }, function(err, docs) { - if(docs.length > 0) { - var date = new Date(docs[0].createdAt); - date.setSeconds(date.getSeconds() + 2); - var now = new Date(); - var retry_in = (date.getTime() - now.getTime()) / 1000; - if(retry_in > 0) { - res.header({'Retry-After': retry_in}); - res.sendStatus(429); - return; - } - } - + checkTimeout(guid, res, "PUT", function() { validateLogin(adminpass, userpass, channel_name, "vote", res, function(exists) { if(!exists) { res.sendStatus(404); @@ -320,13 +254,7 @@ router.route('/api/list/:channel_name/:video_id').put(function(req,res) { 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() { - db.collection("timeout_api").update({type: "PUT", guid: guid}, { - $set: { - "createdAt": new Date(), - type: "PUT", - guid: guid, - }, - }, {upsert: true}, function(err, docs) { + updateTimeout(guid, res, "PUT", function(err, docs) { res.header({'Content-Type': 'application/json'}); res.status(200).send(JSON.stringify(song[0])); return; @@ -339,58 +267,98 @@ router.route('/api/list/:channel_name/:video_id').put(function(req,res) { }); }); +router.route('/api/list/:channel_name/__np__').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('userpass')) { + res.sendStatus(400); + return; + } + + 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 channel_name = req.params.channel_name; + req.body.userpass = req.body.userpass == "" ? "" : crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex"); + var userpass = req.body.userpass; + + if(typeof(userpass) != "string") { + res.sendStatus(400); + return; + } + + checkTimeout(guid, res, "POST", function() { + db.collection(channel_name).find({now_playing: true}, toShowChannel, function(err, list) { + if(list.length > 0) { + db.collection(channel_name + "_settings").find({views: {$exists: true}}, function(err, conf) { + if(conf.length == 0) { + res.sendStatus(404); + return; + } else if(conf[0].userpass != userpass && conf[0].userpass != "") { + res.sendStatus(403); + return; + } + updateTimeout(guid, res, "POST", function(err, docs) { + res.setHeader('Content-Type', 'application/json'); + res.status(200).send(JSON.stringify(list[0])); + }); + }); + } else { + res.sendStatus(404); + } + }); + }); +}); + 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') || + + var fetch_only = false; + if(req.body.hasOwnProperty('fetch_song')) { + fetch_only = true; + } + if(!fetch_only && (!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')) { + !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"); + req.body.userpass = 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"; - } + if(!fetch_only) { + var adminpass = req.body.adminpass == "" ? "" : Functions.hash_pass(crypto.createHash('sha256').update(req.body.adminpass, 'utf8').digest("hex")); + 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); + console.log("crash here", e); res.sendStatus(400); return; } - db.collection("timeout_api").find({ - type: "POST", - guid: guid, - }, function(err, docs) { - if(docs.length > 0) { - var date = new Date(docs[0].createdAt); - date.setSeconds(date.getSeconds() + 2); - var now = new Date(); - var retry_in = (date.getTime() - now.getTime()) / 1000; - if(retry_in > 0) { - res.header({'Retry-After': retry_in}); - res.sendStatus(429); - return; - } - } - - validateLogin(adminpass, userpass, channel_name, "add", res, function(exists) { + checkTimeout(guid, res, "POST", function() { + var type = fetch_only ? "fetch_song" : "add"; + validateLogin(adminpass, userpass, channel_name, type, res, function(exists) { db.collection(channel_name).find({id: video_id}, function(err, result) { if(result.length == 0) { + if(fetch_only) { + res.sendStatus(404); + return; + } db.collection(channel_name).find({now_playing: true}, function(err, now_playing) { var set_np = false; if(now_playing.length == 0) { @@ -427,6 +395,10 @@ router.route('/api/list/:channel_name/:video_id').post(function(req,res) { }); }) }) + } else if(fetch_only) { + res.setHeader('Content-Type', 'application/json'); + res.status(200).send(JSON.stringify(result[0])); + return; } else { res.sendStatus(409); return; @@ -436,72 +408,68 @@ router.route('/api/list/:channel_name/:video_id').post(function(req,res) { }); }); -function postEnd(channel_name, configs, new_song, guid, res) { - if(configs != undefined) { - io.to(channel_name).emit("conf", configs); - } - List.getNextSong(channel_name, function() { - db.collection("timeout_api").update({type: "POST", guid: guid}, { - $set: { - "createdAt": new Date(), - type: "POST", - guid: guid, - }, - }, {upsert: true}, function(err, docs) { - Search.get_correct_info(new_song, channel_name, !new_song.now_playing, function() { - res.header({'Content-Type': 'application/json'}); - res.status(200).send(JSON.stringify(new_song)); - 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"); var channel_name = req.params.channel_name; - db.collection(channel_name).find({views: {$exists: false}}, { - start: 1, - end: 1, - added: 1, - id: 1, - title: 1, - votes: 1, - duration: 1, - type: 1, - _id: 0, - now_playing: 1, - }, function(err, docs) { + db.collection(channel_name).find({views: {$exists: false}}, toShowChannel, function(err, docs) { if(docs.length > 0) { - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(docs)); + db.collection(channel_name + "_settings").find({views: {$exists: true}}, function(err, conf) { + if(conf.length == 0) { + res.sendStatus(404); + return; + } else if(conf[0].userpass != "" && conf[0].userpass != undefined) { + res.sendStatus(403); + return; + } + res.setHeader('Content-Type', 'application/json'); + res.status(200).send(JSON.stringify(docs)); + }); } else { - /*res.status(404); - res.send(404);*/ - res.status(404).redirect("/404"); + res.sendStatus(404); + } + }); +}); + +router.route('/api/list/:channel_name/:video_id').get(function(req, res) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + + var channel_name = req.params.channel_name; + var video_id = req.params.video_id; + var searchQuery = {id: video_id}; + if(video_id == "__np__") { + searchQuery = {now_playing: true}; + } + db.collection(channel_name).find(searchQuery, toShowChannel, function(err, docs) { + if(docs.length > 0) { + db.collection(channel_name + "_settings").find({views: {$exists: true}}, function(err, conf) { + if(conf.length == 0) { + res.sendStatus(404); + return; + } else if(conf[0].userpass != "" && conf[0].userpass != undefined) { + res.sendStatus(403); + return; + } + res.setHeader('Content-Type', 'application/json'); + res.status(200).send(JSON.stringify(docs[0])); + return; + }); + } else { + res.sendStatus(404); + return; } }); }); router.route('/api/conf/: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"); + var channel_name = req.params.channel_name; - db.collection(channel_name + "_settings").find({views: {$exists: true}}, { - addsongs: 1, - adminpass: 1, - allvideos: 1, - frontpage: 1, - longsongs: 1, - removeplay: 1, - shuffle: 1, - skip: 1, - startTime: 1, - userpass: 1, - vote: 1, - _id: 0 - }, function(err, docs) { - if(docs.length > 0) { + db.collection(channel_name + "_settings").find({views: {$exists: true}}, toShowConfig, function(err, docs) { + if(docs.length > 0 && docs[0].userpass == "" || docs[0].userpass == undefined) { var conf = docs[0]; if(conf.adminpass != "") { conf.adminpass = true; @@ -514,22 +482,116 @@ router.route('/api/conf/:channel_name').get(function(req, res) { conf.userpass = false; } res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(conf)); + res.status(200).send(JSON.stringify(conf)); + } else if(docs.length > 0 && docs[0].userpass != "" && docs[0].userpass != undefined){ + res.sendStatus(403); + return; } else { - /*res.status(404); - res.send(404);*/ - res.status(404).redirect("/404"); + res.sendStatus(404); + return; } }); }); +router.route('/api/conf/:channel_name').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('userpass')) { + res.sendStatus(400); + return; + } + 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 channel_name = req.params.channel_name; + req.body.userpass = req.body.userpass == "" ? "" : crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex"); + var userpass = req.body.userpass; + + if(typeof(userpass) != "string") { + res.sendStatus(400); + return; + } + + checkTimeout(guid, res, "POST", function() { + db.collection(channel_name + "_settings").find({views: {$exists: true}}, toShowConfig, function(err, docs) { + if(docs.length > 0 && docs[0].userpass == userpass) { + var conf = docs[0]; + if(conf.adminpass != "") { + conf.adminpass = true; + } else { + conf.adminpass = false; + } + if(conf.userpass != "") { + conf.userpass = true; + } else { + conf.userpass = false; + } + updateTimeout(guid, res, "POST", function(err, docs) { + res.setHeader('Content-Type', 'application/json'); + res.status(200).send(JSON.stringify(conf)); + }); + } else if(docs.length > 0 && docs[0].userpass != userpass) { + res.sendStatus(403); + return; + } else { + res.sendStatus(404); + return; + } + }); + }); +}); + +router.route('/api/list/:channel_name').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('userpass')) { + res.sendStatus(400); + return; + } + + 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 channel_name = req.params.channel_name; + req.body.userpass = req.body.userpass == "" ? "" : crypto.createHash('sha256').update(req.body.userpass, 'utf8').digest("hex"); + var userpass = req.body.userpass; + + if(typeof(userpass) != "string") { + res.sendStatus(400); + return; + } + + checkTimeout(guid, res, "POST", function() { + db.collection(channel_name).find({views: {$exists: false}}, toShowChannel, function(err, list) { + if(list.length > 0) { + db.collection(channel_name + "_settings").find({views: {$exists: true}}, function(err, conf) { + if(conf.length == 0) { + res.sendStatus(404); + return; + } else if(conf[0].userpass != userpass && conf[0].userpass != "") { + res.sendStatus(403); + return; + } + updateTimeout(guid, res, "POST", function(err, docs) { + res.setHeader('Content-Type', 'application/json'); + res.status(200).send(JSON.stringify(list)); + }); + }); + } else { + res.sendStatus(404); + } + }); + }); +}); + router.route('/api/imageblob').post(function(req, res) { var Jimp = require("jimp"); Jimp.read('https://img.youtube.com/vi/' + req.body.id + '/mqdefault.jpg', function (err, image) { if (err) console.log(err); image.blur(50) .write(path.join(pathThumbnails, '/public/assets/images/thumbnails/' + req.body.id + '.jpg'), function(e, r) { - res.send(req.body.id + ".jpg"); + res.status(200).send(req.body.id + ".jpg"); + return; }); }); }); @@ -568,17 +630,17 @@ try { } transporter.sendMail(msg, (error, info) => { if (error) { - res.send("failed"); + res.status(500).send("failed"); transporter.close(); return; } - res.send("success"); + res.status(200).send("success"); transporter.close(); }); } }); } else { - res.send("failed"); + res.status(500).send("failed"); return; } }); @@ -587,9 +649,89 @@ try { console.log("Seems you forgot to create a mailconfig.js in /server/config/. Have a look at the mailconfig.example.js."); router.route('/api/mail').post(function(req, res) { console.log("Someone tried to send a mail, but the mailsystem hasn't been enabled..") - res.send("failed"); + res.status(500).send("failed"); return; }); } +function updateTimeout(guid, res, type, callback) { + db.collection("timeout_api").update({type: "DELETE", guid: guid}, { + $set: { + "createdAt": new Date(), + type: "DELETE", + guid: guid, + }, + }, {upsert: true}, function(err, docs) { + callback(err, docs); + }); +} + +function checkTimeout(guid, res, type, callback) { + db.collection("timeout_api").find({ + type: type, + guid: guid, + }, function(err, docs) { + if(docs.length > 0) { + var date = new Date(docs[0].createdAt); + date.setSeconds(date.getSeconds() + 2); + var now = new Date(); + var retry_in = (date.getTime() - now.getTime()) / 1000; + if(retry_in > 0) { + res.header({'Retry-After': retry_in}); + res.sendStatus(429); + return; + } + } + callback(); + }); +} + +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 && type != "config") { + res.sendStatus(403); + return; + } + + if( + (type == "fetch_song") || + (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, conf); + } else { + res.sendStatus(403); + return; + } + }); +} + +function postEnd(channel_name, configs, new_song, guid, res) { + if(configs != undefined) { + io.to(channel_name).emit("conf", configs); + } + List.getNextSong(channel_name, function() { + updateTimeout(guid, res, "POST", function(err, docs) { + Search.get_correct_info(new_song, channel_name, !new_song.now_playing, function() { + res.header({'Content-Type': 'application/json'}); + res.status(200).send(JSON.stringify(new_song)); + return; + }); + }); + }); +} + module.exports = router;