cuminmymeowth

Untitled

Jan 16th, 2016
95
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. function makeAlert(title, text, klass) {
  2.     if(!klass) {
  3.         klass = "alert-info";
  4.     }
  5.  
  6.     var wrap = $("<div/>").addClass("col-md-12");
  7.  
  8.     var al = $("<div/>").addClass("alert")
  9.         .addClass(klass)
  10.         .html(text)
  11.         .appendTo(wrap);
  12.     $("<br/>").prependTo(al);
  13.     $("<strong/>").text(title).prependTo(al);
  14.     $("<button/>").addClass("close pull-right").html("&times;")
  15.         .click(function() {
  16.             al.hide("fade", function() {
  17.                 wrap.remove();
  18.             });
  19.         })
  20.         .prependTo(al);
  21.     return wrap;
  22. }
  23.  
  24. function formatURL(data) {
  25.     switch(data.type) {
  26.         case "yt":
  27.             return "http://youtube.com/watch?v=" + data.id;
  28.         case "vi":
  29.             return "http://vimeo.com/" + data.id;
  30.         case "dm":
  31.             return "http://dailymotion.com/video/" + data.id;
  32.         case "sc":
  33.             return data.id;
  34.         case "li":
  35.             return "http://livestream.com/" + data.id;
  36.         case "tw":
  37.             return "http://twitch.tv/" + data.id;
  38.         case "rt":
  39.             return data.id;
  40.         case "jw":
  41.             return data.id;
  42.         case "im":
  43.             return "http://imgur.com/a/" + data.id;
  44.         case "us":
  45.             return "http://ustream.tv/" + data.id;
  46.         case "gd":
  47.             return "https://docs.google.com/file/d/" + data.id;
  48.         case "fi":
  49.             return data.id;
  50.         case "hb":
  51.             return "http://hitbox.tv/" + data.id;
  52.         default:
  53.             return "#";
  54.     }
  55. }
  56.  
  57. function findUserlistItem(name) {
  58.     var children = $("#userlist .userlist_item");
  59.     if(children.length == 0)
  60.         return null;
  61.     name = name.toLowerCase();
  62.     // WARNING: Incoming hax because of jQuery and bootstrap bullshit
  63.     var keys = Object.keys(children);
  64.     for(var k in keys) {
  65.         var i = keys[k];
  66.         if(isNaN(parseInt(i))) {
  67.             continue;
  68.         }
  69.         var child = children[i];
  70.         if($(child.children[1]).text().toLowerCase() == name)
  71.             return $(child);
  72.     }
  73.     return null;
  74. }
  75.  
  76. function formatUserlistItem(div) {
  77.     var data = {
  78.         name: div.data("name") || "",
  79.         rank: div.data("rank"),
  80.         profile: div.data("profile") || { image: "", text: ""},
  81.         leader: div.data("leader") || false,
  82.         icon: div.data("icon") || false,
  83.         afk: div.data("afk") || false
  84.     };
  85.     var name = $(div.children()[1]);
  86.     name.removeClass();
  87.     name.css("font-style", "");
  88.     name.addClass(getNameColor(data.rank));
  89.     div.find(".profile-box").remove();
  90.  
  91.     if (data.afk) {
  92.         div.addClass("userlist_afk");
  93.     } else {
  94.         div.removeClass("userlist_afk");
  95.     }
  96.  
  97.     if (div.data("meta") && div.data("meta").muted) {
  98.         div.addClass("userlist_muted");
  99.     } else {
  100.         div.removeClass("userlist_muted");
  101.     }
  102.  
  103.     if (div.data("meta") && div.data("meta").smuted) {
  104.         div.addClass("userlist_smuted");
  105.     } else {
  106.         div.removeClass("userlist_smuted");
  107.     }
  108.  
  109.     var profile = null;
  110.     name.mouseenter(function(ev) {
  111.         if (profile)
  112.             profile.remove();
  113.  
  114.         var top = ev.clientY + 5;
  115.         var horiz = ev.clientX;
  116.         profile = $("<div/>")
  117.             .addClass("profile-box linewrap")
  118.             .css("top", top + "px")
  119.             .appendTo(div);
  120.  
  121.         if(data.profile.image) {
  122.             $("<img/>").addClass("profile-image")
  123.                 .attr("src", data.profile.image)
  124.                 .appendTo(profile);
  125.         }
  126.         $("<strong/>").text(data.name).appendTo(profile);
  127.  
  128.         var meta = div.data("meta") || {};
  129.         if (meta.ip) {
  130.             $("<br/>").appendTo(profile);
  131.             $("<em/>").text(meta.ip).appendTo(profile);
  132.         }
  133.         if (meta.aliases) {
  134.             $("<br/>").appendTo(profile);
  135.             $("<em/>").text("aliases: " + meta.aliases.join(", ")).appendTo(profile);
  136.         }
  137.         $("<hr/>").css("margin-top", "5px").css("margin-bottom", "5px").appendTo(profile);
  138.         $("<p/>").text(data.profile.text).appendTo(profile);
  139.  
  140.         if ($("body").hasClass("synchtube")) horiz -= profile.outerWidth();
  141.         profile.css("left", horiz + "px")
  142.     });
  143.     name.mousemove(function(ev) {
  144.         var top = ev.clientY + 5;
  145.         var horiz = ev.clientX;
  146.  
  147.         if ($("body").hasClass("synchtube")) horiz -= profile.outerWidth();
  148.         profile.css("left", horiz + "px")
  149.             .css("top", top + "px");
  150.     });
  151.     name.mouseleave(function() {
  152.         profile.remove();
  153.     });
  154.     var icon = div.children()[0];
  155.     icon.innerHTML = "";
  156.     // denote current leader with a star
  157.     if(data.leader) {
  158.         $("<span/>").addClass("glyphicon glyphicon-star-empty").appendTo(icon);
  159.     }
  160.     if(data.afk) {
  161.         name.css("font-style", "italic");
  162.         $("<span/>").addClass("glyphicon glyphicon-time").appendTo(icon);
  163.     }
  164.     if (data.icon) {
  165.         $("<span/>").addClass("glyphicon " + data.icon).prependTo(icon);
  166.     }
  167. }
  168.  
  169. function getNameColor(rank) {
  170.     if(rank >= Rank.Siteadmin)
  171.         return "userlist_siteadmin";
  172.     else if(rank >= Rank.Admin)
  173.         return "userlist_owner";
  174.     else if(rank >= Rank.Moderator)
  175.         return "userlist_op";
  176.     else if(rank == Rank.Guest)
  177.         return "userlist_guest";
  178.     else
  179.         return "";
  180. }
  181.  
  182. function addUserDropdown(entry) {
  183.     var name = entry.data("name"),
  184.         rank = entry.data("rank"),
  185.         leader = entry.data("leader"),
  186.         meta = entry.data("meta") || {};
  187.     entry.find(".user-dropdown").remove();
  188.     var menu = $("<div/>")
  189.         .addClass("user-dropdown")
  190.         .appendTo(entry)
  191.         .hide();
  192.  
  193.     $("<strong/>").text(name).appendTo(menu);
  194.     $("<br/>").appendTo(menu);
  195.  
  196.     var btngroup = $("<div/>").addClass("btn-group-vertical").appendTo(menu);
  197.  
  198.     /* ignore button */
  199.     var ignore = $("<button/>").addClass("btn btn-xs btn-default")
  200.         .appendTo(btngroup)
  201.         .click(function () {
  202.             if(IGNORED.indexOf(name) == -1) {
  203.                 ignore.text("Unignore User");
  204.                 IGNORED.push(name);
  205.             } else {
  206.                 ignore.text("Ignore User");
  207.                 IGNORED.splice(IGNORED.indexOf(name), 1);
  208.             }
  209.         });
  210.     if(IGNORED.indexOf(name) == -1) {
  211.         ignore.text("Ignore User");
  212.     } else {
  213.         ignore.text("Unignore User");
  214.     }
  215.  
  216.     /* pm button */
  217.     if (name !== CLIENT.name) {
  218.         var pm = $("<button/>").addClass("btn btn-xs btn-default")
  219.             .text("Private Message")
  220.             .appendTo(btngroup)
  221.             .click(function () {
  222.                 initPm(name).find(".panel-heading").click();
  223.                 menu.hide();
  224.             });
  225.     }
  226.  
  227.     /* give/remove leader (moderator+ only) */
  228.     if (hasPermission("leaderctl")) {
  229.         var ldr = $("<button/>").addClass("btn btn-xs btn-default")
  230.             .appendTo(btngroup);
  231.         if(leader) {
  232.             ldr.text("Remove Leader");
  233.             ldr.click(function () {
  234.                 socket.emit("assignLeader", {
  235.                     name: ""
  236.                 });
  237.             });
  238.         } else {
  239.             ldr.text("Give Leader");
  240.             ldr.click(function () {
  241.                 socket.emit("assignLeader", {
  242.                     name: name
  243.                 });
  244.             });
  245.         }
  246.     }
  247.  
  248.     /* kick button */
  249.     if(hasPermission("kick")) {
  250.         $("<button/>").addClass("btn btn-xs btn-default")
  251.             .text("Kick")
  252.             .click(function () {
  253.                 var reason = prompt("Enter kick reason (optional)");
  254.                 socket.emit("chatMsg", {
  255.                     msg: "/kick " + name + " " + reason,
  256.                     meta: {}
  257.                 });
  258.             })
  259.             .appendTo(btngroup);
  260.     }
  261.  
  262.     /* mute buttons */
  263.     if (hasPermission("mute")) {
  264.         var mute = $("<button/>").addClass("btn btn-xs btn-default")
  265.             .text("Mute")
  266.             .click(function () {
  267.                 socket.emit("chatMsg", {
  268.                     msg: "/mute " + name,
  269.                     meta: {}
  270.                 });
  271.             })
  272.             .appendTo(btngroup);
  273.         var smute = $("<button/>").addClass("btn btn-xs btn-default")
  274.             .text("Shadow Mute")
  275.             .click(function () {
  276.                 socket.emit("chatMsg", {
  277.                     msg: "/smute " + name,
  278.                     meta: {}
  279.                 });
  280.             })
  281.             .appendTo(btngroup);
  282.         var unmute = $("<button/>").addClass("btn btn-xs btn-default")
  283.             .text("Unmute")
  284.             .click(function () {
  285.                 socket.emit("chatMsg", {
  286.                     msg: "/unmute " + name,
  287.                     meta: {}
  288.                 });
  289.             })
  290.             .appendTo(btngroup);
  291.         if (meta.muted) {
  292.             mute.hide();
  293.             smute.hide();
  294.         } else {
  295.             unmute.hide();
  296.         }
  297.     }
  298.  
  299.     /* ban buttons */
  300.     if(hasPermission("ban")) {
  301.         $("<button/>").addClass("btn btn-xs btn-default")
  302.             .text("Name Ban")
  303.             .click(function () {
  304.                 var reason = prompt("Enter ban reason (optional)");
  305.                 socket.emit("chatMsg", {
  306.                     msg: "/ban " + name + " " + reason,
  307.                     meta: {}
  308.                 });
  309.             })
  310.             .appendTo(btngroup);
  311.         $("<button/>").addClass("btn btn-xs btn-default")
  312.             .text("IP Ban")
  313.             .click(function () {
  314.                 var reason = prompt("Enter ban reason (optional)");
  315.                 socket.emit("chatMsg", {
  316.                     msg: "/ipban " + name + " " + reason,
  317.                     meta: {}
  318.                 });
  319.             })
  320.             .appendTo(btngroup);
  321.     }
  322.  
  323.     var showdd = function(ev) {
  324.         // Workaround for Chrome
  325.         if (ev.shiftKey) return true;
  326.         ev.preventDefault();
  327.         if(menu.css("display") == "none") {
  328.             $(".user-dropdown").hide();
  329.             $(document).bind("mouseup.userlist-ddown", function (e) {
  330.                 if (menu.has(e.target).length === 0 &&
  331.                     entry.parent().has(e.target).length === 0) {
  332.                     menu.hide();
  333.                     $(document).unbind("mouseup.userlist-ddown");
  334.                 }
  335.             });
  336.             menu.show();
  337.             menu.css("top", entry.position().top);
  338.         } else {
  339.             menu.hide();
  340.         }
  341.         return false;
  342.     };
  343.     entry.contextmenu(showdd);
  344.     entry.click(showdd);
  345. }
  346.  
  347. function calcUserBreakdown() {
  348.     var breakdown = {
  349.         "Site Admins": 0,
  350.         "Channel Admins": 0,
  351.         "Moderators": 0,
  352.         "Regular Users": 0,
  353.         "Guests": 0,
  354.         "Anonymous": 0,
  355.         "AFK": 0
  356.     };
  357.     var total = 0;
  358.     $("#userlist .userlist_item").each(function (index, item) {
  359.         var data = {
  360.             rank: $(item).data("rank")
  361.         };
  362.  
  363.         if(data.rank >= 255)
  364.             breakdown["Site Admins"]++;
  365.         else if(data.rank >= 3)
  366.             breakdown["Channel Admins"]++;
  367.         else if(data.rank == 2)
  368.             breakdown["Moderators"]++;
  369.         else if(data.rank >= 1)
  370.             breakdown["Regular Users"]++;
  371.         else
  372.             breakdown["Guests"]++;
  373.  
  374.         total++;
  375.  
  376.         if($(item).data("afk"))
  377.             breakdown["AFK"]++;
  378.     });
  379.  
  380.     breakdown["Anonymous"] = CHANNEL.usercount - total;
  381.  
  382.     return breakdown;
  383. }
  384.  
  385. function sortUserlist() {
  386.     var slice = Array.prototype.slice;
  387.     var list = slice.call($("#userlist .userlist_item"));
  388.     list.sort(function (a, b) {
  389.         var r1 = $(a).data("rank");
  390.         var r2 = $(b).data("rank");
  391.         var afk1 = $(a).find(".glyphicon-time").length > 0;
  392.         var afk2 = $(b).find(".glyphicon-time").length > 0;
  393.         var name1 = a.children[1].innerHTML.toLowerCase();
  394.         var name2 = b.children[1].innerHTML.toLowerCase();
  395.  
  396.         if(USEROPTS.sort_afk) {
  397.             if(afk1 && !afk2)
  398.                 return 1;
  399.             if(!afk1 && afk2)
  400.                 return -1;
  401.         }
  402.  
  403.         if(USEROPTS.sort_rank) {
  404.             if(r1 < r2)
  405.                 return 1;
  406.             if(r1 > r2)
  407.                 return -1;
  408.         }
  409.  
  410.         return name1 === name2 ? 0 : (name1 < name2 ? -1 : 1);
  411.     });
  412.  
  413.     list.forEach(function (item) {
  414.         $(item).detach();
  415.     });
  416.     list.forEach(function (item) {
  417.         $(item).appendTo($("#userlist"));
  418.     });
  419. }
  420.  
  421. /* queue stuff */
  422.  
  423. function scrollQueue() {
  424.     var li = playlistFind(PL_CURRENT);
  425.     if(!li)
  426.         return;
  427.  
  428.     li = $(li);
  429.     $("#queue").scrollTop(0);
  430.     var scroll = li.position().top - $("#queue").position().top;
  431.     $("#queue").scrollTop(scroll);
  432. }
  433.  
  434. function makeQueueEntry(item, addbtns) {
  435.     var video = item.media;
  436.     var li = $("<li/>");
  437.     li.addClass("queue_entry");
  438.     li.addClass("pluid-" + item.uid);
  439.     li.data("uid", item.uid);
  440.     li.data("media", video);
  441.     li.data("temp", item.temp);
  442.     if(video.thumb) {
  443.         $("<img/>").attr("src", video.thumb.url)
  444.             .css("float", "left")
  445.             .css("clear", "both")
  446.             .appendTo(li);
  447.     }
  448.     var title = $("<a/>").addClass("qe_title").appendTo(li)
  449.         .text(video.title)
  450.         .attr("href", formatURL(video))
  451.         .attr("target", "_blank");
  452.     var addedby = $("<span/>").addClass("qe_addedby").appendTo(li)
  453.         .text(' via ' + item.queueby);
  454.     var time = $("<span/>").addClass("qe_time").appendTo(li);
  455.     time.text(video.duration);
  456.     var clear = $("<div/>").addClass("qe_clear").appendTo(li);
  457.     if(item.temp) {
  458.         li.addClass("queue_temp");
  459.     }
  460.  
  461.     if(addbtns)
  462.         addQueueButtons(li);
  463.     return li;
  464. }
  465.  
  466. function makeSearchEntry(video) {
  467.     var li = $("<li/>");
  468.     li.addClass("queue_entry");
  469.     li.data("media", video);
  470.     if(video.thumb) {
  471.         $("<img/>").attr("src", video.thumb.url)
  472.             .css("float", "left")
  473.             .css("clear", "both")
  474.             .appendTo(li);
  475.     }
  476.     var title = $("<a/>").addClass("qe_title").appendTo(li)
  477.         .text(video.title)
  478.         .attr("href", formatURL(video))
  479.         .attr("target", "_blank");
  480.     var time = $("<span/>").addClass("qe_time").appendTo(li);
  481.     time.text(video.duration);
  482.     var clear = $("<div/>").addClass("qe_clear").appendTo(li);
  483.  
  484.     return li;
  485. }
  486.  
  487. function addQueueButtons(li) {
  488.     li.find(".btn-group").remove();
  489.     var menu = $("<div/>").addClass("btn-group").appendTo(li);
  490.     // Play
  491.     if(hasPermission("playlistjump")) {
  492.         $("<button/>").addClass("btn btn-xs btn-default qbtn-play")
  493.             .html("<span class='glyphicon glyphicon-play'></span>Play")
  494.             .click(function() {
  495.                 socket.emit("jumpTo", li.data("uid"));
  496.             })
  497.             .appendTo(menu);
  498.     }
  499.     // Queue next
  500.     if(hasPermission("playlistmove")) {
  501.         $("<button/>").addClass("btn btn-xs btn-default qbtn-next")
  502.             .html("<span class='glyphicon glyphicon-share-alt'></span>Queue Next")
  503.             .click(function() {
  504.                 socket.emit("moveMedia", {
  505.                     from: li.data("uid"),
  506.                     after: PL_CURRENT
  507.                 });
  508.             })
  509.             .appendTo(menu);
  510.     }
  511.     // Temp/Untemp
  512.     if(hasPermission("settemp")) {
  513.         var tempstr = li.data("temp")?"Make Permanent":"Make Temporary";
  514.         $("<button/>").addClass("btn btn-xs btn-default qbtn-tmp")
  515.             .html("<span class='glyphicon glyphicon-flag'></span>" + tempstr)
  516.             .click(function() {
  517.                 socket.emit("setTemp", {
  518.                     uid: li.data("uid"),
  519.                     temp: !li.data("temp")
  520.                 });
  521.             })
  522.             .appendTo(menu);
  523.     }
  524.     // Delete
  525.     if(hasPermission("playlistdelete")) {
  526.         $("<button/>").addClass("btn btn-xs btn-default qbtn-delete")
  527.             .html("<span class='glyphicon glyphicon-trash'></span>Delete")
  528.             .click(function() {
  529.                 socket.emit("delete", li.data("uid"));
  530.             })
  531.             .appendTo(menu);
  532.     }
  533.  
  534.     if(USEROPTS.qbtn_hide && !USEROPTS.qbtn_idontlikechange
  535.         || menu.find(".btn").length == 0)
  536.         menu.hide();
  537.  
  538.     // I DON'T LIKE CHANGE
  539.     if(USEROPTS.qbtn_idontlikechange) {
  540.         menu.addClass("pull-left");
  541.         menu.detach().prependTo(li);
  542.         menu.find(".btn").each(function() {
  543.             // Clear icon
  544.             var icon = $(this).find(".glyphicon");
  545.             $(this).html("");
  546.             icon.appendTo(this);
  547.         });
  548.         menu.find(".qbtn-play").addClass("btn-success");
  549.         menu.find(".qbtn-delete").addClass("btn-danger");
  550.     }
  551.     else if(menu.find(".btn").length != 0) {
  552.         li.unbind("contextmenu");
  553.         li.contextmenu(function(ev) {
  554.             // Allow shift+click to open context menu
  555.             // (Chrome workaround, works by default on Firefox)
  556.             if (ev.shiftKey) return true;
  557.             ev.preventDefault();
  558.             if(menu.css("display") == "none")
  559.                 menu.show("blind");
  560.             else
  561.                 menu.hide("blind");
  562.             return false;
  563.         });
  564.     }
  565. }
  566.  
  567. function rebuildPlaylist() {
  568.     var qli = $("#queue li");
  569.     if(qli.length == 0)
  570.         return;
  571.     REBUILDING = Math.random() + "";
  572.     var r = REBUILDING;
  573.     var i = 0;
  574.     qli.each(function() {
  575.         var li = $(this);
  576.         (function(i, r) {
  577.             setTimeout(function() {
  578.                 // Stop if another rebuild is running
  579.                 if(REBUILDING != r)
  580.                     return;
  581.                 addQueueButtons(li);
  582.                 if(i == qli.length - 1) {
  583.                     scrollQueue();
  584.                     REBUILDING = false;
  585.                 }
  586.             }, 10*i);
  587.         })(i, r);
  588.         i++;
  589.     });
  590. }
  591.  
  592. /* menus */
  593.  
  594. /* user settings menu */
  595. function showUserOptions() {
  596.     hidePlayer();
  597.     $("#useroptions").on("hidden.bs.modal", function () {
  598.         unhidePlayer();
  599.     });
  600.  
  601.     if (CLIENT.rank < 2) {
  602.         $("a[href='#us-mod']").parent().hide();
  603.     } else {
  604.         $("a[href='#us-mod']").parent().show();
  605.     }
  606.  
  607.     $("#us-theme").val(USEROPTS.theme);
  608.     $("#us-layout").val(USEROPTS.layout);
  609.     $("#us-no-channelcss").prop("checked", USEROPTS.ignore_channelcss);
  610.     $("#us-no-channeljs").prop("checked", USEROPTS.ignore_channeljs);
  611.     var conninfo = "<strong>Connection Information: </strong>" +
  612.                    "Connected to <code>" + IO_URL + "</code> (";
  613.     if (IO_V6) {
  614.         conninfo += "IPv6, ";
  615.     } else {
  616.         conninfo += "IPv4, ";
  617.     }
  618.  
  619.     if (IO_URL === IO_URLS["ipv4-ssl"] || IO_URL === IO_URLS["ipv6-ssl"]) {
  620.         conninfo += "SSL)";
  621.     } else {
  622.         conninfo += "no SSL)";
  623.     }
  624.  
  625.     conninfo += ".  SSL is enabled by default if it is supported by the server.";
  626.     $("#us-conninfo").html(conninfo);
  627.  
  628.  
  629.     $("#us-synch").prop("checked", USEROPTS.synch);
  630.     $("#us-synch-accuracy").val(USEROPTS.sync_accuracy);
  631.     $("#us-wmode-transparent").prop("checked", USEROPTS.wmode_transparent);
  632.     $("#us-hidevideo").prop("checked", USEROPTS.hidevid);
  633.     $("#us-playlistbuttons").prop("checked", USEROPTS.qbtn_hide);
  634.     $("#us-oldbtns").prop("checked", USEROPTS.qbtn_idontlikechange);
  635.     $("#us-default-quality").val(USEROPTS.default_quality || "auto");
  636.  
  637.     $("#us-chat-timestamp").prop("checked", USEROPTS.show_timestamps);
  638.     $("#us-sort-rank").prop("checked", USEROPTS.sort_rank);
  639.     $("#us-sort-afk").prop("checked", USEROPTS.sort_afk);
  640.     $("#us-blink-title").val(USEROPTS.blink_title);
  641.     $("#us-ping-sound").val(USEROPTS.boop);
  642.     $("#us-sendbtn").prop("checked", USEROPTS.chatbtn);
  643.     $("#us-no-emotes").prop("checked", USEROPTS.no_emotes);
  644.  
  645.     $("#us-modflair").prop("checked", USEROPTS.modhat);
  646.     $("#us-joinmessage").prop("checked", USEROPTS.joinmessage);
  647.     $("#us-shadowchat").prop("checked", USEROPTS.show_shadowchat);
  648.  
  649.     formatScriptAccessPrefs();
  650.  
  651.     $("a[href='#us-general']").click();
  652.     $("#useroptions").modal();
  653. }
  654.  
  655. function saveUserOptions() {
  656.     USEROPTS.theme                = $("#us-theme").val();
  657.     createCookie("cytube-theme", USEROPTS.theme, 1000);
  658.     USEROPTS.layout               = $("#us-layout").val();
  659.     USEROPTS.ignore_channelcss    = $("#us-no-channelcss").prop("checked");
  660.     USEROPTS.ignore_channeljs     = $("#us-no-channeljs").prop("checked");
  661.     USEROPTS.secure_connection    = $("#us-ssl").prop("checked");
  662.  
  663.     USEROPTS.synch                = $("#us-synch").prop("checked");
  664.     USEROPTS.sync_accuracy        = parseFloat($("#us-synch-accuracy").val()) || 2;
  665.     USEROPTS.wmode_transparent    = $("#us-wmode-transparent").prop("checked");
  666.     USEROPTS.hidevid              = $("#us-hidevideo").prop("checked");
  667.     USEROPTS.qbtn_hide            = $("#us-playlistbuttons").prop("checked");
  668.     USEROPTS.qbtn_idontlikechange = $("#us-oldbtns").prop("checked");
  669.     USEROPTS.default_quality      = $("#us-default-quality").val();
  670.  
  671.     USEROPTS.show_timestamps      = $("#us-chat-timestamp").prop("checked");
  672.     USEROPTS.sort_rank            = $("#us-sort-rank").prop("checked");
  673.     USEROPTS.sort_afk             = $("#us-sort-afk").prop("checked");
  674.     USEROPTS.blink_title          = $("#us-blink-title").val();
  675.     USEROPTS.boop                 = $("#us-ping-sound").val();
  676.     USEROPTS.chatbtn              = $("#us-sendbtn").prop("checked");
  677.     USEROPTS.no_emotes            = $("#us-no-emotes").prop("checked");
  678.  
  679.     if (CLIENT.rank >= 2) {
  680.         USEROPTS.modhat      = $("#us-modflair").prop("checked");
  681.         USEROPTS.joinmessage = $("#us-joinmessage").prop("checked");
  682.         USEROPTS.show_shadowchat = $("#us-shadowchat").prop("checked");
  683.     }
  684.  
  685.     storeOpts();
  686.     applyOpts();
  687. }
  688.  
  689. function storeOpts() {
  690.     for(var key in USEROPTS) {
  691.         setOpt(key, USEROPTS[key]);
  692.     }
  693. }
  694.  
  695. function applyOpts() {
  696.     if ($("#usertheme").attr("href") !== USEROPTS.theme) {
  697.         var old = $("#usertheme").attr("id", "usertheme_old");
  698.         var theme = USEROPTS.theme;
  699.         if (theme === "default") {
  700.             theme = "/css/themes/slate.css";
  701.         }
  702.         $("<link/>").attr("rel", "stylesheet")
  703.             .attr("type", "text/css")
  704.             .attr("id", "usertheme")
  705.             .attr("href", theme)
  706.             .attr("onload", "$('#usertheme_old').remove()")
  707.             .appendTo($("head"));
  708.         fixWeirdButtonAlignmentIssue();
  709.     }
  710.  
  711.         synchtubeLayout();
  712.  
  713.  
  714.     if(USEROPTS.hidevid) {
  715.         $("#qualitywrap").html("");
  716.         removeVideo();
  717.         $("#chatwrap").removeClass("col-lg-5 col-md-5").addClass("col-lg-12 col-md-12");
  718.     }
  719.  
  720.     $("#chatbtn").remove();
  721.     if(USEROPTS.chatbtn) {
  722.         var btn = $("<button/>").addClass("btn btn-default btn-block")
  723.             .text("Send")
  724.             .attr("id", "chatbtn")
  725.             .appendTo($("#chatwrap"));
  726.         btn.click(function() {
  727.             if($("#chatline").val().trim()) {
  728.                 socket.emit("chatMsg", {
  729.                     msg: $("#chatline").val(),
  730.                     meta: {}
  731.                 });
  732.                 $("#chatline").val("");
  733.             }
  734.         });
  735.     }
  736.  
  737.     if (USEROPTS.modhat) {
  738.         $("#modflair").removeClass("label-default")
  739.             .addClass("label-success");
  740.     } else {
  741.         $("#modflair").removeClass("label-success")
  742.             .addClass("label-default");
  743.     }
  744. }
  745.  
  746. function showPollMenu() {
  747.     $("#pollwrap .poll-menu").remove();
  748.     var menu = $("<div/>").addClass("well poll-menu")
  749.         .prependTo($("#pollwrap"));
  750.  
  751.     $("<button/>").addClass("btn btn-sm btn-danger pull-right")
  752.         .text("Cancel")
  753.         .appendTo(menu)
  754.         .click(function() {
  755.             menu.remove();
  756.         });
  757.  
  758.     $("<strong/>").text("Title").appendTo(menu);
  759.  
  760.     var title = $("<input/>").addClass("form-control")
  761.         .attr("type", "text")
  762.         .appendTo(menu);
  763.  
  764.     $("<strong/>").text("Timeout (optional)").appendTo(menu);
  765.     var timeout = $("<input/>").addClass("form-control")
  766.         .attr("type", "text")
  767.         .appendTo(menu);
  768.  
  769.     var checkboxOuter = $("<div/>").addClass("checkbox").appendTo(menu);
  770.     var lbl = $("<label/>").text("Hide poll results")
  771.         .appendTo(checkboxOuter);
  772.     var hidden = $("<input/>").attr("type", "checkbox")
  773.         .prependTo(lbl);
  774.  
  775.     $("<strong/>").text("Options").appendTo(menu);
  776.  
  777.     var addbtn = $("<button/>").addClass("btn btn-sm btn-default")
  778.         .text("Add Option")
  779.         .appendTo(menu);
  780.  
  781.     function addOption() {
  782.         $("<input/>").addClass("form-control")
  783.             .attr("type", "text")
  784.             .addClass("poll-menu-option")
  785.             .insertBefore(addbtn);
  786.     }
  787.  
  788.     addbtn.click(addOption);
  789.     addOption();
  790.     addOption();
  791.  
  792.     $("<button/>").addClass("btn btn-default btn-block")
  793.         .text("Open Poll")
  794.         .appendTo(menu)
  795.         .click(function() {
  796.             var opts = [];
  797.             menu.find(".poll-menu-option").each(function() {
  798.                 if($(this).val() != "")
  799.                     opts.push($(this).val());
  800.             });
  801.             socket.emit("newPoll", {
  802.                 title: title.val(),
  803.                 opts: opts,
  804.                 obscured: hidden.prop("checked"),
  805.                 timeout: timeout.val() ? parseInt(timeout.val()) : undefined
  806.             });
  807.             menu.remove();
  808.         });
  809. }
  810.  
  811. function scrollChat() {
  812.     $("#messagebuffer").scrollTop($("#messagebuffer").prop("scrollHeight"));
  813. }
  814.  
  815. function hasPermission(key) {
  816.     if(key.indexOf("playlist") == 0 && CHANNEL.openqueue) {
  817.         var key2 = "o" + key;
  818.         var v = CHANNEL.perms[key2];
  819.         if(typeof v == "number" && CLIENT.rank >= v) {
  820.             return true;
  821.         }
  822.     }
  823.     var v = CHANNEL.perms[key];
  824.     if(typeof v != "number") {
  825.         return false;
  826.     }
  827.     return CLIENT.rank >= v;
  828. }
  829.  
  830. function setVisible(selector, bool) {
  831.     // I originally added this check because of a race condition
  832.     // Now it seems to work without but I don't trust it
  833.     if($(selector) && $(selector).attr("id") != selector.substring(1)) {
  834.         setTimeout(function() {
  835.             setVisible(selector, bool);
  836.         }, 100);
  837.         return;
  838.     }
  839.     var disp = bool ? "" : "none";
  840.     $(selector).css("display", disp);
  841. }
  842.  
  843. function setParentVisible(selector, bool) {
  844.     var disp = bool ? "" : "none";
  845.     $(selector).parent().css("display", disp);
  846. }
  847.  
  848. function handleModPermissions() {
  849.     $("#cs-chanranks-adm").attr("disabled", CLIENT.rank < 4);
  850.     $("#cs-chanranks-owner").attr("disabled", CLIENT.rank < 4);
  851.     /* update channel controls */
  852.     $("#cs-pagetitle").val(CHANNEL.opts.pagetitle);
  853.     $("#cs-pagetitle").attr("disabled", CLIENT.rank < 3);
  854.     $("#cs-externalcss").val(CHANNEL.opts.externalcss);
  855.     $("#cs-externalcss").attr("disabled", CLIENT.rank < 3);
  856.     $("#cs-externaljs").val(CHANNEL.opts.externaljs);
  857.     $("#cs-externaljs").attr("disabled", CLIENT.rank < 3);
  858.     $("#cs-chat_antiflood").prop("checked", CHANNEL.opts.chat_antiflood);
  859.     if ("chat_antiflood_params" in CHANNEL.opts) {
  860.         $("#cs-chat_antiflood_burst").val(CHANNEL.opts.chat_antiflood_params.burst);
  861.         $("#cs-chat_antiflood_sustained").val(CHANNEL.opts.chat_antiflood_params.sustained);
  862.     }
  863.     $("#cs-show_public").prop("checked", CHANNEL.opts.show_public);
  864.     $("#cs-show_public").attr("disabled", CLIENT.rank < 3);
  865.     $("#cs-password").val(CHANNEL.opts.password || "");
  866.     $("#cs-password").attr("disabled", CLIENT.rank < 3);
  867.     $("#cs-enable_link_regex").prop("checked", CHANNEL.opts.enable_link_regex);
  868.     $("#cs-afk_timeout").val(CHANNEL.opts.afk_timeout);
  869.     $("#cs-allow_voteskip").prop("checked", CHANNEL.opts.allow_voteskip);
  870.     $("#cs-voteskip_ratio").val(CHANNEL.opts.voteskip_ratio);
  871.     $("#cs-allow_dupes").prop("checked", CHANNEL.opts.allow_dupes);
  872.     $("#cs-torbanned").prop("checked", CHANNEL.opts.torbanned);
  873.     $("#cs-allow_ascii_control").prop("checked", CHANNEL.opts.allow_ascii_control);
  874.     $("#cs-playlist_max_per_user").val(CHANNEL.opts.playlist_max_per_user || 0);
  875.     (function() {
  876.         if(typeof CHANNEL.opts.maxlength != "number") {
  877.             $("#cs-maxlength").val("");
  878.             return;
  879.         }
  880.         var h = parseInt(CHANNEL.opts.maxlength / 3600);
  881.         h = ""+h;
  882.         if(h.length < 2) h = "0" + h;
  883.         var m = parseInt((CHANNEL.opts.maxlength % 3600) / 60);
  884.         m = ""+m;
  885.         if(m.length < 2) m = "0" + m;
  886.         var s = parseInt(CHANNEL.opts.maxlength % 60);
  887.         s = ""+s;
  888.         if(s.length < 2) s = "0" + s;
  889.         $("#cs-maxlength").val(h + ":" + m + ":" + s);
  890.     })();
  891.     $("#cs-csstext").val(CHANNEL.css);
  892.     $("#cs-jstext").val(CHANNEL.js);
  893.     $("#cs-motdtext").val(CHANNEL.motd_text);
  894.     setParentVisible("a[href='#cs-motdeditor']", hasPermission("motdedit"));
  895.     setParentVisible("a[href='#cs-permedit']", CLIENT.rank >= 3);
  896.     setParentVisible("a[href='#cs-banlist']", hasPermission("ban"));
  897.     setParentVisible("a[href='#cs-csseditor']", CLIENT.rank >= 3);
  898.     setParentVisible("a[href='#cs-jseditor']", CLIENT.rank >= 3);
  899.     setParentVisible("a[href='#cs-chatfilters']", hasPermission("filteredit"));
  900.     setParentVisible("a[href='#cs-emotes']", hasPermission("emoteedit"));
  901.     setParentVisible("a[href='#cs-chanranks']", CLIENT.rank >= 3);
  902.     setParentVisible("a[href='#cs-chanlog']", CLIENT.rank >= 2);
  903.     $("#cs-chatfilters-import").attr("disabled", !hasPermission("filterimport"));
  904.     $("#cs-emotes-import").attr("disabled", !hasPermission("filterimport"));
  905. }
  906.  
  907. function handlePermissionChange() {
  908.     if(CLIENT.rank >= 2) {
  909.         handleModPermissions();
  910.     }
  911.  
  912.     $("#qlockbtn").attr("disabled", !hasPermission("playlistlock"));
  913.     setVisible("#showchansettings", CLIENT.rank >= 2);
  914.     setVisible("#playlistmanagerwrap", CLIENT.rank >= 1);
  915.     setVisible("#modflair", CLIENT.rank >= 2);
  916.     setVisible("#adminflair", CLIENT.rank >= 255);
  917.     setVisible("#guestlogin", CLIENT.rank < 0);
  918.     setVisible("#chatline", CLIENT.rank >= 0);
  919.     setVisible("#queue", hasPermission("seeplaylist"));
  920.     setVisible("#plmeta", hasPermission("seeplaylist"));
  921.     $("#getplaylist").attr("disabled", !hasPermission("seeplaylist"));
  922.  
  923.     setVisible("#showplaylistmanager", hasPermission("seeplaylist"));
  924.     setVisible("#showmediaurl", hasPermission("playlistadd"));
  925.     setVisible("#showcustomembed", hasPermission("playlistaddcustom"));
  926.     $("#queue_next").attr("disabled", !hasPermission("playlistnext"));
  927.  
  928.     if(hasPermission("playlistadd") ||
  929.         hasPermission("playlistmove") ||
  930.         hasPermission("playlistjump") ||
  931.         hasPermission("playlistdelete") ||
  932.         hasPermission("settemp")) {
  933.         if(USEROPTS.first_visit && $("#plonotification").length == 0) {
  934.             var al = makeAlert("Playlist Options", [
  935.                 "From the Options menu, you can choose to automatically",
  936.                 " hide the buttons on each entry (and show them when",
  937.                 " you right click).  You can also choose to use the old",
  938.                 " style of playlist buttons.",
  939.                 "<br>"].join(""))
  940.                 .attr("id", "plonotification")
  941.                 .insertAfter($("#queuefail"));
  942.  
  943.             al.find(".close").remove();
  944.  
  945.             $("<button/>").addClass("btn btn-primary")
  946.                 .text("Dismiss")
  947.                 .appendTo(al.find(".alert"))
  948.                 .click(function() {
  949.                     USEROPTS.first_visit = false;
  950.                     storeOpts();
  951.                     al.hide("fade", function() {
  952.                         al.remove();
  953.                     });
  954.                 });
  955.         }
  956.     }
  957.  
  958.     if(hasPermission("playlistmove")) {
  959.         $("#queue").sortable("enable");
  960.         $("#queue").addClass("queue_sortable");
  961.     }
  962.     else {
  963.         $("#queue").sortable("disable");
  964.         $("#queue").removeClass("queue_sortable");
  965.     }
  966.  
  967.     setVisible("#clearplaylist", hasPermission("playlistclear"));
  968.     setVisible("#shuffleplaylist", hasPermission("playlistshuffle"));
  969.     if (!hasPermission("addnontemp")) {
  970.         $(".add-temp").prop("checked", true);
  971.         $(".add-temp").attr("disabled", true);
  972.     } else {
  973.         $(".add-temp").attr("disabled", false);
  974.     }
  975.  
  976.     fixWeirdButtonAlignmentIssue();
  977.  
  978.     setVisible("#newpollbtn", hasPermission("pollctl"));
  979.     $("#voteskip").attr("disabled", !hasPermission("voteskip") ||
  980.                                     !CHANNEL.opts.allow_voteskip);
  981.  
  982.     $("#pollwrap .active").find(".btn-danger").remove();
  983.     if(hasPermission("pollctl")) {
  984.         var poll = $("#pollwrap .active");
  985.         if(poll.length > 0) {
  986.             $("<button/>").addClass("btn btn-danger pull-right")
  987.                 .text("End Poll")
  988.                 .insertAfter(poll.find(".close"))
  989.                 .click(function() {
  990.                     socket.emit("closePoll");
  991.                 });
  992.         }
  993.     }
  994.     var poll = $("#pollwrap .active");
  995.     if(poll.length > 0) {
  996.         poll.find(".btn").attr("disabled", !hasPermission("pollvote"));
  997.     }
  998.     var users = $("#userlist").children();
  999.     for(var i = 0; i < users.length; i++) {
  1000.         addUserDropdown($(users[i]));
  1001.     }
  1002.  
  1003.     $("#chatline").attr("disabled", !hasPermission("chat"));
  1004.     rebuildPlaylist();
  1005. }
  1006.  
  1007. function fixWeirdButtonAlignmentIssue() {
  1008.     // Weird things happen to the alignment in chromium when I toggle visibility
  1009.     // of the above buttons
  1010.     // This fixes it?
  1011.     var wtf = $("#videocontrols").removeClass("pull-right");
  1012.     setTimeout(function () {
  1013.         wtf.addClass("pull-right");
  1014.     }, 1);
  1015. }
  1016.  
  1017. /* search stuff */
  1018.  
  1019. function clearSearchResults() {
  1020.     $("#library").html("");
  1021.     $("#search_clear").remove();
  1022.     var p = $("#library").data("paginator");
  1023.     if(p) {
  1024.         p.paginator.html("");
  1025.     }
  1026. }
  1027.  
  1028. function addLibraryButtons(li, id, source) {
  1029.     var btns = $("<div/>").addClass("btn-group")
  1030.         .addClass("pull-left")
  1031.         .prependTo(li);
  1032.  
  1033.     var type = (source === "library") ? "lib" : source;
  1034.  
  1035.     if(hasPermission("playlistadd")) {
  1036.         if(hasPermission("playlistnext")) {
  1037.             $("<button/>").addClass("btn btn-xs btn-default")
  1038.                 .text("Next")
  1039.                 .click(function() {
  1040.                     socket.emit("queue", {
  1041.                         id: id,
  1042.                         pos: "next",
  1043.                         type: type,
  1044.                         temp: $(".add-temp").prop("checked")
  1045.                     });
  1046.                 })
  1047.                 .appendTo(btns);
  1048.         }
  1049.         $("<button/>").addClass("btn btn-xs btn-default")
  1050.             .text("End")
  1051.             .click(function() {
  1052.                 socket.emit("queue", {
  1053.                     id: id,
  1054.                     pos: "end",
  1055.                     type: type,
  1056.                     temp: $(".add-temp").prop("checked")
  1057.                 });
  1058.             })
  1059.             .appendTo(btns);
  1060.     }
  1061.     if(CLIENT.rank >= 2 && source === "library") {
  1062.         $("<button/>").addClass("btn btn-xs btn-danger")
  1063.             .html("<span class='glyphicon glyphicon-trash'></span>")
  1064.             .click(function() {
  1065.                 socket.emit("uncache", {
  1066.                     id: id
  1067.                 });
  1068.                 li.hide("fade", function() {
  1069.                     li.remove();
  1070.                 });
  1071.             })
  1072.             .appendTo(btns);
  1073.     }
  1074. }
  1075.  
  1076. /* queue stuff */
  1077.  
  1078. var AsyncQueue = function () {
  1079.     this._q = [];
  1080.     this._lock = false;
  1081.     this._tm = 0;
  1082. };
  1083.  
  1084. AsyncQueue.prototype.next = function () {
  1085.     if (this._q.length > 0) {
  1086.         if (!this.lock())
  1087.             return;
  1088.         var item = this._q.shift();
  1089.         var fn = item[0], tm = item[1];
  1090.         this._tm = Date.now() + item[1];
  1091.         fn(this);
  1092.     }
  1093. };
  1094.  
  1095. AsyncQueue.prototype.lock = function () {
  1096.     if (this._lock) {
  1097.         if (this._tm > 0 && Date.now() > this._tm) {
  1098.             this._tm = 0;
  1099.             return true;
  1100.         }
  1101.         return false;
  1102.     }
  1103.  
  1104.     this._lock = true;
  1105.     return true;
  1106. };
  1107.  
  1108. AsyncQueue.prototype.release = function () {
  1109.     var self = this;
  1110.     if (!self._lock)
  1111.         return false;
  1112.  
  1113.     self._lock = false;
  1114.     self.next();
  1115.     return true;
  1116. };
  1117.  
  1118. AsyncQueue.prototype.queue = function (fn) {
  1119.     var self = this;
  1120.     self._q.push([fn, 20000]);
  1121.     self.next();
  1122. };
  1123.  
  1124. AsyncQueue.prototype.reset = function () {
  1125.     this._q = [];
  1126.     this._lock = false;
  1127. };
  1128.  
  1129. var PL_ACTION_QUEUE = new AsyncQueue();
  1130.  
  1131. // Because jQuery UI does weird things
  1132. function playlistFind(uid) {
  1133.     var children = document.getElementById("queue").children;
  1134.     for(var i in children) {
  1135.         if(typeof children[i].getAttribute != "function")
  1136.             continue;
  1137.         if(children[i].getAttribute("class").indexOf("pluid-" + uid) != -1)
  1138.             return children[i];
  1139.     }
  1140.     return false;
  1141. }
  1142.  
  1143. function playlistMove(from, after, cb) {
  1144.     var lifrom = $(".pluid-" + from);
  1145.     if(lifrom.length == 0) {
  1146.         cb(false);
  1147.         return;
  1148.     }
  1149.  
  1150.     var q = $("#queue");
  1151.  
  1152.     if(after === "prepend") {
  1153.         lifrom.hide("blind", function() {
  1154.             lifrom.detach();
  1155.             lifrom.prependTo(q);
  1156.             lifrom.show("blind", cb);
  1157.         });
  1158.     }
  1159.     else if(after === "append") {
  1160.         lifrom.hide("blind", function() {
  1161.             lifrom.detach();
  1162.             lifrom.appendTo(q);
  1163.             lifrom.show("blind", cb);
  1164.         });
  1165.     }
  1166.     else {
  1167.         var liafter = $(".pluid-" + after);
  1168.         if(liafter.length == 0) {
  1169.             cb(false);
  1170.             return;
  1171.         }
  1172.         lifrom.hide("blind", function() {
  1173.             lifrom.detach();
  1174.             lifrom.insertAfter(liafter);
  1175.             lifrom.show("blind", cb);
  1176.         });
  1177.     }
  1178. }
  1179.  
  1180. function extractQueryParam(query, param) {
  1181.     var params = {};
  1182.     query.split("&").forEach(function (kv) {
  1183.         kv = kv.split("=");
  1184.         params[kv[0]] = kv[1];
  1185.     });
  1186.  
  1187.     return params[param];
  1188. }
  1189.  
  1190. function parseMediaLink(url) {
  1191.     if(typeof url != "string") {
  1192.         return {
  1193.             id: null,
  1194.             type: null
  1195.         };
  1196.     }
  1197.     url = url.trim();
  1198.     url = url.replace("feature=player_embedded&", "");
  1199.  
  1200.     if(url.indexOf("jw:") == 0) {
  1201.         return {
  1202.             id: url.substring(3),
  1203.             type: "jw"
  1204.         };
  1205.     }
  1206.  
  1207.     if(url.indexOf("rtmp://") == 0) {
  1208.         return {
  1209.             id: url,
  1210.             type: "rt"
  1211.         };
  1212.     }
  1213.  
  1214.     var m;
  1215.     if((m = url.match(/youtube\.com\/watch\?([^#]+)/))) {
  1216.         return {
  1217.             id: extractQueryParam(m[1], "v"),
  1218.             type: "yt"
  1219.         };
  1220.     }
  1221.  
  1222.     if((m = url.match(/youtu\.be\/([^\?&#]+)/))) {
  1223.         return {
  1224.             id: m[1],
  1225.             type: "yt"
  1226.         };
  1227.     }
  1228.  
  1229.     if((m = url.match(/youtube\.com\/playlist\?([^#]+)/))) {
  1230.         return {
  1231.             id: extractQueryParam(m[1], "list"),
  1232.             type: "yp"
  1233.         };
  1234.     }
  1235.  
  1236.     if((m = url.match(/twitch\.tv\/([^\?&#]+)/))) {
  1237.         return {
  1238.             id: m[1],
  1239.             type: "tw"
  1240.         };
  1241.     }
  1242.  
  1243.     if((m = url.match(/livestream\.com\/([^\?&#]+)/))) {
  1244.         return {
  1245.             id: m[1],
  1246.             type: "li"
  1247.         };
  1248.     }
  1249.  
  1250.     if((m = url.match(/ustream\.tv\/([^\?&#]+)/))) {
  1251.         return {
  1252.             id: m[1],
  1253.             type: "us"
  1254.         };
  1255.     }
  1256.    
  1257.     if ((m = url.match(/hitbox\.tv\/([^\?&#]+)/))) {
  1258.         return {
  1259.             id: m[1],
  1260.             type: "hb"
  1261.         };
  1262.     }
  1263.  
  1264.     if((m = url.match(/vimeo\.com\/([^\?&#]+)/))) {
  1265.         return {
  1266.             id: m[1],
  1267.             type: "vi"
  1268.         };
  1269.     }
  1270.  
  1271.     if((m = url.match(/dailymotion\.com\/video\/([^\?&#_]+)/))) {
  1272.         return {
  1273.             id: m[1],
  1274.             type: "dm"
  1275.         };
  1276.     }
  1277.  
  1278.     if((m = url.match(/imgur\.com\/a\/([^\?&#]+)/))) {
  1279.         return {
  1280.             id: m[1],
  1281.             type: "im"
  1282.         };
  1283.     }
  1284.  
  1285.     if((m = url.match(/soundcloud\.com\/([^\?&#]+)/))) {
  1286.         return {
  1287.             id: url,
  1288.             type: "sc"
  1289.         };
  1290.     }
  1291.  
  1292.     if ((m = url.match(/(?:docs|drive)\.google\.com\/file\/d\/([^\/]*)/))) {
  1293.         return {
  1294.             id: m[1],
  1295.             type: "gd"
  1296.         };
  1297.     }
  1298.  
  1299.     if ((m = url.match(/plus\.google\.com\/(?:u\/\d+\/)?photos\/(\d+)\/albums\/(\d+)\/(\d+)/))) {
  1300.         return {
  1301.             id: m[1] + "_" + m[2] + "_" + m[3],
  1302.             type: "gp"
  1303.         };
  1304.     }
  1305.  
  1306.     if ((m = url.match(/(?:gp:)?(\d{21}_\d{19}_\d{19})/))) {
  1307.         return {
  1308.             id: m[1],
  1309.       type: "gp"
  1310.   };
  1311. }
  1312.  
  1313. if ((m = url.match(/gd:([^\/]{28})/))) {
  1314.   return {
  1315.       id: m[1],
  1316.       type: "gd"
  1317.   };
  1318. }
  1319.  
  1320. if((m = url.match(/yt:([^\?&#]+)/))) {
  1321.   return {
  1322.       id: m[1],
  1323.       type: "yt"
  1324.   };
  1325. }
  1326.  
  1327. if((m = url.match(/dm:([^\?&#_]+)/))) {
  1328.   return {
  1329.       id: m[1],
  1330.       type: "dm"
  1331.   };
  1332. }
  1333.  
  1334. if((m = url.match(/vi:([^\?&#]+)/))) {
  1335.   return {
  1336.       id: m[1],
  1337.       type: "vi"      
  1338.  
  1339.         };
  1340.     }
  1341.  
  1342.     /* Raw file */
  1343.     var tmp = url.split("?")[0];
  1344.     if (tmp.match(/^https?:\/\//)) {
  1345.         if (tmp.match(/\.(mp4|flv|webm|og[gv]|mp3|mov)$/)) {
  1346.             return {
  1347.                 id: url,
  1348.                 type: "fi"
  1349.             };
  1350.         } else {
  1351.             Callbacks.queueFail({
  1352.                 link: url,
  1353.                 msg: "The file you are attempting to queue does not match the supported " +
  1354.                      "file extensions mp4, flv, webm, ogg, ogv, mp3, mov."
  1355.             });
  1356.             throw new Error("ERROR_QUEUE_UNSUPPORTED_EXTENSION");
  1357.         }
  1358.     }
  1359.  
  1360.     return {
  1361.         id: null,
  1362.         type: null
  1363.     };
  1364. }
  1365.  
  1366. function sendVideoUpdate() {
  1367.     if (!CLIENT.leader) {
  1368.         return;
  1369.     }
  1370.     PLAYER.getTime(function (seconds) {
  1371.         socket.emit("mediaUpdate", {
  1372.             id: PLAYER.videoId,
  1373.             currentTime: seconds,
  1374.             paused: PLAYER.paused,
  1375.             type: PLAYER.type
  1376.         });
  1377.     });
  1378. }
  1379.  
  1380. /* chat */
  1381.  
  1382. function formatChatMessage(data, last) {
  1383.     // Backwards compat
  1384.     if (!data.meta || data.msgclass) {
  1385.         data.meta = {
  1386.             addClass: data.msgclass,
  1387.             // And the award for "variable name most like Java source code" goes to...
  1388.             addClassToNameAndTimestamp: data.msgclass
  1389.         };
  1390.     }
  1391.     // Phase 1: Determine whether to show the username or not
  1392.     var skip = data.username === last.name;
  1393.     if(data.meta.addClass === "server-whisper")
  1394.         skip = true;
  1395.     // Prevent impersonation by abuse of the bold filter
  1396.     if(data.msg.match(/^\s*<strong>\w+\s*:\s*<\/strong>\s*/))
  1397.         skip = false;
  1398.     if (data.meta.forceShowName)
  1399.         skip = false;
  1400.  
  1401.     data.msg = execEmotes(data.msg);
  1402.  
  1403.     last.name = data.username;
  1404.     var div = $("<div/>");
  1405.     /* drink is a special case because the entire container gets the class, not
  1406.        just the message */
  1407.     if (data.meta.addClass === "drink") {
  1408.         div.addClass("drink");
  1409.         data.meta.addClass = "";
  1410.     }
  1411.  
  1412.     // Add timestamps (unless disabled)
  1413.     if (USEROPTS.show_timestamps) {
  1414.         var time = $("<span/>").addClass("timestamp").appendTo(div);
  1415.         var timestamp = new Date(data.time).toTimeString().split(" ")[0];
  1416.         time.text("["+timestamp+"] ");
  1417.         if (data.meta.addClass && data.meta.addClassToNameAndTimestamp) {
  1418.             time.addClass(data.meta.addClass);
  1419.         }
  1420.     }
  1421.  
  1422.     // Add username
  1423.     var name = $("<span/>");
  1424.     if (!skip) {
  1425.         name.appendTo(div);
  1426.     }
  1427.     $("<strong/>").addClass("username").text(data.username + ": ").appendTo(name);
  1428.     if (data.meta.modflair) {
  1429.         name.addClass(getNameColor(data.meta.modflair));
  1430.     }
  1431.     if (data.meta.addClass && data.meta.addClassToNameAndTimestamp) {
  1432.         name.addClass(data.meta.addClass);
  1433.     }
  1434.     if (data.meta.superadminflair) {
  1435.         name.addClass("label")
  1436.             .addClass(data.meta.superadminflair.labelclass);
  1437.         $("<span/>").addClass(data.meta.superadminflair.icon)
  1438.             .addClass("glyphicon")
  1439.             .css("margin-right", "3px")
  1440.             .prependTo(name);
  1441.     }
  1442.  
  1443.     // Add the message itself
  1444.     var message = $("<span/>").appendTo(div);
  1445.     message[0].innerHTML = data.msg;
  1446.  
  1447.     // For /me the username is part of the message
  1448.     if (data.meta.action) {
  1449.         name.remove();
  1450.         message[0].innerHTML = data.username + " " + data.msg;
  1451.     }
  1452.     if (data.meta.addClass) {
  1453.         message.addClass(data.meta.addClass);
  1454.     }
  1455.     if (data.meta.shadow) {
  1456.         div.addClass("chat-shadow");
  1457.     }
  1458.  
  1459.     div.find("img").load(function () {
  1460.         if (SCROLLCHAT) {
  1461.             scrollChat();
  1462.         }
  1463.     });
  1464.     return div;
  1465. }
  1466.  
  1467. function addChatMessage(data) {
  1468.     if(IGNORED.indexOf(data.username) !== -1) {
  1469.         return;
  1470.     }
  1471.     if (data.meta.shadow && !USEROPTS.show_shadowchat) {
  1472.         return;
  1473.     }
  1474.     var div = formatChatMessage(data, LASTCHAT);
  1475.     // Incoming: a bunch of crap for the feature where if you hover over
  1476.     // a message, it highlights messages from that user
  1477.     var safeUsername = data.username.replace(/[^\w-]/g, '$');
  1478.     div.addClass("chat-msg-" + safeUsername);
  1479.     div.appendTo($("#messagebuffer"));
  1480.     div.mouseover(function() {
  1481.         $(".chat-msg-" + safeUsername).addClass("nick-hover");
  1482.     });
  1483.     div.mouseleave(function() {
  1484.         $(".nick-hover").removeClass("nick-hover");
  1485.     });
  1486.     // Cap chatbox at most recent 100 messages
  1487.     if($("#messagebuffer").children().length > 100) {
  1488.         $($("#messagebuffer").children()[0]).remove();
  1489.     }
  1490.     if(SCROLLCHAT)
  1491.         scrollChat();
  1492.  
  1493.     var isHighlight = false;
  1494.     if (CLIENT.name && data.username != CLIENT.name) {
  1495.         if (data.msg.toLowerCase().indexOf(CLIENT.name.toLowerCase()) != -1) {
  1496.             div.addClass("nick-highlight");
  1497.             isHighlight = true;
  1498.         }
  1499.     }
  1500.  
  1501.     pingMessage(isHighlight);
  1502.  
  1503. }
  1504.  
  1505. function pingMessage(isHighlight) {
  1506.     if (!FOCUSED) {
  1507.         if (!TITLE_BLINK && (USEROPTS.blink_title === "always" ||
  1508.             USEROPTS.blink_title === "onlyping" && isHighlight)) {
  1509.             TITLE_BLINK = setInterval(function() {
  1510.                 if(document.title == "*Chat*")
  1511.                     document.title = PAGETITLE;
  1512.                 else
  1513.                     document.title = "*Chat*";
  1514.             }, 1000);
  1515.         }
  1516.  
  1517.         if (USEROPTS.boop === "always" || (USEROPTS.boop === "onlyping" &&
  1518.             isHighlight)) {
  1519.             CHATSOUND.play();
  1520.         }
  1521.     }
  1522. }
  1523.  
  1524. /* layouts */
  1525.  
  1526. function compactLayout() {
  1527.     /* Undo synchtube layout */
  1528.     if ($("body").hasClass("synchtube")) {
  1529.         $("#chatwrap").detach().insertBefore($("#videowrap"));
  1530.         $("#leftcontrols").detach().insertBefore($("#rightcontrols"));
  1531.         $("#leftpane").detach().insertBefore($("#rightpane"));
  1532.         $("#userlist").css("float", "left");
  1533.     }
  1534.  
  1535.     /* Undo fluid layout */
  1536.     if ($("body").hasClass("fluid")) {
  1537.         $(".container-fluid").removeClass("container-fluid").addClass("container");
  1538.     }
  1539.  
  1540.     /* Undo HD layout */
  1541.     if ($("body").hasClass("hd")) {
  1542.         $("#drinkbar").detach().removeClass().addClass("col-lg-12 col-md-12")
  1543.           .appendTo("#drinkbarwrap");
  1544.         $("#chatwrap").detach().removeClass().addClass("col-lg-5 col-md-5")
  1545.           .appendTo("#main");
  1546.         $("#videowrap").detach().removeClass().addClass("col-lg-7 col-md-7")
  1547.           .appendTo("#main");
  1548.  
  1549.         $("#leftcontrols").detach().removeClass().addClass("col-lg-5 col-md-5")
  1550.           .prependTo("#controlsrow");
  1551.  
  1552.         $("#plcontrol").detach().appendTo("#rightcontrols");
  1553.         $("#videocontrols").detach().appendTo("#rightcontrols");
  1554.  
  1555.         $("#playlistrow").prepend('<div id="leftpane" class="col-lg-5 col-md-5" />');
  1556.         $("#leftpane").append('<div id="leftpane-inner" class="row" />');
  1557.  
  1558.         $("#pollwrap").detach().removeClass().addClass("col-lg-12 col-md-12")
  1559.           .appendTo("#leftpane-inner");
  1560.         $("#playlistmanagerwrap").detach().removeClass().addClass("col-lg-12 col-md-12")
  1561.           .css("margin-top", "10px")
  1562.           .appendTo("#leftpane-inner");
  1563.  
  1564.         $("#rightpane").detach().removeClass().addClass("col-lg-7 col-md-7")
  1565.           .appendTo("#playlistrow");
  1566.  
  1567.         $("nav").addClass("navbar-fixed-top");
  1568.         $("#mainpage").css("padding-top", "60px");
  1569.         $("#queue").css("max-height", "500px");
  1570.         $("#messagebuffer, #userlist").css("max-height", "");
  1571.         $("body").removeClass("hd");
  1572.     }
  1573.  
  1574.     $("body").addClass("compact");
  1575.     handleVideoResize();
  1576. }
  1577.  
  1578. function fluidLayout() {
  1579.     $(".container").removeClass("container").addClass("container-fluid");
  1580.     $("footer .container-fluid").removeClass("container-fluid").addClass("container");
  1581.     $("body").addClass("fluid");
  1582.     handleVideoResize();
  1583. }
  1584.  
  1585. function synchtubeLayout() {
  1586.     $("#videowrap").detach().insertBefore($("#chatwrap"));
  1587.     $("#rightcontrols").detach().insertBefore($("#leftcontrols"));
  1588.     $("#rightpane").detach().insertBefore($("#leftpane"));
  1589.     $("#userlist").css("float", "right");
  1590.     $("body").addClass("synchtube");
  1591. }
  1592.  
  1593. function hdLayout() {
  1594.     var videowrap = $("#videowrap"),
  1595.         chatwrap = $("#chatwrap"),
  1596.         playlist = $("#rightpane")
  1597.  
  1598.     videowrap.detach().insertAfter($("#drinkbar"))
  1599.         .removeClass()
  1600.         .addClass("col-md-8 col-md-offset-2");
  1601.  
  1602.     playlist.detach().insertBefore(chatwrap)
  1603.         .removeClass()
  1604.         .addClass("col-md-6");
  1605.  
  1606.     chatwrap.removeClass()
  1607.         .addClass("col-md-6");
  1608.  
  1609.     var ch = "320px";
  1610.     $("#messagebuffer").css("max-height", ch);
  1611.     $("#userlist").css("max-height", ch);
  1612.     $("#queue").css("max-height", "312px");
  1613.  
  1614.     $("#leftcontrols").detach()
  1615.         .insertAfter(chatwrap)
  1616.         .removeClass()
  1617.         .addClass("col-md-6");
  1618.  
  1619.     $("#playlistmanagerwrap").detach()
  1620.         .insertBefore($("#leftcontrols"))
  1621.         .css("margin-top", "0")
  1622.         .removeClass()
  1623.         .addClass("col-md-6");
  1624.  
  1625.     $("#showplaylistmanager").addClass("btn-sm");
  1626.  
  1627.     var plcontrolwrap = $("<div/>").addClass("col-md-12")
  1628.         .prependTo($("#rightpane-inner"));
  1629.  
  1630.     $("#plcontrol").detach().appendTo(plcontrolwrap);
  1631.     $("#videocontrols").detach()
  1632.         .appendTo(plcontrolwrap);
  1633.  
  1634.     $("#controlswrap").remove();
  1635.  
  1636.     $("#pollwrap").detach()
  1637.         .insertAfter($("#leftcontrols"))
  1638.         .removeClass()
  1639.         .addClass("col-md-6 col-md-offset-6");
  1640.  
  1641.     $("#leftpane").remove();
  1642.     $("nav.navbar-fixed-top").removeClass("navbar-fixed-top");
  1643.     $("#mainpage").css("padding-top", "0");
  1644.  
  1645.     $("body").addClass("hd");
  1646.     handleVideoResize();
  1647. }
  1648.  
  1649. function chatOnly() {
  1650.     var chat = $("#chatwrap").detach();
  1651.     removeVideo();
  1652.     $("#wrap").remove();
  1653.     $("footer").remove();
  1654.     chat.prependTo($("body"));
  1655.     chat.css({
  1656.         "min-height": "100%",
  1657.         "min-width": "100%",
  1658.         margin: "0",
  1659.         padding: "0"
  1660.     });
  1661.     $("<span/>").addClass("label label-default pull-right pointer")
  1662.         .text("User Options")
  1663.         .appendTo($("#chatheader"))
  1664.         .click(showUserOptions);
  1665.     $("<span/>").addClass("label label-default pull-right pointer")
  1666.         .attr("id", "showchansettings")
  1667.         .text("Channel Settings")
  1668.         .appendTo($("#chatheader"))
  1669.         .click(function () {
  1670.             $("#channeloptions").modal();
  1671.         });
  1672.     setVisible("#showchansettings", CLIENT.rank >= 2);
  1673.     $("body").addClass("chatOnly");
  1674.     handleWindowResize();
  1675. }
  1676.  
  1677. function handleWindowResize() {
  1678.     if ($("body").hasClass("chatOnly")) {
  1679.         var h = $("body").outerHeight() - $("#chatline").outerHeight() -
  1680.                 $("#chatheader").outerHeight();
  1681.         $("#messagebuffer").outerHeight(h);
  1682.         $("#userlist").outerHeight(h);
  1683.         return;
  1684.     } else {
  1685.         handleVideoResize();
  1686.     }
  1687. }
  1688.  
  1689. function handleVideoResize() {
  1690.     if ($("#ytapiplayer").length === 0) return;
  1691.  
  1692.     var intv, ticks = 0;
  1693.     var resize = function () {
  1694.         if (++ticks > 10) clearInterval(intv);
  1695.         if ($("#ytapiplayer").parent().height() === 0) return;
  1696.         clearInterval(intv);
  1697.  
  1698.         var responsiveFrame = $("#ytapiplayer").parent();
  1699.         var height = responsiveFrame.outerHeight() - $("#chatline").outerHeight() - 2;
  1700.         $("#messagebuffer").height(height);
  1701.         $("#userlist").height(height);
  1702.  
  1703.         $("#ytapiplayer").attr("height", VHEIGHT = responsiveFrame.outerHeight());
  1704.         $("#ytapiplayer").attr("width", VWIDTH = responsiveFrame.outerWidth());
  1705.     };
  1706.  
  1707.     if ($("#ytapiplayer").height() > 0) resize();
  1708.     else intv = setInterval(resize, 500);
  1709. }
  1710.  
  1711. $(window).resize(handleWindowResize);
  1712. handleWindowResize();
  1713.  
  1714. function removeVideo() {
  1715.     try {
  1716.         PLAYER.setVolume(0);
  1717.         if (PLAYER.type === "rv") {
  1718.             killVideoUntilItIsDead($(PLAYER.player));
  1719.         }
  1720.     } catch (e) {
  1721.     }
  1722.  
  1723.     $("#videowrap").remove();
  1724.     $("#chatwrap").removeClass("col-lg-5 col-md-5").addClass("col-md-12");
  1725. }
  1726.  
  1727. /* channel administration stuff */
  1728.  
  1729. function genPermissionsEditor() {
  1730.     $("#cs-permedit").html("");
  1731.     var form = $("<form/>").addClass("form-horizontal")
  1732.         .attr("action", "javascript:void(0)")
  1733.         .appendTo($("#cs-permedit"));
  1734.  
  1735.     function makeOption(text, key, permset, defval) {
  1736.         var group = $("<div/>").addClass("form-group")
  1737.             .appendTo(form);
  1738.         $("<label/>").addClass("control-label col-sm-4")
  1739.             .text(text)
  1740.             .appendTo(group);
  1741.         var controls = $("<div/>").addClass("col-sm-8")
  1742.             .appendTo(group);
  1743.         var select = $("<select/>").addClass("form-control")
  1744.             .appendTo(controls)
  1745.             .data("key", key);
  1746.  
  1747.         for (var i = 0; i < permset.length; i++) {
  1748.             $("<option/>").attr("value", permset[i][1])
  1749.                 .text(permset[i][0])
  1750.                 .attr("selected", defval === permset[i][1])
  1751.                 .appendTo(select);
  1752.         }
  1753.     }
  1754.  
  1755.     function addDivider(text, nonewline) {
  1756.         $("<hr/>").appendTo(form);
  1757.         if (!nonewline) {
  1758.             $("<h3/>").text(text).appendTo(form);
  1759.         }
  1760.     }
  1761.  
  1762.     var standard = [
  1763.         ["Anonymous"    , "-1"],
  1764.         ["Guest"        , "0"],
  1765.         ["Registered"   , "1"],
  1766.         ["Leader"       , "1.5"],
  1767.         ["Moderator"    , "2"],
  1768.         ["Channel Admin", "3"],
  1769.         ["Nobody"       , "1000000"]
  1770.     ];
  1771.  
  1772.     var noanon = [
  1773.         ["Guest"        , "0"],
  1774.         ["Registered"   , "1"],
  1775.         ["Leader"       , "1.5"],
  1776.         ["Moderator"    , "2"],
  1777.         ["Channel Admin", "3"],
  1778.         ["Nobody"       , "1000000"]
  1779.     ];
  1780.  
  1781.     var modleader = [
  1782.         ["Leader"       , "1.5"],
  1783.         ["Moderator"    , "2"],
  1784.         ["Channel Admin", "3"],
  1785.         ["Nobody"       , "1000000"]
  1786.     ];
  1787.  
  1788.     var modplus = [
  1789.         ["Moderator"    , "2"],
  1790.         ["Channel Admin", "3"],
  1791.         ["Nobody"       , "1000000"]
  1792.     ];
  1793.  
  1794.     $("<h3/>").text("Open playlist permissions").appendTo(form);
  1795.     makeOption("Add to playlist", "oplaylistadd", standard, CHANNEL.perms.oplaylistadd+"");
  1796.     makeOption("Add/move to next", "oplaylistnext", standard, CHANNEL.perms.oplaylistnext+"");
  1797.     makeOption("Move playlist items", "oplaylistmove", standard, CHANNEL.perms.oplaylistmove+"");
  1798.     makeOption("Delete playlist items", "oplaylistdelete", standard, CHANNEL.perms.oplaylistdelete+"");
  1799.     makeOption("Jump to video", "oplaylistjump", standard, CHANNEL.perms.oplaylistjump+"");
  1800.     makeOption("Queue playlist", "oplaylistaddlist", standard, CHANNEL.perms.oplaylistaddlist+"");
  1801.  
  1802.     addDivider("General playlist permissions");
  1803.     makeOption("View the playlist", "seeplaylist", standard, CHANNEL.perms.seeplaylist+"");
  1804.     makeOption("Add to playlist", "playlistadd", standard, CHANNEL.perms.playlistadd+"");
  1805.     makeOption("Add/move to next", "playlistnext", standard, CHANNEL.perms.playlistnext+"");
  1806.     makeOption("Move playlist items", "playlistmove", standard, CHANNEL.perms.playlistmove+"");
  1807.     makeOption("Delete playlist items", "playlistdelete", standard, CHANNEL.perms.playlistdelete+"");
  1808.     makeOption("Jump to video", "playlistjump", standard, CHANNEL.perms.playlistjump+"");
  1809.     makeOption("Queue playlist", "playlistaddlist", standard, CHANNEL.perms.playlistaddlist+"");
  1810.     makeOption("Queue livestream", "playlistaddlive", standard, CHANNEL.perms.playlistaddlive+"");
  1811.     makeOption("Embed custom media", "playlistaddcustom", standard, CHANNEL.perms.playlistaddcustom + "");
  1812.     makeOption("Add raw video file", "playlistaddrawfile", standard, CHANNEL.perms.playlistaddrawfile + "");
  1813.     makeOption("Exceed maximum media length", "exceedmaxlength", standard, CHANNEL.perms.exceedmaxlength+"");
  1814.     makeOption("Exceed maximum number of videos per user", "exceedmaxitems", standard, CHANNEL.perms.exceedmaxitems+"");
  1815.     makeOption("Add nontemporary media", "addnontemp", standard, CHANNEL.perms.addnontemp+"");
  1816.     makeOption("Temp/untemp playlist item", "settemp", standard, CHANNEL.perms.settemp+"");
  1817.     makeOption("Lock/unlock playlist", "playlistlock", modleader, CHANNEL.perms.playlistlock+"");
  1818.     makeOption("Shuffle playlist", "playlistshuffle", standard, CHANNEL.perms.playlistshuffle+"");
  1819.     makeOption("Clear playlist", "playlistclear", standard, CHANNEL.perms.playlistclear+"");
  1820.  
  1821.     addDivider("Polls");
  1822.     makeOption("Open/Close poll", "pollctl", modleader, CHANNEL.perms.pollctl+"");
  1823.     makeOption("Vote", "pollvote", standard, CHANNEL.perms.pollvote+"");
  1824.     makeOption("View hidden poll results", "viewhiddenpoll", standard, CHANNEL.perms.viewhiddenpoll+"");
  1825.     makeOption("Voteskip", "voteskip", standard, CHANNEL.perms.voteskip+"");
  1826.     makeOption("View voteskip results", "viewvoteskip", standard, CHANNEL.perms.viewvoteskip+"");
  1827.  
  1828.     addDivider("Moderation");
  1829.     makeOption("Assign/Remove leader", "leaderctl", modplus, CHANNEL.perms.leaderctl+"");
  1830.     makeOption("Mute users", "mute", modleader, CHANNEL.perms.mute+"");
  1831.     makeOption("Kick users", "kick", modleader, CHANNEL.perms.kick+"");
  1832.     makeOption("Ban users", "ban", modplus, CHANNEL.perms.ban+"");
  1833.     makeOption("Edit MOTD", "motdedit", modplus, CHANNEL.perms.motdedit+"");
  1834.     makeOption("Edit chat filters", "filteredit", modplus, CHANNEL.perms.filteredit+"");
  1835.     makeOption("Import chat filters", "filterimport", modplus, CHANNEL.perms.filterimport+"");
  1836.     makeOption("Edit chat emotes", "emoteedit", modplus, CHANNEL.perms.emoteedit+"");
  1837.     makeOption("Import chat emotes", "emoteimport", modplus, CHANNEL.perms.emoteimport+"");
  1838.  
  1839.     addDivider("Misc");
  1840.     makeOption("Drink calls", "drink", modleader, CHANNEL.perms.drink+"");
  1841.     makeOption("Chat", "chat", noanon, CHANNEL.perms.chat+"");
  1842.     makeOption("Clear Chat", "chatclear", modleader, CHANNEL.perms.chatclear+"");
  1843.  
  1844.     var sgroup = $("<div/>").addClass("form-group").appendTo(form);
  1845.     var sgroupinner = $("<div/>").addClass("col-sm-8 col-sm-offset-4").appendTo(sgroup);
  1846.     var submit = $("<button/>").addClass("btn btn-primary").appendTo(sgroupinner);
  1847.     submit.text("Save");
  1848.     submit.click(function() {
  1849.         var perms = {};
  1850.         form.find("select").each(function() {
  1851.             perms[$(this).data("key")] = parseFloat($(this).val());
  1852.         });
  1853.         socket.emit("setPermissions", perms);
  1854.     });
  1855.  
  1856.     var msggroup = $("<div/>").addClass("form-group").insertAfter(sgroup);
  1857.     var msginner = $("<div/>").addClass("col-sm-8 col-sm-offset-4").appendTo(msggroup);
  1858.     var text = $("<span/>").addClass("text-info").text("Permissions updated")
  1859.         .appendTo(msginner);
  1860.  
  1861.     setTimeout(function () {
  1862.         msggroup.hide("fade", function () {
  1863.             msggroup.remove();
  1864.         });
  1865.     }, 5000);
  1866. }
  1867.  
  1868. function waitUntilDefined(obj, key, fn) {
  1869.     if(typeof obj[key] === "undefined") {
  1870.         setTimeout(function () {
  1871.             waitUntilDefined(obj, key, fn);
  1872.         }, 100);
  1873.         return;
  1874.     }
  1875.     fn();
  1876. }
  1877.  
  1878. function hidePlayer() {
  1879.     if(!PLAYER)
  1880.         return;
  1881.  
  1882.     if(!/(chrome|MSIE)/ig.test(navigator.userAgent))
  1883.         return;
  1884.  
  1885.     PLAYER.size = {
  1886.         width: $("#ytapiplayer").width(),
  1887.         height: $("#ytapiplayer").height()
  1888.     };
  1889.  
  1890.     $("#ytapiplayer").attr("width", 1)
  1891.         .attr("height", 1);
  1892. }
  1893.  
  1894. function unhidePlayer() {
  1895.     if(!PLAYER)
  1896.         return;
  1897.  
  1898.     if(!/(chrome|MSIE)/ig.test(navigator.userAgent))
  1899.         return;
  1900.  
  1901.     $("#ytapiplayer").width(PLAYER.size.width)
  1902.         .height(PLAYER.size.height);
  1903. }
  1904.  
  1905. function chatDialog(div) {
  1906.     var parent = $("<div/>").addClass("profile-box")
  1907.         .css({
  1908.             padding: "10px",
  1909.             "z-index": "auto",
  1910.             position: "absolute"
  1911.         })
  1912.         .appendTo($("#chatwrap"));
  1913.  
  1914.     div.appendTo(parent);
  1915.     var cw = $("#chatwrap").width();
  1916.     var ch = $("#chatwrap").height();
  1917.     var x = cw/2 - parent.width()/2;
  1918.     var y = ch/2 - parent.height()/2;
  1919.     parent.css("left", x + "px");
  1920.     parent.css("top", y + "px");
  1921.     return parent;
  1922. }
  1923.  
  1924. function errDialog(err) {
  1925.     var div = $("<div/>").addClass("profile-box")
  1926.         .css("padding", "10px")
  1927.         .text(err)
  1928.         .appendTo($("body"));
  1929.  
  1930.     $("<br/>").appendTo(div);
  1931.     $("<button/>").addClass("btn btn-xs btn-default")
  1932.         .css("width", "100%")
  1933.         .text("OK")
  1934.         .click(function () { div.remove(); })
  1935.         .appendTo(div);
  1936.     var cw = $("#chatwrap").width();
  1937.     var ch = $("#chatwrap").height();
  1938.     var cp = $("#chatwrap").offset();
  1939.     var x = cp.left + cw/2 - div.width()/2;
  1940.     var y = cp.top + ch/2 - div.height()/2;
  1941.     div.css("left", x + "px")
  1942.         .css("top", y + "px")
  1943.         .css("position", "absolute");
  1944.     return div;
  1945. }
  1946.  
  1947. function queueMessage(data, type) {
  1948.     if (!data)
  1949.         data = { link: null };
  1950.     if (!data.msg || data.msg === true) {
  1951.         data.msg = "Queue failed.  Check your link to make sure it is valid.";
  1952.     }
  1953.     var ltype = "label-danger";
  1954.     var title = "Error";
  1955.     if (type === "alert-warning") {
  1956.         ltype = "label-warning";
  1957.         title = "Warning";
  1958.     }
  1959.  
  1960.     var alerts = $(".qfalert.qf-" + type + " .alert");
  1961.     for (var i = 0; i < alerts.length; i++) {
  1962.         var al = $(alerts[i]);
  1963.         var cl = al.clone();
  1964.         cl.children().remove();
  1965.         if (cl.text() === data.msg) {
  1966.             var tag = al.find("." + ltype);
  1967.             if (tag.length > 0) {
  1968.                 var morelinks = al.find(".qflinks");
  1969.                 $("<a/>").attr("href", data.link)
  1970.                     .attr("target", "_blank")
  1971.                     .text(data.link)
  1972.                     .appendTo(morelinks);
  1973.                 $("<br/>").appendTo(morelinks);
  1974.                 var count = parseInt(tag.text().match(/\d+/)[0]) + 1;
  1975.                 tag.text(tag.text().replace(/\d+/, ""+count));
  1976.             } else {
  1977.                 var tag = $("<span/>")
  1978.                     .addClass("label pull-right pointer " + ltype)
  1979.                     .text("+ 1 more")
  1980.                     .appendTo(al);
  1981.                 var morelinks = $("<div/>")
  1982.                     .addClass("qflinks")
  1983.                     .appendTo(al)
  1984.                     .hide();
  1985.                 $("<a/>").attr("href", data.link)
  1986.                     .attr("target", "_blank")
  1987.                     .text(data.link)
  1988.                     .appendTo(morelinks);
  1989.                 $("<br/>").appendTo(morelinks);
  1990.                 tag.click(function () {
  1991.                     morelinks.toggle();
  1992.                 });
  1993.             }
  1994.             return;
  1995.         }
  1996.     }
  1997.     var text = data.msg;
  1998.     if (typeof data.link === "string") {
  1999.         text += "<br><a href='" + data.link + "' target='_blank'>" +
  2000.                 data.link + "</a>";
  2001.     }
  2002.     makeAlert(title, text, type)
  2003.         .addClass("qfalert qf-" + type)
  2004.         .appendTo($("#queuefail"));
  2005. }
  2006.  
  2007. function setupChanlogFilter(data) {
  2008.     var getKey = function (ln) {
  2009.         var left = ln.indexOf("[", 1);
  2010.         var right = ln.indexOf("]", left);
  2011.         if (left === -1 || right === -1) {
  2012.             return "unknown";
  2013.         }
  2014.         return ln.substring(left+1, right);
  2015.     };
  2016.  
  2017.     data = data.split("\n").filter(function (ln) {
  2018.         return ln.indexOf("[") === 0 && ln.indexOf("]") > 0;
  2019.     });
  2020.  
  2021.     var log = $("#cs-chanlog-text");
  2022.     var select = $("#cs-chanlog-filter");
  2023.     select.html("");
  2024.     log.data("lines", data);
  2025.  
  2026.     var keys = {};
  2027.     data.forEach(function (ln) {
  2028.         keys[getKey(ln)] = true;
  2029.     });
  2030.  
  2031.     Object.keys(keys).forEach(function (key) {
  2032.         $("<option/>").attr("value", key).text(key).appendTo(select);
  2033.     });
  2034.  
  2035.     $("<option/>").attr("value", "chat").text("chat").prependTo(select);
  2036. }
  2037.  
  2038. function filterChannelLog() {
  2039.     var log = $("#cs-chanlog-text");
  2040.     var filter = $("#cs-chanlog-filter").val();
  2041.     var getKey = function (ln) {
  2042.         var left = ln.indexOf("[", 1);
  2043.         var right = ln.indexOf("]", left);
  2044.         if (left === -1) {
  2045.             return false;
  2046.         }
  2047.         return ln.substring(left+1, right);
  2048.     };
  2049.  
  2050.     var getTimestamp = function (ln) {
  2051.         var right = ln.indexOf("]");
  2052.         return ln.substring(1, right);
  2053.     };
  2054.  
  2055.     var getMessage = function (ln) {
  2056.         var right = ln.indexOf("]");
  2057.         return ln.substring(right + 2);
  2058.     };
  2059.  
  2060.     var show = [];
  2061.     (log.data("lines")||[]).forEach(function (ln) {
  2062.         var key = getKey(ln);
  2063.         if (!filter || !key && filter.indexOf("chat") !== -1) {
  2064.             show.push(ln);
  2065.         } else if (filter.indexOf(key) >= 0) {
  2066.             show.push(ln);
  2067.         }
  2068.     });
  2069.  
  2070.     log.text(show.join("\n"));
  2071.     log.scrollTop(log.prop("scrollHeight"));
  2072. }
  2073.  
  2074. function makeModal() {
  2075.     var wrap = $("<div/>").addClass("modal fade");
  2076.     var dialog = $("<div/>").addClass("modal-dialog").appendTo(wrap);
  2077.     var content = $("<div/>").addClass("modal-content").appendTo(dialog);
  2078.  
  2079.     var head = $("<div/>").addClass("modal-header").appendTo(content);
  2080.     $("<button/>").addClass("close")
  2081.         .attr("data-dismiss", "modal")
  2082.         .attr("data-hidden", "true")
  2083.         .html("&times;")
  2084.         .appendTo(head);
  2085.  
  2086.     wrap.on("hidden.bs.modal", function () {
  2087.         unhidePlayer();
  2088.         wrap.remove();
  2089.     });
  2090.     return wrap;
  2091. }
  2092.  
  2093. function formatCSModList() {
  2094.     var tbl = $("#cs-chanranks table");
  2095.     tbl.find("tbody").remove();
  2096.     var entries = tbl.data("entries") || [];
  2097.     entries.sort(function(a, b) {
  2098.         if (a.rank === b.rank) {
  2099.             var x = a.name.toLowerCase();
  2100.             var y = b.name.toLowerCase();
  2101.             return y == x ? 0 : (x < y ? -1 : 1);
  2102.         }
  2103.  
  2104.         return b.rank - a.rank;
  2105.     });
  2106.  
  2107.     entries.forEach(function (entry) {
  2108.         var tr = $("<tr/>").addClass("cs-chanrank-tr-" + entry.name);
  2109.         var name = $("<td/>").text(entry.name).appendTo(tr);
  2110.         name.addClass(getNameColor(entry.rank));
  2111.         var rankwrap = $("<td/>");
  2112.         var rank = $("<span/>").text(entry.rank).appendTo(rankwrap);
  2113.         var dd = $("<div/>").addClass("btn-group");
  2114.         var toggle = $("<button/>")
  2115.             .addClass("btn btn-xs btn-default dropdown-toggle")
  2116.             .attr("data-toggle", "dropdown")
  2117.             .html("Edit <span class=caret></span>")
  2118.             .appendTo(dd);
  2119.         if (CLIENT.rank <= entry.rank && !(CLIENT.rank === 4 && entry.rank === 4)) {
  2120.             toggle.addClass("disabled");
  2121.         }
  2122.  
  2123.         var menu = $("<ul/>").addClass("dropdown-menu")
  2124.             .attr("role", "menu")
  2125.             .appendTo(dd);
  2126.  
  2127.         var ranks = [
  2128.             { name: "Remove Moderator", rank: 1 },
  2129.             { name: "Moderator", rank: 2 },
  2130.             { name: "Admin", rank: 3 },
  2131.             { name: "Owner", rank: 4 },
  2132.             { name: "Founder", rank: 5 }
  2133.         ];
  2134.  
  2135.         ranks.forEach(function (r) {
  2136.             var li = $("<li/>").appendTo(menu);
  2137.             var a = $("<a/>")
  2138.                 .addClass(getNameColor(r.rank))
  2139.                 .attr("href", "javascript:void(0)")
  2140.                 .text(r.name)
  2141.                 .appendTo(li);
  2142.             if (r.rank !== entry.rank) {
  2143.                 a.click(function () {
  2144.                     socket.emit("setChannelRank", {
  2145.                         name: entry.name,
  2146.                         rank: r.rank
  2147.                     });
  2148.                 });
  2149.             } else {
  2150.                 $("<span/>").addClass("glyphicon glyphicon-ok")
  2151.                     .appendTo(a);
  2152.                 li.addClass("disabled");
  2153.             }
  2154.  
  2155.             if (r.rank > CLIENT.rank || (CLIENT.rank < 4 && r.rank === CLIENT.rank)) {
  2156.                 li.addClass("disabled");
  2157.             }
  2158.         });
  2159.  
  2160.         dd.css("margin-right", "10px").prependTo(rankwrap);
  2161.         rankwrap.appendTo(tr);
  2162.         tr.appendTo(tbl);
  2163.     });
  2164. }
  2165.  
  2166. function formatCSBanlist() {
  2167.     var tbl = $("#cs-banlist table");
  2168.     tbl.find("tbody").remove();
  2169.     var entries = tbl.data("entries") || [];
  2170.     var sparse = {};
  2171.     for (var i = 0; i < entries.length; i++) {
  2172.         if (!(entries[i].name in sparse)) {
  2173.             sparse[entries[i].name] = [];
  2174.         }
  2175.         sparse[entries[i].name].push(entries[i]);
  2176.     }
  2177.  
  2178.     var flat = [];
  2179.     for (var name in sparse) {
  2180.         flat.push({
  2181.             name: name,
  2182.             bans: sparse[name]
  2183.         });
  2184.     }
  2185.     flat.sort(function (a, b) {
  2186.         var x = a.name.toLowerCase(),
  2187.             y = b.name.toLowerCase();
  2188.         return x === y ? 0 : (x > y ? 1 : -1);
  2189.     });
  2190.  
  2191.     var addBanRow = function (entry, after) {
  2192.         var tr = $("<tr/>");
  2193.         if (after) {
  2194.             tr.insertAfter(after);
  2195.         } else {
  2196.             tr.appendTo(tbl);
  2197.         }
  2198.         var unban = $("<button/>").addClass("btn btn-xs btn-danger")
  2199.             .appendTo($("<td/>").appendTo(tr));
  2200.         unban.click(function () {
  2201.             socket.emit("unban", {
  2202.                 id: entry.id,
  2203.                 name: entry.name
  2204.             });
  2205.         });
  2206.         $("<span/>").addClass("glyphicon glyphicon-remove-circle").appendTo(unban);
  2207.         $("<td/>").text(entry.ip).appendTo(tr);
  2208.         $("<td/>").text(entry.name).appendTo(tr);
  2209.         $("<td/>").text(entry.bannedby).appendTo(tr);
  2210.         tr.attr("title", "Ban Reason: " + entry.reason);
  2211.         return tr;
  2212.     };
  2213.  
  2214.     flat.forEach(function (person) {
  2215.         var bans = person.bans;
  2216.         var name = person.name;
  2217.         var first = addBanRow(bans.shift());
  2218.  
  2219.         if (bans.length > 0) {
  2220.             var showmore = $("<button/>").addClass("btn btn-xs btn-default pull-right");
  2221.             $("<span/>").addClass("glyphicon glyphicon-list").appendTo(showmore);
  2222.             showmore.appendTo(first.find("td")[1]);
  2223.  
  2224.             showmore.click(function () {
  2225.                 if (showmore.data("elems")) {
  2226.                     showmore.data("elems").forEach(function (e) {
  2227.                         e.remove();
  2228.                     });
  2229.                     showmore.data("elems", null);
  2230.                 } else {
  2231.                     var elems = [];
  2232.                     bans.forEach(function (b) {
  2233.                         elems.push(addBanRow(b, first));
  2234.                     });
  2235.                     showmore.data("elems", elems);
  2236.                 }
  2237.             });
  2238.         }
  2239.     });
  2240. }
  2241.  
  2242. function checkEntitiesInStr(str) {
  2243.     var entities = {
  2244.         "&": "&amp;",
  2245.         "<": "&lt;",
  2246.         ">": "&gt;",
  2247.         '"': "&quot;",
  2248.         "'": "&#39;",
  2249.         "\\(": "&#40;",
  2250.         "\\)": "&#41;"
  2251.     };
  2252.  
  2253.     var m = str.match(/([&<>"'])|(\\\()|(\\\))/);
  2254.     if (m && m[1] in entities) {
  2255.         return { src: m[1].replace(/^\\/, ""), replace: entities[m[1]] };
  2256.     } else {
  2257.         return false;
  2258.     }
  2259. }
  2260.  
  2261. function formatCSChatFilterList() {
  2262.     var tbl = $("#cs-chatfilters table");
  2263.     tbl.find("tbody").remove();
  2264.     tbl.find(".ui-sortable").remove();
  2265.     var entries = tbl.data("entries") || [];
  2266.     entries.forEach(function (f) {
  2267.         var tr = $("<tr/>").appendTo(tbl);
  2268.         var controlgroup = $("<div/>").addClass("btn-group")
  2269.             .appendTo($("<td/>").appendTo(tr));
  2270.         var control = $("<button/>").addClass("btn btn-xs btn-default")
  2271.             .attr("title", "Edit this filter")
  2272.             .appendTo(controlgroup);
  2273.         $("<span/>").addClass("glyphicon glyphicon-list").appendTo(control);
  2274.         var del = $("<button/>").addClass("btn btn-xs btn-danger")
  2275.             .appendTo(controlgroup);
  2276.         $("<span/>").addClass("glyphicon glyphicon-trash").appendTo(del);
  2277.         del.click(function () {
  2278.             socket.emit("removeFilter", f);
  2279.         });
  2280.         var name = $("<code/>").text(f.name).appendTo($("<td/>").appendTo(tr));
  2281.         var activetd = $("<td/>").appendTo(tr);
  2282.         var active = $("<input/>").attr("type", "checkbox")
  2283.             .prop("checked", f.active)
  2284.             .appendTo(activetd)
  2285.             .change(function () {
  2286.                 f.active = $(this).prop("checked");
  2287.                 socket.emit("updateFilter", f);
  2288.             });
  2289.  
  2290.         var reset = function () {
  2291.             control.data("editor") && control.data("editor").remove();
  2292.             control.data("editor", null);
  2293.             control.parent().find(".btn-success").remove();
  2294.             var tbody = $(tbl.children()[1]);
  2295.             if (tbody.find(".filter-edit-row").length === 0) {
  2296.                 tbody.sortable("enable");
  2297.             }
  2298.         };
  2299.  
  2300.         control.click(function () {
  2301.             if (control.data("editor")) {
  2302.                 return reset();
  2303.             }
  2304.             $(tbl.children()[1]).sortable("disable");
  2305.             var tr2 = $("<tr/>").insertAfter(tr).addClass("filter-edit-row");
  2306.             var wrap = $("<td/>").attr("colspan", "3").appendTo(tr2);
  2307.             var form = $("<form/>").addClass("form-inline").attr("role", "form")
  2308.                 .attr("action", "javascript:void(0)")
  2309.                 .appendTo(wrap);
  2310.             var addTextbox = function (placeholder) {
  2311.                 var div = $("<div/>").addClass("form-group").appendTo(form)
  2312.                     .css("margin-right", "10px");
  2313.                 var input = $("<input/>").addClass("form-control")
  2314.                     .attr("type", "text")
  2315.                     .attr("placeholder", placeholder)
  2316.                     .attr("title", placeholder)
  2317.                     .appendTo(div);
  2318.                 return input;
  2319.             };
  2320.  
  2321.             var regex = addTextbox("Filter regex").val(f.source);
  2322.             var flags = addTextbox("Regex flags").val(f.flags);
  2323.             var replace = addTextbox("Replacement text").val(f.replace);
  2324.  
  2325.             var checkwrap = $("<div/>").addClass("checkbox").appendTo(form);
  2326.             var checklbl = $("<label/>").text("Filter Links").appendTo(checkwrap);
  2327.             var filterlinks = $("<input/>").attr("type", "checkbox")
  2328.                 .prependTo(checklbl)
  2329.                 .prop("checked", f.filterlinks);
  2330.  
  2331.             var save = $("<button/>").addClass("btn btn-xs btn-success")
  2332.                 .attr("title", "Save changes")
  2333.                 .insertAfter(control);
  2334.             $("<span/>").addClass("glyphicon glyphicon-floppy-save").appendTo(save);
  2335.             save.click(function () {
  2336.                 f.source = regex.val();
  2337.                 var entcheck = checkEntitiesInStr(f.source);
  2338.                 if (entcheck) {
  2339.                     alert("Warning: " + entcheck.src + " will be replaced by " +
  2340.                           entcheck.replace + " in the message preprocessor.  This " +
  2341.                           "regular expression may not match what you intended it to " +
  2342.                           "match.");
  2343.                 }
  2344.                 f.flags = flags.val();
  2345.                 f.replace = replace.val();
  2346.                 f.filterlinks = filterlinks.prop("checked");
  2347.  
  2348.                 socket.emit("updateFilter", f);
  2349.                 socket.once("updateFilterSuccess", function () {
  2350.                     reset();
  2351.                 });
  2352.             });
  2353.  
  2354.             control.data("editor", tr2);
  2355.         });
  2356.     });
  2357.     $(tbl.children()[1]).sortable({
  2358.         start: function(ev, ui) {
  2359.             FILTER_FROM = ui.item.prevAll().length;
  2360.         },
  2361.         update: function(ev, ui) {
  2362.             FILTER_TO = ui.item.prevAll().length;
  2363.             if(FILTER_TO != FILTER_FROM) {
  2364.                 socket.emit("moveFilter", {
  2365.                     from: FILTER_FROM,
  2366.                     to: FILTER_TO
  2367.                 });
  2368.             }
  2369.         }
  2370.     });
  2371. }
  2372.  
  2373. function formatCSEmoteList() {
  2374.     var tbl = $("#cs-emotes table");
  2375.     tbl.find("tbody").remove();
  2376.     var entries = CHANNEL.emotes || [];
  2377.     entries.forEach(function (f) {
  2378.         var tr = $("<tr/>").appendTo(tbl);
  2379.         var del = $("<button/>").addClass("btn btn-xs btn-danger")
  2380.             .appendTo($("<td/>").appendTo(tr));
  2381.         $("<span/>").addClass("glyphicon glyphicon-trash").appendTo(del);
  2382.         del.click(function () {
  2383.             socket.emit("removeEmote", f);
  2384.         });
  2385.         var name = $("<code/>").text(f.name).addClass("linewrap")
  2386.             .appendTo($("<td/>").appendTo(tr));
  2387.         var image = $("<code/>").text(f.image).addClass("linewrap")
  2388.             .appendTo($("<td/>").appendTo(tr));
  2389.         image.popover({
  2390.             html: true,
  2391.             trigger: "hover",
  2392.             content: '<img src="' + f.image + '" class="channel-emote">'
  2393.         });
  2394.  
  2395.         image.click(function () {
  2396.             var td = image.parent();
  2397.             td.find(".popover").remove();
  2398.             image.detach();
  2399.             var edit = $("<input/>").addClass("form-control").attr("type", "text")
  2400.                 .appendTo(td);
  2401.  
  2402.             edit.val(f.image);
  2403.             edit.focus();
  2404.  
  2405.             var finish = function () {
  2406.                 var val = edit.val();
  2407.                 edit.remove();
  2408.                 image.appendTo(td);
  2409.                 socket.emit("updateEmote", {
  2410.                     name: f.name,
  2411.                     image: val
  2412.                 });
  2413.             };
  2414.  
  2415.             edit.blur(finish);
  2416.             edit.keyup(function (ev) {
  2417.                 if (ev.keyCode === 13) {
  2418.                     finish();
  2419.                 }
  2420.             });
  2421.         });
  2422.     });
  2423. }
  2424.  
  2425. function formatTime(sec) {
  2426.     var h = Math.floor(sec / 3600) + "";
  2427.     var m = Math.floor((sec % 3600) / 60) + "";
  2428.     var s = sec % 60 + "";
  2429.  
  2430.     if (h.length < 2) {
  2431.         h = "0" + h;
  2432.     }
  2433.  
  2434.     if (m.length < 2) {
  2435.         m = "0" + m;
  2436.     }
  2437.  
  2438.     if (s.length < 2) {
  2439.         s = "0" + s;
  2440.     }
  2441.  
  2442.     if (h === "00") {
  2443.         return [m, s].join(":");
  2444.     } else {
  2445.         return [h, m, s].join(":");
  2446.     }
  2447. }
  2448.  
  2449. function formatUserPlaylistList() {
  2450.     var list = $("#userpl_list").data("entries") || [];
  2451.     list.sort(function (a, b) {
  2452.         var x = a.name.toLowerCase();
  2453.         var y = b.name.toLowerCase();
  2454.         return x == y ? 0 : (x < y ? -1 : 1);
  2455.     });
  2456.  
  2457.     $("#userpl_list").html("");
  2458.     list.forEach(function (pl) {
  2459.         var li = $("<li/>").addClass("queue_entry").appendTo($("#userpl_list"));
  2460.         var title = $("<span/>").addClass("qe_title").appendTo(li)
  2461.             .text(pl.name);
  2462.         var time = $("<span/>").addClass("pull-right").appendTo(li)
  2463.             .text(pl.count + " items, playtime " + formatTime(pl.duration));
  2464.         var clear = $("<div/>").addClass("qe_clear").appendTo(li);
  2465.  
  2466.         var btns = $("<div/>").addClass("btn-group pull-left").prependTo(li);
  2467.         if (hasPermission("playlistadd")) {
  2468.             $("<button/>").addClass("btn btn-xs btn-default")
  2469.                 .text("End")
  2470.                 .appendTo(btns)
  2471.                 .click(function () {
  2472.                     socket.emit("queuePlaylist", {
  2473.                         name: pl.name,
  2474.                         pos: "end",
  2475.                         temp: $(".add-temp").prop("checked")
  2476.                     });
  2477.                 });
  2478.         }
  2479.  
  2480.         if (hasPermission("playlistadd") && hasPermission("playlistnext")) {
  2481.             $("<button/>").addClass("btn btn-xs btn-default")
  2482.                 .text("Next")
  2483.                 .prependTo(btns)
  2484.                 .click(function () {
  2485.                     socket.emit("queuePlaylist", {
  2486.                         name: pl.name,
  2487.                         pos: "next",
  2488.                         temp: $(".add-temp").prop("checked")
  2489.                     });
  2490.                 });
  2491.         }
  2492.  
  2493.         $("<button/>").addClass("btn btn-xs btn-danger")
  2494.             .html("<span class='glyphicon glyphicon-trash'></span>")
  2495.             .attr("title", "Delete playlist")
  2496.             .appendTo(btns)
  2497.             .click(function () {
  2498.                 socket.emit("deletePlaylist", {
  2499.                     name: pl.name
  2500.                 });
  2501.             });
  2502.     });
  2503. }
  2504.  
  2505. function loadEmotes(data) {
  2506.     CHANNEL.emotes = [];
  2507.     data.forEach(function (e) {
  2508.         e.regex = new RegExp(e.source, "gi");
  2509.         CHANNEL.emotes.push(e);
  2510.     });
  2511. }
  2512.  
  2513. function execEmotes(msg) {
  2514.     if (USEROPTS.no_emotes) {
  2515.         return msg;
  2516.     }
  2517.  
  2518.     CHANNEL.emotes.forEach(function (e) {
  2519.         msg = msg.replace(e.regex, '$1<img class="channel-emote" src="' +
  2520.                                    e.image + '" title="' + e.name + '">');
  2521.     });
  2522.  
  2523.     return msg;
  2524. }
  2525.  
  2526. function initPm(user) {
  2527.     if ($("#pm-" + user).length > 0) {
  2528.         return $("#pm-" + user);
  2529.     }
  2530.  
  2531.     var pm = $("<div/>").addClass("panel panel-default pm-panel")
  2532.         .appendTo($("#pmbar"))
  2533.         .data("last", { name: "" })
  2534.         .attr("id", "pm-" + user);
  2535.  
  2536.     var title = $("<div/>").addClass("panel-heading").text(user).appendTo(pm);
  2537.     var close = $("<button/>").addClass("close pull-right")
  2538.         .html("&times;")
  2539.         .appendTo(title).click(function () {
  2540.             pm.remove();
  2541.             $("#pm-placeholder-" + user).remove();
  2542.         });
  2543.  
  2544.     var body = $("<div/>").addClass("panel-body").appendTo(pm).hide();
  2545.     var placeholder;
  2546.     title.click(function () {
  2547.         body.toggle();
  2548.         pm.removeClass("panel-primary").addClass("panel-default");
  2549.         if (!body.is(":hidden")) {
  2550.             placeholder = $("<div/>").addClass("pm-panel-placeholder")
  2551.                 .attr("id", "pm-placeholder-" + user)
  2552.                 .insertAfter(pm);
  2553.             var left = pm.position().left;
  2554.             pm.css("position", "absolute")
  2555.                 .css("bottom", "0px")
  2556.                 .css("left", left);
  2557.         } else {
  2558.             pm.css("position", "");
  2559.             $("#pm-placeholder-" + user).remove();
  2560.         }
  2561.     });
  2562.     var buffer = $("<div/>").addClass("pm-buffer linewrap").appendTo(body);
  2563.     $("<hr/>").appendTo(body);
  2564.     var input = $("<input/>").addClass("form-control pm-input").attr("type", "text")
  2565.         .appendTo(body);
  2566.  
  2567.     input.keyup(function (ev) {
  2568.         if (ev.keyCode === 13) {
  2569.             var meta = {};
  2570.             var msg = input.val();
  2571.             if (msg.trim() === "") {
  2572.                 return;
  2573.             }
  2574.  
  2575.             if (USEROPTS.modhat && CLIENT.rank >= Rank.Moderator) {
  2576.                 meta.modflair = CLIENT.rank;
  2577.             }
  2578.  
  2579.             if (CLIENT.rank >= 2 && msg.indexOf("/m ") === 0) {
  2580.                 meta.modflair = CLIENT.rank;
  2581.                 msg = msg.substring(3);
  2582.             }
  2583.             socket.emit("pm", {
  2584.                 to: user,
  2585.                 msg: msg,
  2586.                 meta: meta
  2587.             });
  2588.             input.val("");
  2589.         }
  2590.     });
  2591.  
  2592.     return pm;
  2593. }
  2594.  
  2595. function killVideoUntilItIsDead(video) {
  2596.     try {
  2597.         video[0].volume = 0;
  2598.         video[0].muted = true;
  2599.         video.attr("src", "");
  2600.         video.remove();
  2601.     } catch (e) {
  2602.     }
  2603. }
  2604.  
  2605. function fallbackRaw(data) {
  2606.     $("<div/>").insertBefore($("#ytapiplayer")).attr("id", "ytapiplayer");
  2607.     $("video").each(function () {
  2608.         killVideoUntilItIsDead($(this));
  2609.     });
  2610.     $("audio").each(function () {
  2611.         killVideoUntilItIsDead($(this));
  2612.     });
  2613.     data.type = "fl";
  2614.     data.url = data.direct.sd.url;
  2615.     PLAYER.player = undefined;
  2616.     PLAYER = new FlashPlayer(data);
  2617.  
  2618.     handleMediaUpdate(data);
  2619. }
  2620.  
  2621. function checkScriptAccess(source, type, cb) {
  2622.     var pref = JSPREF[CHANNEL.name.toLowerCase() + "_" + type];
  2623.     if (pref === "ALLOW") {
  2624.         return cb("ALLOW");
  2625.     } else if (pref !== "DENY") {
  2626.         var div = $("#chanjs-allow-prompt");
  2627.         if (div.length > 0) {
  2628.             setTimeout(function () {
  2629.                 checkScriptAccess(source, type, cb);
  2630.             }, 500);
  2631.             return;
  2632.         }
  2633.  
  2634.         div = $("<div/>").attr("id", "chanjs-allow-prompt");
  2635.         var close = $("<button/>").addClass("close pull-right")
  2636.             .html("&times;")
  2637.             .appendTo(div);
  2638.         var form = $("<form/>")
  2639.             .attr("action", "javascript:void(0)")
  2640.             .attr("id", "chanjs-allow-prompt")
  2641.             .attr("style", "text-align: center")
  2642.             .appendTo(div);
  2643.         form.append("<span>This channel has special features that require your permission to run.</span><br>");
  2644.         $("<a/>").attr("href", source)
  2645.             .attr("target", "_blank")
  2646.             .text(type === "embedded" ? "view embedded script" : source)
  2647.             .appendTo(form);
  2648.         form.append("<div id='chanjs-allow-prompt-buttons'>" +
  2649.                         "<button id='chanjs-allow' class='btn btn-xs btn-danger'>Allow</button>" +
  2650.                         "<button id='chanjs-deny' class='btn btn-xs btn-danger'>Deny</button>" +
  2651.                     "</div>");
  2652.         form.append("<div class='checkbox'><label><input type='checkbox' " +
  2653.                     "id='chanjs-save-pref'/>Remember my choice for this channel" +
  2654.                     "</label></div>");
  2655.         var dialog = chatDialog(div);
  2656.  
  2657.         close.click(function () {
  2658.             dialog.remove();
  2659.             /* Implicit denial of script access */
  2660.             cb("DENY");
  2661.         });
  2662.  
  2663.         $("#chanjs-allow").click(function () {
  2664.             var save = $("#chanjs-save-pref").is(":checked");
  2665.             dialog.remove();
  2666.             if (save) {
  2667.                 JSPREF[CHANNEL.name.toLowerCase() + "_" + type] = "ALLOW";
  2668.                 setOpt("channel_js_pref", JSPREF);
  2669.             }
  2670.             cb("ALLOW");
  2671.         });
  2672.  
  2673.         $("#chanjs-deny").click(function () {
  2674.             var save = $("#chanjs-save-pref").is(":checked");
  2675.             dialog.remove();
  2676.             if (save) {
  2677.                 JSPREF[CHANNEL.name.toLowerCase() + "_" + type] = "DENY";
  2678.                 setOpt("channel_js_pref", JSPREF);
  2679.             }
  2680.             cb("DENY");
  2681.         });
  2682.     }
  2683. }
  2684.  
  2685. function formatScriptAccessPrefs() {
  2686.     var tbl = $("#us-scriptcontrol table");
  2687.     tbl.find("tbody").remove();
  2688.  
  2689.     var channels = Object.keys(JSPREF).sort();
  2690.     channels.forEach(function (channel) {
  2691.         var parts = channel.split("_");
  2692.         if (!parts[1].match(/^(external|embedded)$/)) {
  2693.             return;
  2694.         }
  2695.  
  2696.         var pref = JSPREF[channel];
  2697.         var tr = $("<tr/>").appendTo(tbl);
  2698.         $("<td/>").text(parts[0]).appendTo(tr);
  2699.         $("<td/>").text(parts[1]).appendTo(tr);
  2700.  
  2701.         var pref_td = $("<td/>").appendTo(tr);
  2702.         var allow_label = $("<label/>").addClass("radio-inline")
  2703.             .text("Allow").appendTo(pref_td);
  2704.         var allow = $("<input/>").attr("type", "radio")
  2705.             .prop("checked", pref === "ALLOW").
  2706.             prependTo(allow_label);
  2707.         allow.change(function () {
  2708.             if (allow.is(":checked")) {
  2709.                 JSPREF[channel] = "ALLOW";
  2710.                 setOpt("channel_js_pref", JSPREF);
  2711.                 deny.prop("checked", false);
  2712.             }
  2713.         });
  2714.  
  2715.         var deny_label = $("<label/>").addClass("radio-inline")
  2716.             .text("Deny").appendTo(pref_td);
  2717.         var deny = $("<input/>").attr("type", "radio")
  2718.             .prop("checked", pref === "DENY").
  2719.             prependTo(deny_label);
  2720.         deny.change(function () {
  2721.             if (deny.is(":checked")) {
  2722.                 JSPREF[channel] = "DENY";
  2723.                 setOpt("channel_js_pref", JSPREF);
  2724.                 allow.prop("checked", false);
  2725.             }
  2726.         });
  2727.  
  2728.         var clearpref = $("<button/>").addClass("btn btn-sm btn-danger")
  2729.             .text("Clear Preference")
  2730.             .appendTo($("<td/>").appendTo(tr))
  2731.             .click(function () {
  2732.                 delete JSPREF[channel];
  2733.                 setOpt("channel_js_pref", JSPREF);
  2734.                 tr.remove();
  2735.             });
  2736.     });
  2737. }
  2738.  
  2739. /*
  2740.     VIMEO SIMULATOR 2014
  2741.  
  2742.     Vimeo decided to block my domain.  After repeated emails, they refused to
  2743.     unblock it.  Rather than give in to their demands, there is a serverside
  2744.     option which extracts direct links to the h264 encoded MP4 video files.
  2745.     These files can be loaded in a custom player to allow Vimeo playback without
  2746.     triggering their dumb API domain block.
  2747.  
  2748.     It's a little bit hacky, but my only other option is to keep buying new
  2749.     domains every time one gets blocked.  No thanks to Vimeo, who were of no help
  2750.     and unwilling to compromise on the issue.
  2751. */
  2752. function vimeoSimulator2014(data) {
  2753.     /* Vimeo Simulator uses the raw file player */
  2754.     data.type = "fi";
  2755.  
  2756.     /* Convert youtube-style quality key to vimeo workaround quality */
  2757.     var q = {
  2758.         small: "mobile",
  2759.         medium: "sd",
  2760.         large: "sd",
  2761.         hd720: "hd",
  2762.         hd1080:"hd",
  2763.         highres: "hd"
  2764.     }[USEROPTS.default_quality] || "sd";
  2765.  
  2766.     var fallback = {
  2767.         hd: "sd",
  2768.         sd: "mobile",
  2769.         mobile: false
  2770.     };
  2771.  
  2772.     /* Pick highest quality less than or equal to user's preference from the options */
  2773.     while (!(q in data.meta.direct) && q != false) {
  2774.         q = fallback[q];
  2775.     }
  2776.     if (!q) {
  2777.         q = "sd";
  2778.     }
  2779.  
  2780.     data.url = data.meta.direct[q].url;
  2781.     return data;
  2782. }
  2783.  
  2784. function googlePlusSimulator2014(data) {
  2785.     /* Google+ Simulator uses the raw file player */
  2786.     data.type = "fi";
  2787.  
  2788.     if (!data.meta.gpdirect) {
  2789.         data.url = "";
  2790.         return data;
  2791.     }
  2792.  
  2793.     /* Convert youtube-style quality key to vimeo workaround quality */
  2794.     var q = USEROPTS.default_quality || "auto";
  2795.  
  2796.     var fallbacks = ["hd1080", "hd720", "large", "medium", "small"];
  2797.     var i = fallbacks.indexOf(q);
  2798.     if (i < 0) {
  2799.         i = fallbacks.indexOf("medium");
  2800.     }
  2801.  
  2802.     while (!(q in data.meta.gpdirect) && i < fallbacks.length) {
  2803.         q = fallbacks[i++];
  2804.     }
  2805.  
  2806.     if (i === fallbacks.length) {
  2807.         var hasCodecs = Object.keys(data.meta.gpdirect);
  2808.         if (hasCodecs.length > 0) {
  2809.             q = hasCodecs[0];
  2810.         }
  2811.     }
  2812.  
  2813.     data.url = data.meta.gpdirect[q].url;
  2814.     data.contentType = data.meta.gpdirect[q].contentType;
  2815.     return data;
  2816. }
Add Comment
Please, Sign In to add comment