diff --git a/.gitignore b/.gitignore index 1ffdf0f4..1bd7019a 100755 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ server/config/cert_config.js server/config/recaptcha.js server/config/analytics.js server/config/google.js +server/config/allowed_api.js server/public/assets/dist/maps/ server/public/assets/dist/callback.min.js server/public/assets/dist/token.min.js diff --git a/server/apps/client.js b/server/apps/client.js index b394c9c1..8861f99f 100755 --- a/server/apps/client.js +++ b/server/apps/client.js @@ -87,9 +87,10 @@ app.socketIO = io; /* Globally needed "libraries" and files */ -var Functions = require(pathThumbnails + '/handlers/functions.js'); var router = require(pathThumbnails + '/routing/client/router.js'); -var api = require(pathThumbnails + '/routing/client/api.js'); +var api_file = require(pathThumbnails + '/routing/client/api.js'); +var api = api_file.router; +api_file.sIO = app.socketIO; var ico_router = require(pathThumbnails + '/routing/client/icons_routing.js'); app.get('/robots.txt', function (req, res) { diff --git a/server/config/allowed_api.example.js b/server/config/allowed_api.example.js new file mode 100644 index 00000000..981ec029 --- /dev/null +++ b/server/config/allowed_api.example.js @@ -0,0 +1,3 @@ +var key = [""]; + +module.exports = key; diff --git a/server/handlers/aggregates.js b/server/handlers/aggregates.js index bedbdae2..40e2e472 100644 --- a/server/handlers/aggregates.js +++ b/server/handlers/aggregates.js @@ -11,6 +11,8 @@ var toShowConfig = { "userpass": 1, "vote": 1, "toggleChat": { $ifNull: [ "$toggleChat", true ] }, + "strictSkip": { $ifNull: [ "$strictSkip", false ] }, + "strictSkipNumber": { $ifNull: [ "$strictSkipNumber", 10 ] }, "description": { $ifNull: [ "$description", "" ] }, "thumbnail": { $ifNull: [ "$thumbnail", "" ] }, "rules": { $ifNull: [ "$rules", "" ] }, diff --git a/server/handlers/functions.js b/server/handlers/functions.js index 7396bc05..da8ec299 100644 --- a/server/handlers/functions.js +++ b/server/handlers/functions.js @@ -438,7 +438,7 @@ function left_channel(coll, guid, short_id, in_list, socket, change, caller) { } -function checkTimeout(type, timeout, channel, guid, conf_pass, this_pass, socket, callback, error_message){ +function checkTimeout(type, timeout, channel, guid, conf_pass, this_pass, socket, callback, error_message, error_callback){ if(conf_pass != "" && conf_pass == this_pass) { callback(); return; @@ -454,7 +454,9 @@ function checkTimeout(type, timeout, channel, guid, conf_pass, this_pass, socket var retry_in = (date.getTime() - now.getTime()) / 1000; if(retry_in > 0) { - if(error_message) { + if(typeof(error_callback) == "function") { + error_callback(); + } else if(error_message) { var sOrNot = Math.ceil(retry_in) > 1 || Math.ceil(retry_in) == 0 ? "s" : ""; socket.emit("toast", error_message + Math.ceil(retry_in) + " second" + sOrNot + "."); } else { diff --git a/server/handlers/list.js b/server/handlers/list.js index 75bc7dd9..28d2040e 100644 --- a/server/handlers/list.js +++ b/server/handlers/list.js @@ -123,7 +123,7 @@ function list(msg, guid, coll, offline, socket) { } } -function skip(list, guid, coll, offline, socket) { +function skip(list, guid, coll, offline, socket, callback) { var socketid = socket.zoff_id; if(list !== undefined && list !== null && list !== "") @@ -198,15 +198,20 @@ function skip(list, guid, coll, offline, socket) { hash = adminpass; //db.collection(coll + "_settings").find(function(err, docs){ - + var strictSkip = false; + var strictSkipNumber = 10; + if(docs[0].strictSkip) strictSkip = docs[0].strictSkip; + if(docs[0].strictSkipNumber) strictSkipNumber = docs[0].strictSkipNumber; if(docs !== null && docs.length !== 0) { if(!docs[0].skip || (docs[0].adminpass == hash && docs[0].adminpass !== "") || error) { db.collection("frontpage_lists").find({"_id": coll}, function(err, frontpage_viewers){ - if((frontpage_viewers[0].viewers/2 <= docs[0].skips.length+1 && !Functions.contains(docs[0].skips, guid) && frontpage_viewers[0].viewers != 2) || - (frontpage_viewers[0].viewers == 2 && docs[0].skips.length+1 == 2 && !Functions.contains(docs[0].skips, guid)) || - (docs[0].adminpass == hash && docs[0].adminpass !== "" && docs[0].skip)) + if( + (strictSkip && ((docs[0].adminpass == hash && docs[0].adminpass !== "") || (docs[0].skips.length+1 >= strictSkipNumber))) || + (!strictSkip && ((frontpage_viewers[0].viewers/2 <= docs[0].skips.length+1 && !Functions.contains(docs[0].skips, guid) && frontpage_viewers[0].viewers != 2) || + (frontpage_viewers[0].viewers == 2 && docs[0].skips.length+1 == 2 && !Functions.contains(docs[0].skips, guid)) || + (docs[0].adminpass == hash && docs[0].adminpass !== "" && docs[0].skip)))) { Functions.checkTimeout("skip", 1, coll, coll, error, true, socket, function() { change_song(coll, error, video_id, docs); @@ -223,14 +228,27 @@ function skip(list, guid, coll, offline, socket) { } }); }, "The channel is skipping too often, please wait "); - }else if(!Functions.contains(docs[0].skips, guid)){ + } else if(!Functions.contains(docs[0].skips, guid)){ db.collection(coll + "_settings").update({ id: "config" }, {$push:{skips:guid}}, function(err, d){ - if(frontpage_viewers[0].viewers == 2) - to_skip = 1; - else - to_skip = (Math.ceil(frontpage_viewers[0].viewers/2) - docs[0].skips.length-1); + if(frontpage_viewers[0].viewers == 2 && !strictSkip) { + to_skip = 1; + } else if(strictSkip) { + to_skip = (strictSkipNumber) - docs[0].skips.length-1; + } else { + to_skip = (Math.ceil(frontpage_viewers[0].viewers/2) - docs[0].skips.length-1); + } socket.emit("toast", to_skip + " more are needed to skip!"); - socket.to(coll).emit('chat', {from: name, msg: " voted to skip"}); + db.collection("user_names").find({"guid": guid}, function(err, docs) { + if(docs.length == 1) { + db.collection("registered_users").find({"_id": docs[0].name}, function(err, n) { + var icon = false; + if(n.length > 0 && n[0].icon) { + icon = n[0].icon; + } + socket.to(coll).emit('chat', {from: docs[0].name, msg: " voted to skip"}); + }) + } + }); }); }else{ socket.emit("toast", "alreadyskip"); diff --git a/server/handlers/list_settings.js b/server/handlers/list_settings.js index 19638715..0b06e0e6 100644 --- a/server/handlers/list_settings.js +++ b/server/handlers/list_settings.js @@ -222,6 +222,14 @@ function conf_function(params, coll, guid, offline, socket) { if(params.hasOwnProperty("toggleChat") && docs[0].adminpass != "" && docs[0].adminpass != undefined && docs[0].adminpass == hash) { obj.toggleChat = params.toggleChat; } + if(params.hasOwnProperty("strictSkip") && docs[0].adminpass != "" && docs[0].adminpass != undefined && docs[0].adminpass == hash) { + obj.strictSkip = params.strictSkip; + } + if(params.hasOwnProperty("strictSkipNumber") && docs[0].adminpass != "" && docs[0].adminpass != undefined && docs[0].adminpass == hash) { + try { + obj.strictSkipNumber = parseInt(params.strictSkipNumber); + } catch(e) {} + } if(params.userpass_changed) { obj["userpass"] = userpass; } else if (frontpage) { diff --git a/server/public/assets/css/style.css b/server/public/assets/css/style.css index 99defd32..d50937a1 100755 --- a/server/public/assets/css/style.css +++ b/server/public/assets/css/style.css @@ -824,12 +824,20 @@ input[type=text]:focus:not([readonly]) + label, input[type=password]:focus:not([ width: 100%; } -#password{ +#password, #strict-input-number{ width: 84%; margin-left: 30px; margin-bottom: 0; } +#strict-input-number { + border-bottom: transparent; +} + +.strict-skip-info { + width: 1000px; +} + span.badge.new, .progress .determinate, .progress .indeterminate, @@ -2412,7 +2420,7 @@ nav ul li:hover, nav ul li.active { color: lightgrey; } -#admin-lock +#admin-lock, #strict-skip-lock { position:absolute; display: flex; diff --git a/server/public/assets/js/admin.js b/server/public/assets/js/admin.js index 0f1604fe..7a5cabab 100755 --- a/server/public/assets/js/admin.js +++ b/server/public/assets/js/admin.js @@ -3,6 +3,11 @@ var Admin = { beginning:true, logged_in: false, + update_strict_skip: function(value) { + var form = document.getElementById("adminSettingsForm"); + form.strictSkipNumber = value; + Admin.submitAdmin(form, false); + }, pw: function(msg) { Admin.logged_in = msg; @@ -16,12 +21,16 @@ var Admin = { if(Helper.html(".suggested-badge") != "0" && Helper.html(".suggested-badge") != "") { Helper.removeClass(".suggested-badge", "hide"); } + if(conf != undefined && conf.strictSkip) { + Helper.removeClass(".strict-skip-input", "hide"); + } } else { Admin.hideUserSuggested(); + Helper.addClass(".strict-skip-input", "hide"); } Helper.removeClass(".delete-context-menu", "context-menu-disabled"); names = ["vote","addsongs","longsongs","frontpage", "allvideos", - "removeplay", "skip", "shuffle", "userpass", "toggleChat"]; + "removeplay", "skip", "shuffle", "userpass", "toggleChat", "strictSkip"]; //Crypt.set_pass(chan.toLowerCase(), Crypt.tmp_pass); for (var i = 0; i < names.length; i++) { @@ -105,7 +114,7 @@ var Admin = { w_p = true; adminpass = ""; names = ["vote","addsongs","longsongs","frontpage", "allvideos", - "removeplay", "skip", "shuffle", "toggleChat"]; + "removeplay", "skip", "shuffle", "toggleChat", "strictSkip"]; document.getElementById("password").value = ""; Helper.addClass(".info_change_li", "hide"); for (i = 0; i < names.length; i++) { @@ -120,7 +129,7 @@ var Admin = { document.getElementById("admin-lock").innerHTML = "lock"; } - + Helper.addClass(".strict-skip-input", "hide"); Helper.addClass(".user-password-li", "hide"); Helper.addClass(".chat-toggle-li", "hide"); Helper.addClass(".delete-all", "hide"); @@ -138,7 +147,7 @@ var Admin = { }, save: function(userpass) { - Admin.submitAdmin(document.getElementById("adminForm").elements, userpass); + Admin.submitAdmin(document.getElementById("adminSettingsForm").elements, userpass); }, set_conf: function(conf_array) { @@ -146,7 +155,7 @@ var Admin = { music = conf_array.allvideos; longsongs = conf_array.longsongs; names = ["vote","addsongs","longsongs","frontpage", "allvideos", - "removeplay", "skip", "shuffle", "userpass", "toggleChat"]; + "removeplay", "skip", "shuffle", "userpass", "toggleChat", "strictSkip"]; if(!conf.hasOwnProperty("toggleChat")) conf.toggleChat = true; toggleChat = conf.toggleChat; hasadmin = conf_array.adminpass != ""; @@ -163,6 +172,7 @@ var Admin = { document.getElementsByName(names[i])[0].removeAttribute("disabled"); } } + document.getElementById("strict-input-number").value = conf.strictSkipNumber; if((hasadmin) && !Admin.logged_in) { if(Helper.html("#admin-lock") != "lock") Admin.display_logged_out(); } else if(!hasadmin) { @@ -172,7 +182,14 @@ var Admin = { Helper.removeClass(".change_user_pass", "hide"); } } - + if(Admin.logged_in) { + if(conf != undefined && conf.strictSkip) { + Helper.removeClass(".strict-skip-input", "hide"); + } + } + if(conf != undefined && !conf.strictSkip) { + Helper.addClass(".strict-skip-input", "hide"); + } if(!document.getElementsByClassName("password_protected")[0].checked) { Helper.addClass(".change_user_pass", "hide"); //Crypt.remove_userpass(chan.toLowerCase()); @@ -217,6 +234,13 @@ var Admin = { skipping = form.skip.checked; shuffling = form.shuffle.checked; toggleChat = form.toggleChat.checked; + strictSkip = form.strictSkip.checked; + + if(form.strictSkipNumber) { + strictSkipNumber = form.strictSkipNumber; + } else { + strictSkipNumber = conf.strictSkipNumber; + } var pass_send = userpass_changed && !form.userpass.checked ? "" : userpass; configs = { @@ -231,8 +255,10 @@ var Admin = { skipping: skipping, shuffling: shuffling, toggleChat: toggleChat, + strictSkip: strictSkip, userpass: Crypt.crypt_pass(pass_send), - userpass_changed: userpass_changed + userpass_changed: userpass_changed, + strictSkipNumber: strictSkipNumber }; emit("conf", configs); diff --git a/server/public/assets/js/listeners.js b/server/public/assets/js/listeners.js index 1da96410..fc45c9cf 100755 --- a/server/public/assets/js/listeners.js +++ b/server/public/assets/js/listeners.js @@ -1278,6 +1278,11 @@ function addDynamicListeners() { Admin.pass_save(); }); + addListener("submit", "#strictSkipForm", function(event){ + this.preventDefault(); + Admin.update_strict_skip(document.getElementById("strict-input-number").value); + }); + addListener("click", "#channel-share-modal", function(){ M.Modal.getInstance(document.getElementById("channel-share-modal")).close(); }); diff --git a/server/public/partials/channel/settings.handlebars b/server/public/partials/channel/settings.handlebars index fcb7f1d5..b059b7af 100644 --- a/server/public/partials/channel/settings.handlebars +++ b/server/public/partials/channel/settings.handlebars @@ -5,14 +5,16 @@
-
  • Vote @@ -51,7 +52,6 @@
  • -
  • Skip @@ -64,7 +64,6 @@
  • -
  • Song length @@ -77,7 +76,6 @@
  • -
  • Type @@ -114,6 +112,18 @@
  • +
  • + + Strict skip + +
    + +
    +
  • Chat @@ -139,7 +149,15 @@
  • - +
    +
  • +
    + queue_play_next + +
    votes needed to skip.
    +
    +
  • +
  • Change password
  • diff --git a/server/routing/client/api.js b/server/routing/client/api.js index 321171c9..edf27440 100644 --- a/server/routing/client/api.js +++ b/server/routing/client/api.js @@ -6,6 +6,7 @@ var ObjectId = mongojs.ObjectId; var token_db = mongojs("tokens"); var cookieParser = require("cookie-parser"); var db = require(pathThumbnails + '/handlers/db.js'); +var allowed_key = require(pathThumbnails + '/config/allowed_api.js'); var crypto = require('crypto'); var List = require(pathThumbnails + '/handlers/list.js'); var Functions = require(pathThumbnails + '/handlers/functions.js'); @@ -14,6 +15,11 @@ var Search = require(pathThumbnails + '/handlers/search.js'); var uniqid = require('uniqid'); var Filter = require('bad-words'); var filter = new Filter({ placeHolder: 'x'}); + +var _exports = { + router: router, + sIO: {} +} var projects = require(pathThumbnails + "/handlers/aggregates.js"); var error = { @@ -67,6 +73,24 @@ var error = { success: false, results: [], }, + settings: { + status: 409, + error: "The channel doesn't have strict skipping enabled.", + success: false, + results: [], + }, + already_skip: { + status: 206, + error: false, + success: true, + results: [], + }, + more_skip_needed: { + status: 202, + error: false, + success: true, + results: [], + }, no_error: { status: 200, error: false, @@ -271,6 +295,75 @@ router.route('/api/list/:channel_name/:video_id').delete(function(req, res) { }); }); +router.route('/api/skip/: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"); + res.header({"Content-Type": "application/json"}); + + var api_key = req.body.api_key; + var guid = req.body.chat_name; + var channel_name = cleanChannelName(req.params.channel_name); + var userpass = ""; + if(req.body.userpass && req.body.userpass != "") userpass = crypto.createHash('sha256').update(Functions.decrypt_string(req.body.userpass)).digest("base64"); + if(allowed_key.indexOf(api_key) > -1) { + db.collection(channel_name + "_settings").find({"id": "config"}, function(err, settings) { + if(settings.length == 0) { + res.status(404).send(error.not_found.list); + return; + } + settings = settings[0]; + if(!settings.strictSkip) { + res.status(409).send(error.settings); + return; + } + if(settings.userpass == "" || (settings.userpass == userpass)) { + if(settings.skips.length+1 >= settings.strictSkipNumber && !Functions.contains(settings.skips, guid)) { + Functions.checkTimeout("skip", 1, channel_name, channel_name, false, true, undefined, function() { + db.collection(channel_name).find({now_playing: true}, function(err, np) { + if(np.length != 1) { + res.status(404).send(error.not_found.list); + return; + } + List.change_song(channel_name, false, np[0].id, [settings], function() { + res.status(200).send(error.no_error); + return; + }); + _exports.sIO.to(channel_name).emit('chat', {from: guid, icon: false, msg: " skipped via API."}); + }); + }, "", function() { + res.status(429).send(error.tooMany); + return; + }); + } else if(!Functions.contains(settings.skips, guid)) { + db.collection(channel_name + "_settings").update({ id: "config" }, {$push:{skips:guid}}, function(err, d){ + var to_skip = (settings.strictSkipNumber) - settings.skips.length-1; + _exports.sIO.to(channel_name).emit('chat', {from: guid, msg: " voted to skip via API."}); + // VOTED TO SKIP + var to_send = error.more_skip_needed; + to_send.results = [to_skip] + res.status(202).send(to_send); + return; + }); + } else { + //ALREADY SKIP + res.status(206).send(error.already_skip); + return; + } + } else { + // NOT AUTHENTICATED + res.status(403).send(error.not_authenticated); + return; + } + }); + } else { + // WRONG API KEY + var toSend = error.not_authenticated; + toSend.status = 406; + res.status(406).send(toSend); + 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"); @@ -1492,4 +1585,4 @@ function postEnd(channel_name, configs, new_song, guid, res, authenticated, auth }); } -module.exports = router; +module.exports = _exports;