911558760f
Usage of wmode was specific to Flash, which is long dead.
1353 lines
42 KiB
JavaScript
1353 lines
42 KiB
JavaScript
const Callbacks = {
|
|
/* fired when socket connection completes */
|
|
connect: function() {
|
|
HAS_CONNECTED_BEFORE = true;
|
|
SOCKETIO_CONNECT_ERROR_COUNT = 0;
|
|
$("#socketio-connect-error").remove();
|
|
socket.emit("joinChannel", {
|
|
name: CHANNEL.name
|
|
});
|
|
|
|
if (CHANNEL.opts.password) {
|
|
socket.emit("channelPassword", CHANNEL.opts.password);
|
|
}
|
|
|
|
if (CLIENT.name && CLIENT.guest) {
|
|
socket.emit("login", {
|
|
name: CLIENT.name
|
|
});
|
|
}
|
|
|
|
$("<div/>").addClass("server-msg-reconnect")
|
|
.text("Connected")
|
|
.appendTo($("#messagebuffer"));
|
|
scrollChat();
|
|
stopQueueSpinner(null);
|
|
},
|
|
|
|
disconnect: function() {
|
|
if(KICKED)
|
|
return;
|
|
$("<div/>")
|
|
.addClass("server-msg-disconnect")
|
|
.text("Disconnected from server.")
|
|
.appendTo($("#messagebuffer"));
|
|
scrollChat();
|
|
},
|
|
|
|
reconnect: function () {
|
|
socket.emit("reportReconnect");
|
|
},
|
|
|
|
// Socket.IO error callback
|
|
error: function (msg) {
|
|
window.SOCKET_ERROR_REASON = msg;
|
|
$("<div/>")
|
|
.addClass("server-msg-disconnect")
|
|
.text("Unable to connect: " + msg)
|
|
.appendTo($("#messagebuffer"));
|
|
},
|
|
|
|
errorMsg: function(data) {
|
|
if (data.alert) {
|
|
alert(data.msg);
|
|
} else {
|
|
errDialog(data.msg);
|
|
}
|
|
},
|
|
|
|
costanza: function (data) {
|
|
$("#costanza-modal").modal("hide");
|
|
var modal = makeModal();
|
|
modal.attr("id", "costanza-modal")
|
|
.appendTo($("body"));
|
|
|
|
var body = $("<div/>").addClass("modal-body")
|
|
.appendTo(modal.find(".modal-content"));
|
|
$("<img/>").attr("src", "http://i0.kym-cdn.com/entries/icons/original/000/005/498/1300044776986.jpg")
|
|
.appendTo(body);
|
|
|
|
$("<strong/>").text(data.msg).appendTo(body);
|
|
modal.modal();
|
|
},
|
|
|
|
announcement: function(data) {
|
|
// Suppress this announcement for people who have already closed it
|
|
if (data.id && CyTube.ui.suppressedAnnouncementId
|
|
&& data.id === CyTube.ui.suppressedAnnouncementId) {
|
|
return;
|
|
}
|
|
$("#announcements").html("");
|
|
var signature = "<br>\u2014" + data.from;
|
|
var announcement = makeAlert(data.title, data.text + signature)
|
|
.appendTo($("#announcements"));
|
|
if (data.id) {
|
|
announcement.find(".close").on('click', function suppressThisAnnouncement() {
|
|
CyTube.ui.suppressedAnnouncementId = data.id;
|
|
setOpt("suppressed_announcement_id", data.id);
|
|
});
|
|
}
|
|
},
|
|
|
|
kick: function(data) {
|
|
KICKED = true;
|
|
$("<div/>").addClass("server-msg-disconnect")
|
|
.text("Kicked: " + data.reason)
|
|
.appendTo($("#messagebuffer"));
|
|
scrollChat();
|
|
},
|
|
|
|
noflood: function(data) {
|
|
$("<div/>")
|
|
.addClass("server-msg-disconnect")
|
|
.text(data.action + ": " + data.msg)
|
|
.appendTo($("#messagebuffer"));
|
|
scrollChat();
|
|
},
|
|
|
|
spamFiltered: function(data) {
|
|
var message = "Spam Filtered.";
|
|
switch (data.reason) {
|
|
case "NEW_USER_CHAT":
|
|
message = "Your account is too new to chat in this channel. " +
|
|
"Please wait a while and try again.";
|
|
break;
|
|
case "NEW_USER_CHAT_LINK":
|
|
message = "Your account is too new to post links in this channel. " +
|
|
"Please wait a while and try again.";
|
|
break;
|
|
}
|
|
|
|
errDialog(message);
|
|
},
|
|
|
|
needPassword: function (wrongpw) {
|
|
var div = $("<div/>");
|
|
$("<strong/>").text("Channel Password")
|
|
.appendTo(div);
|
|
if (wrongpw) {
|
|
$("<br/>").appendTo(div);
|
|
$("<span/>").addClass("text-error")
|
|
.text("Wrong Password")
|
|
.appendTo(div);
|
|
}
|
|
|
|
var pwbox = $("<input/>").addClass("form-control")
|
|
.attr("type", "password")
|
|
.appendTo(div);
|
|
var submit = $("<button/>").addClass("btn btn-xs btn-default btn-block")
|
|
.css("margin-top", "5px")
|
|
.text("Submit")
|
|
.appendTo(div);
|
|
var parent = chatDialog(div, '9999');
|
|
parent.attr("id", "needpw");
|
|
var sendpw = function () {
|
|
socket.emit("channelPassword", pwbox.val());
|
|
parent.remove();
|
|
};
|
|
submit.click(sendpw);
|
|
pwbox.keydown(function (ev) {
|
|
if (ev.keyCode == 13) {
|
|
sendpw();
|
|
}
|
|
});
|
|
pwbox.focus();
|
|
},
|
|
|
|
cancelNeedPassword: function () {
|
|
$("#needpw").remove();
|
|
},
|
|
|
|
cooldown: function (time) {
|
|
time = time + 200;
|
|
$("#chatline").css("color", "#ff0000");
|
|
$(".pm-input").css("color", "#ff0000");
|
|
if (CHATTHROTTLE && $("#chatline").data("throttle_timer")) {
|
|
clearTimeout($("#chatline").data("throttle_timer"));
|
|
}
|
|
CHATTHROTTLE = true;
|
|
$("#chatline").data("throttle_timer", setTimeout(function () {
|
|
CHATTHROTTLE = false;
|
|
$("#chatline").css("color", "");
|
|
$(".pm-input").css("color", "");
|
|
}, time));
|
|
},
|
|
|
|
channelNotRegistered: function() {
|
|
var div = $("<div/>").addClass("alert alert-info")
|
|
.appendTo($("<div/>").addClass("col-md-12").appendTo($("#announcements")));
|
|
|
|
$("<button/>").addClass("close pull-right")
|
|
.appendTo(div)
|
|
.on('click', function () {
|
|
div.parent().remove();
|
|
})
|
|
.html("×");
|
|
$("<h4/>").appendTo(div).text("Unregistered channel");
|
|
$("<p/>").appendTo(div)
|
|
.html("This channel is not registered to a CyTube account. You can still " +
|
|
"use it, but some features will not be available. To register a " +
|
|
"channel to your account, visit your <a href='/account/channels'>" +
|
|
"channels</a> page.");
|
|
},
|
|
|
|
setMotd: function(motd) {
|
|
CHANNEL.motd = motd;
|
|
$("#motd").html(motd);
|
|
$("#cs-motdtext").val(motd);
|
|
if (motd != "") {
|
|
$("#motdwrap").show();
|
|
$("#motd").show();
|
|
$("#togglemotd").find(".glyphicon-plus")
|
|
.removeClass("glyphicon-plus")
|
|
.addClass("glyphicon-minus");
|
|
} else {
|
|
$("#motdwrap").hide();
|
|
}
|
|
},
|
|
|
|
chatFilters: function(entries) {
|
|
var tbl = $("#cs-chatfilters table");
|
|
tbl.data("entries", entries);
|
|
formatCSChatFilterList();
|
|
},
|
|
|
|
updateChatFilter: function (f) {
|
|
var entries = $("#cs-chatfilters table").data("entries") || [];
|
|
var found = false;
|
|
for (var i = 0; i < entries.length; i++) {
|
|
if (entries[i].name === f.name) {
|
|
entries[i] = f;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
entries.push(f);
|
|
}
|
|
|
|
$("#cs-chatfilters table").data("entries", entries);
|
|
formatCSChatFilterList();
|
|
},
|
|
|
|
deleteChatFilter: function (f) {
|
|
var entries = $("#cs-chatfilters table").data("entries") || [];
|
|
var found = false;
|
|
for (var i = 0; i < entries.length; i++) {
|
|
if (entries[i].name === f.name) {
|
|
entries[i] = f;
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found !== false) {
|
|
entries.splice(found, 1);
|
|
}
|
|
|
|
$("#cs-chatfilters table").data("entries", entries);
|
|
formatCSChatFilterList();
|
|
},
|
|
|
|
channelOpts: function(opts) {
|
|
document.title = opts.pagetitle;
|
|
PAGETITLE = opts.pagetitle;
|
|
|
|
if (!USEROPTS.ignore_channelcss &&
|
|
opts.externalcss !== CHANNEL.opts.externalcss) {
|
|
$("#chanexternalcss").remove();
|
|
|
|
if (opts.externalcss.trim() !== "") {
|
|
$("#chanexternalcss").remove();
|
|
$("<link/>")
|
|
.attr("rel", "stylesheet")
|
|
.attr("href", opts.externalcss)
|
|
.attr("id", "chanexternalcss")
|
|
.on("load", function () {
|
|
handleVideoResize();
|
|
})
|
|
.appendTo($("head"));
|
|
}
|
|
}
|
|
|
|
if(opts.externaljs.trim() != "" && !USEROPTS.ignore_channeljs &&
|
|
opts.externaljs !== CHANNEL.opts.externaljs) {
|
|
var viewSource = document.createElement("a");
|
|
viewSource.className = "btn btn-danger";
|
|
viewSource.setAttribute("role", "button");
|
|
viewSource.setAttribute("target", "_blank");
|
|
viewSource.setAttribute("rel", "noopener noreferer");
|
|
viewSource.textContent = "View external script source";
|
|
viewSource.href = opts.externaljs;
|
|
checkScriptAccess(viewSource, "external", function (pref) {
|
|
if (pref === "ALLOW") {
|
|
$.getScript(opts.externaljs);
|
|
}
|
|
});
|
|
}
|
|
|
|
CHANNEL.opts = opts;
|
|
|
|
if(opts.allow_voteskip)
|
|
$("#voteskip").attr("disabled", false);
|
|
else
|
|
$("#voteskip").attr("disabled", true);
|
|
handlePermissionChange();
|
|
},
|
|
|
|
setPermissions: function(perms) {
|
|
CHANNEL.perms = perms;
|
|
genPermissionsEditor();
|
|
handlePermissionChange();
|
|
},
|
|
|
|
channelCSSJS: function(data) {
|
|
if (CyTube.channelCustomizations.cssHash !== data.cssHash) {
|
|
$("#chancss").remove();
|
|
CHANNEL.css = data.css;
|
|
$("#cs-csstext").val(data.css);
|
|
if(data.css && !USEROPTS.ignore_channelcss) {
|
|
$("<style/>").attr("type", "text/css")
|
|
.attr("id", "chancss")
|
|
.text(data.css)
|
|
.on("load", function () {
|
|
handleVideoResize();
|
|
})
|
|
.appendTo($("head"));
|
|
}
|
|
|
|
if (data.cssHash) {
|
|
CyTube.channelCustomizations.cssHash = data.cssHash;
|
|
}
|
|
}
|
|
|
|
if (CyTube.channelCustomizations.jsHash !== data.jsHash) {
|
|
$("#chanjs").remove();
|
|
CHANNEL.js = data.js;
|
|
$("#cs-jstext").val(data.js);
|
|
|
|
if(data.js && !USEROPTS.ignore_channeljs) {
|
|
var viewSource = document.createElement("button");
|
|
viewSource.className = "btn btn-danger";
|
|
viewSource.textContent = "View inline script source";
|
|
viewSource.onclick = function () {
|
|
var content = document.createElement("pre");
|
|
content.textContent = data.js;
|
|
modalAlert({
|
|
title: "Inline JS",
|
|
htmlContent: content.outerHTML,
|
|
dismissText: "Close"
|
|
});
|
|
};
|
|
|
|
checkScriptAccess(viewSource, "embedded", function (pref) {
|
|
if (pref === "ALLOW") {
|
|
$("<script/>").attr("type", "text/javascript")
|
|
.attr("id", "chanjs")
|
|
.text(data.js)
|
|
.appendTo($("body"));
|
|
}
|
|
});
|
|
}
|
|
|
|
if (data.jsHash) {
|
|
CyTube.channelCustomizations.jsHash = data.jsHash;
|
|
}
|
|
}
|
|
},
|
|
|
|
banlist: function(entries) {
|
|
var tbl = $("#cs-banlist table");
|
|
tbl.data("entries", entries);
|
|
formatCSBanlist();
|
|
},
|
|
|
|
banlistRemove: function (data) {
|
|
var entries = $("#cs-banlist table").data("entries") || [];
|
|
var found = false;
|
|
for (var i = 0; i < entries.length; i++) {
|
|
if (entries[i].id === data.id) {
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found !== false) {
|
|
entries.splice(i, 1);
|
|
$("#cs-banlist table").data("entries", entries);
|
|
}
|
|
|
|
formatCSBanlist();
|
|
},
|
|
|
|
channelRanks: function(entries) {
|
|
var tbl = $("#cs-chanranks table");
|
|
tbl.data("entries", entries);
|
|
formatCSModList();
|
|
},
|
|
|
|
channelRankFail: function (data) {
|
|
if ($("#cs-chanranks").is(":visible")) {
|
|
makeAlert("Error", data.msg, "alert-danger")
|
|
.removeClass().addClass("vertical-spacer")
|
|
.insertAfter($("#cs-chanranks form"));
|
|
} else {
|
|
Callbacks.noflood({ action: "/rank", msg: data.msg });
|
|
}
|
|
},
|
|
|
|
readChanLog: function (data) {
|
|
var log = $("#cs-chanlog-text");
|
|
if (log.length == 0)
|
|
return;
|
|
|
|
if (data.success) {
|
|
setupChanlogFilter(data.data);
|
|
filterChannelLog();
|
|
} else {
|
|
$("#cs-chanlog-text").text("Error reading channel log");
|
|
}
|
|
},
|
|
|
|
voteskip: function(data) {
|
|
var icon = $("#voteskip").find(".glyphicon").remove();
|
|
if(data.count > 0) {
|
|
$("#voteskip").text(" ("+data.count+"/"+data.need+")");
|
|
} else {
|
|
$("#voteskip").text("");
|
|
}
|
|
|
|
icon.prependTo($("#voteskip"));
|
|
},
|
|
|
|
/* REGION Rank Stuff */
|
|
|
|
rank: function(r) {
|
|
if(r >= 255)
|
|
SUPERADMIN = true;
|
|
CLIENT.rank = r;
|
|
handlePermissionChange();
|
|
if(SUPERADMIN && $("#setrank").length == 0) {
|
|
var li = $("<li/>").addClass("dropdown")
|
|
.attr("id", "setrank")
|
|
.appendTo($(".nav")[0]);
|
|
$("<a/>").addClass("dropdown-toggle")
|
|
.attr("data-toggle", "dropdown")
|
|
.attr("href", "javascript:void(0)")
|
|
.html("Set Rank <b class='caret'></b>")
|
|
.appendTo(li);
|
|
var menu = $("<ul/>").addClass("dropdown-menu")
|
|
.appendTo(li);
|
|
|
|
function addRank(r, disp) {
|
|
var li = $("<li/>").appendTo(menu);
|
|
$("<a/>").attr("href", "javascript:void(0)")
|
|
.html(disp)
|
|
.on('click', function() {
|
|
socket.emit("borrow-rank", r);
|
|
})
|
|
.appendTo(li);
|
|
}
|
|
|
|
addRank(0, "<span class='userlist_guest'>Guest</span>");
|
|
addRank(1, "<span>Registered</span>");
|
|
addRank(2, "<span class='userlist_op'>Moderator</span>");
|
|
addRank(3, "<span class='userlist_owner'>Admin</span>");
|
|
addRank(255, "<span class='userlist_siteadmin'>Superadmin</span>");
|
|
}
|
|
},
|
|
|
|
login: function(data) {
|
|
if (!data.success) {
|
|
if (data.error != "Session expired") {
|
|
errDialog(data.error);
|
|
}
|
|
} else {
|
|
CLIENT.name = data.name;
|
|
CLIENT.guest = data.guest;
|
|
CLIENT.logged_in = true;
|
|
|
|
if (!CLIENT.guest) {
|
|
socket.emit("initUserPLCallbacks");
|
|
}
|
|
}
|
|
},
|
|
|
|
/* REGION Chat */
|
|
usercount: function(count) {
|
|
CHANNEL.usercount = count;
|
|
var text = count + " connected user";
|
|
if(count != 1) {
|
|
text += "s";
|
|
}
|
|
$("#usercount").text(text);
|
|
},
|
|
|
|
chatMsg: function(data) {
|
|
addChatMessage(data);
|
|
},
|
|
|
|
pm: function (data) {
|
|
var name = data.username;
|
|
if (IGNORED.indexOf(name) !== -1) {
|
|
return;
|
|
}
|
|
|
|
var ping = false;
|
|
|
|
if (data.username === CLIENT.name) {
|
|
name = data.to;
|
|
} else {
|
|
ping = true;
|
|
}
|
|
var pm = initPm(name);
|
|
var msg = formatChatMessage(data, pm.data("last"));
|
|
var buffer = pm.find(".pm-buffer");
|
|
msg.appendTo(buffer);
|
|
buffer.scrollTop(buffer.prop("scrollHeight"));
|
|
if (pm.find(".panel-body").is(":hidden")) {
|
|
pm.removeClass("panel-default").addClass("panel-primary");
|
|
}
|
|
|
|
if (ping) {
|
|
pingMessage(true, "PM: " + name, $(msg.children()[2]).text());
|
|
}
|
|
},
|
|
|
|
clearchat: function() {
|
|
$("#messagebuffer").html("");
|
|
LASTCHAT.name = "";
|
|
},
|
|
|
|
userlist: function(data) {
|
|
$(".userlist_item").remove();
|
|
for(var i = 0; i < data.length; i++) {
|
|
CyTube._internal_do_not_use_or_you_will_be_banned.addUserToList(data[i], false);
|
|
}
|
|
sortUserlist();
|
|
},
|
|
|
|
addUser: function(data) {
|
|
CyTube._internal_do_not_use_or_you_will_be_banned.addUserToList(data, true);
|
|
sortUserlist();
|
|
},
|
|
|
|
setUserMeta: function (data) {
|
|
var user = findUserlistItem(data.name);
|
|
if (user == null) {
|
|
return;
|
|
}
|
|
|
|
user.data("meta", data.meta);
|
|
if (data.meta.muted || data.meta.smuted) {
|
|
user.data("icon", "glyphicon-volume-off");
|
|
} else {
|
|
user.data("icon", false);
|
|
}
|
|
|
|
/*
|
|
* 2017-06-15
|
|
* TODO: Remove this and the empty function below
|
|
* after script authors have had ample time to update
|
|
*/
|
|
socket.listeners('setAFK').forEach(function(listener){
|
|
listener({ name: data.name, afk: data.meta.afk });
|
|
});
|
|
|
|
formatUserlistItem(user, data);
|
|
addUserDropdown(user, data);
|
|
sortUserlist();
|
|
},
|
|
|
|
setAFK: function() {
|
|
return true;
|
|
},
|
|
|
|
setUserProfile: function (data) {
|
|
var user = findUserlistItem(data.name);
|
|
if (user === null)
|
|
return;
|
|
user.data("profile", data.profile);
|
|
formatUserlistItem(user);
|
|
},
|
|
|
|
setLeader: function (name) {
|
|
$(".userlist_item").each(function () {
|
|
$(this).find(".glyphicon-star-empty").remove();
|
|
if ($(this).data("leader")) {
|
|
$(this).data("leader", false);
|
|
addUserDropdown($(this));
|
|
}
|
|
});
|
|
if (name === "") {
|
|
CLIENT.leader = false;
|
|
if(LEADTMR)
|
|
clearInterval(LEADTMR);
|
|
LEADTMR = false;
|
|
return;
|
|
}
|
|
var user = findUserlistItem(name);
|
|
if (user) {
|
|
user.data("leader", true);
|
|
formatUserlistItem(user);
|
|
addUserDropdown(user);
|
|
}
|
|
if (name === CLIENT.name) {
|
|
CLIENT.leader = true;
|
|
// I'm a leader! Set up sync function
|
|
if(LEADTMR)
|
|
clearInterval(LEADTMR);
|
|
LEADTMR = setInterval(sendVideoUpdate, 5000);
|
|
handlePermissionChange();
|
|
} else if (CLIENT.leader) {
|
|
CLIENT.leader = false;
|
|
handlePermissionChange();
|
|
if(LEADTMR)
|
|
clearInterval(LEADTMR);
|
|
LEADTMR = false;
|
|
}
|
|
},
|
|
|
|
setUserRank: function (data) {
|
|
data.name = data.name.toLowerCase();
|
|
var entries = $("#cs-chanranks table").data("entries") || [];
|
|
var found = false;
|
|
for (var i = 0; i < entries.length; i++) {
|
|
if (entries[i].name.toLowerCase() === data.name) {
|
|
entries[i].rank = data.rank;
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
if (found === false) {
|
|
entries.push(data);
|
|
} else if (entries[found].rank < 2) {
|
|
entries.splice(found, 1);
|
|
}
|
|
formatCSModList();
|
|
|
|
var user = findUserlistItem(data.name);
|
|
if (user === null) {
|
|
return;
|
|
}
|
|
|
|
user.data("rank", data.rank);
|
|
if (data.name === CLIENT.name) {
|
|
CLIENT.rank = data.rank;
|
|
handlePermissionChange();
|
|
}
|
|
formatUserlistItem(user);
|
|
addUserDropdown(user);
|
|
if (USEROPTS.sort_rank) {
|
|
sortUserlist();
|
|
}
|
|
},
|
|
|
|
userLeave: function(data) {
|
|
var user = findUserlistItem(data.name);
|
|
if(user !== null)
|
|
user.remove();
|
|
},
|
|
|
|
drinkCount: function(count) {
|
|
if(count != 0) {
|
|
var text = count + " drink";
|
|
if(count != 1) {
|
|
text += "s";
|
|
}
|
|
$("#drinkcount").text(text);
|
|
$("#drinkbar").show();
|
|
} else {
|
|
$("#drinkbar").hide();
|
|
}
|
|
},
|
|
|
|
/* REGION Playlist Stuff */
|
|
playlist: function(data) {
|
|
PL_QUEUED_ACTIONS = [];
|
|
// Clear the playlist first
|
|
var q = $("#queue");
|
|
q.html("");
|
|
|
|
for(var i = 0; i < data.length; i++) {
|
|
var li = makeQueueEntry(data[i], false);
|
|
li.attr("title", data[i].queueby
|
|
? ("Added by: " + data[i].queueby)
|
|
: "Added by: Unknown");
|
|
li.appendTo(q);
|
|
}
|
|
|
|
rebuildPlaylist();
|
|
},
|
|
|
|
setPlaylistMeta: function(data) {
|
|
var c = data.count + " item";
|
|
if(data.count != 1)
|
|
c += "s";
|
|
$("#plcount").text(c);
|
|
$("#pllength").text(data.time);
|
|
},
|
|
|
|
queue: function(data) {
|
|
PL_ACTION_QUEUE.queue(function (plq) {
|
|
stopQueueSpinner(data.item.media);
|
|
var li = makeQueueEntry(data.item, true);
|
|
if (data.item.uid === PL_CURRENT)
|
|
li.addClass("queue_active");
|
|
li.hide();
|
|
var q = $("#queue");
|
|
li.attr("title", data.item.queueby
|
|
? ("Added by: " + data.item.queueby)
|
|
: "Added by: Unknown");
|
|
if (data.after === "prepend") {
|
|
li.prependTo(q);
|
|
li.show("fade", function () {
|
|
plq.release();
|
|
});
|
|
} else if (data.after === "append") {
|
|
li.appendTo(q);
|
|
li.show("fade", function () {
|
|
plq.release();
|
|
});
|
|
} else {
|
|
var liafter = playlistFind(data.after);
|
|
if (!liafter) {
|
|
plq.release();
|
|
return;
|
|
}
|
|
li.insertAfter(liafter);
|
|
li.show("fade", function () {
|
|
plq.release();
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
queueWarn: function (data) {
|
|
queueMessage(data, "alert-warning");
|
|
},
|
|
|
|
queueFail: function (data) {
|
|
if (data.id) {
|
|
stopQueueSpinner(data);
|
|
}
|
|
queueMessage(data, "alert-danger");
|
|
},
|
|
|
|
setTemp: function(data) {
|
|
var li = $(".pluid-" + data.uid);
|
|
if(li.length == 0)
|
|
return false;
|
|
|
|
if(data.temp)
|
|
li.addClass("queue_temp");
|
|
else
|
|
li.removeClass("queue_temp");
|
|
|
|
li.data("temp", data.temp);
|
|
var btn = li.find(".qbtn-tmp");
|
|
if(btn.length > 0) {
|
|
if(data.temp) {
|
|
btn.html(btn.html().replace("Make Temporary",
|
|
"Make Permanent"));
|
|
} else {
|
|
btn.html(btn.html().replace("Make Permanent",
|
|
"Make Temporary"));
|
|
}
|
|
}
|
|
},
|
|
|
|
"delete": function(data) {
|
|
PL_ACTION_QUEUE.queue(function (plq) {
|
|
PL_WAIT_SCROLL = true;
|
|
var li = $(".pluid-" + data.uid);
|
|
li.hide("fade", function() {
|
|
li.remove();
|
|
plq.release();
|
|
PL_WAIT_SCROLL = false;
|
|
});
|
|
});
|
|
},
|
|
|
|
moveVideo: function(data) {
|
|
PL_ACTION_QUEUE.queue(function (plq) {
|
|
playlistMove(data.from, data.after, function () {
|
|
plq.release();
|
|
});
|
|
});
|
|
},
|
|
|
|
setCurrent: function(uid) {
|
|
PL_CURRENT = uid;
|
|
$("#queue li").removeClass("queue_active");
|
|
var li = $(".pluid-" + uid);
|
|
if (li.length !== 0) {
|
|
li.addClass("queue_active");
|
|
var tmr = setInterval(function () {
|
|
if (!PL_WAIT_SCROLL) {
|
|
scrollQueue();
|
|
clearInterval(tmr);
|
|
}
|
|
}, 100);
|
|
}
|
|
},
|
|
|
|
changeMedia: function(data) {
|
|
if ($("body").hasClass("chatOnly") || $("#videowrap").length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Failsafe
|
|
if (isNaN(VOLUME) || VOLUME > 1 || VOLUME < 0) {
|
|
VOLUME = 1;
|
|
}
|
|
|
|
function loadNext() {
|
|
if (!PLAYER || data.type !== PLAYER.mediaType) {
|
|
loadMediaPlayer(data);
|
|
} else {
|
|
handleMediaUpdate(data);
|
|
}
|
|
}
|
|
|
|
// Persist the user's volume preference from the the player, if possible
|
|
if (PLAYER && typeof PLAYER.getVolume === "function") {
|
|
PLAYER.getVolume(function (v) {
|
|
if (typeof v === "number") {
|
|
if (v < 0 || v > 1) {
|
|
// Dailymotion's API was wrong once and caused a huge
|
|
// headache. This alert is here to make debugging easier.
|
|
alert("Something went wrong with retrieving the volume. " +
|
|
"Please tell calzoneman the following: " +
|
|
JSON.stringify({
|
|
v: v,
|
|
t: PLAYER.mediaType,
|
|
i: PLAYER.mediaId
|
|
}));
|
|
} else {
|
|
VOLUME = v;
|
|
setOpt("volume", VOLUME);
|
|
}
|
|
}
|
|
|
|
loadNext();
|
|
});
|
|
} else {
|
|
loadNext();
|
|
}
|
|
|
|
// Reset voteskip since the video changed
|
|
if (CHANNEL.opts.allow_voteskip) {
|
|
$("#voteskip").attr("disabled", false);
|
|
}
|
|
|
|
$("#currenttitle").text("Currently Playing: " + data.title);
|
|
},
|
|
|
|
mediaUpdate: function(data) {
|
|
if ($("body").hasClass("chatOnly") || $("#videowrap").length === 0) {
|
|
return;
|
|
}
|
|
|
|
if (PLAYER) {
|
|
handleMediaUpdate(data);
|
|
}
|
|
},
|
|
|
|
setPlaylistLocked: function (locked) {
|
|
CHANNEL.openqueue = !locked;
|
|
handlePermissionChange();
|
|
if(CHANNEL.openqueue) {
|
|
$("#qlockbtn").removeClass("btn-danger")
|
|
.addClass("btn-success")
|
|
.attr("title", "Playlist Unlocked");
|
|
$("#qlockbtn").find("span")
|
|
.removeClass("glyphicon-lock")
|
|
.addClass("glyphicon-ok");
|
|
} else {
|
|
$("#qlockbtn").removeClass("btn-success")
|
|
.addClass("btn-danger")
|
|
.attr("title", "Playlist Locked");
|
|
$("#qlockbtn").find("span")
|
|
.removeClass("glyphicon-ok")
|
|
.addClass("glyphicon-lock");
|
|
}
|
|
},
|
|
|
|
searchResults: function(data) {
|
|
$("#search_clear").remove();
|
|
clearSearchResults();
|
|
$("#library").data("entries", data.results);
|
|
$("<button/>").addClass("btn btn-default btn-sm btn-block")
|
|
.css("margin-left", "0")
|
|
.attr("id", "search_clear")
|
|
.text("Clear Results")
|
|
.on('click', function() {
|
|
clearSearchResults();
|
|
})
|
|
.insertBefore($("#library"));
|
|
|
|
|
|
$("#search_pagination").remove();
|
|
var opts = {
|
|
preLoadPage: function () {
|
|
$("#library").html("");
|
|
},
|
|
|
|
generator: function (item, page, index) {
|
|
var li = makeSearchEntry(item, false);
|
|
if(hasPermission("playlistadd") || hasPermission("deletefromchannellib")) {
|
|
addLibraryButtons(li, item, data.source);
|
|
}
|
|
$(li).appendTo($("#library"));
|
|
},
|
|
|
|
itemsPerPage: 100
|
|
};
|
|
|
|
var p = Paginate(data.results, opts);
|
|
p.paginator.insertAfter($("#library"))
|
|
.addClass("pull-right")
|
|
.attr("id", "search_pagination");
|
|
$("#library").data("paginator", p);
|
|
},
|
|
|
|
/* REGION Polls */
|
|
newPoll: function(data) {
|
|
Callbacks.closePoll();
|
|
var pollMsg = $("<div/>").addClass("poll-notify")
|
|
.html(data.initiator + " opened a poll: \"" + data.title + "\"")
|
|
.appendTo($("#messagebuffer"));
|
|
scrollChat();
|
|
|
|
var poll = $("<div/>").addClass("well active").prependTo($("#pollwrap"));
|
|
$("<button/>").addClass("close pull-right").html("×")
|
|
.appendTo(poll)
|
|
.on('click', function() { poll.remove(); });
|
|
if(hasPermission("pollctl")) {
|
|
$("<button/>").addClass("btn btn-danger btn-sm pull-right").text("End Poll")
|
|
.appendTo(poll)
|
|
.on('click', function() {
|
|
socket.emit("closePoll");
|
|
});
|
|
}
|
|
|
|
$("<h3/>").html(data.title).appendTo(poll);
|
|
for(var i = 0; i < data.options.length; i++) {
|
|
(function(i) {
|
|
var callback = function () {
|
|
socket.emit("vote", {
|
|
option: i
|
|
});
|
|
poll.find(".option button").each(function() {
|
|
$(this).removeClass("active");
|
|
$(this).parent().removeClass("option-selected");
|
|
});
|
|
$(this).addClass("active");
|
|
$(this).parent().addClass("option-selected");
|
|
};
|
|
$("<button/>").addClass("btn btn-default btn-sm").text(data.counts[i])
|
|
.prependTo($("<div/>").addClass("option").html(data.options[i])
|
|
.appendTo(poll))
|
|
.on('click', callback);
|
|
})(i);
|
|
|
|
}
|
|
$("<span/>").addClass("label label-default pull-right").data('timestamp',data.timestamp).appendTo(poll)
|
|
.attr('title', 'Poll opened by ' + data.initiator).data('initiator',data.initiator)
|
|
.text(new Date(data.timestamp).toTimeString().split(" ")[0]);
|
|
|
|
poll.find(".btn").attr("disabled", !hasPermission("pollvote"));
|
|
},
|
|
|
|
updatePoll: function(data) {
|
|
var poll = $("#pollwrap .active");
|
|
var i = 0;
|
|
poll.find(".option button").each(function() {
|
|
$(this).html(data.counts[i]);
|
|
i++;
|
|
});
|
|
},
|
|
|
|
closePoll: function() {
|
|
if($("#pollwrap .active").length != 0) {
|
|
var poll = $("#pollwrap .active");
|
|
poll.removeClass("active").addClass("muted");
|
|
poll.find(".option button").each(function() {
|
|
$(this).attr("disabled", true);
|
|
});
|
|
poll.find(".btn-danger").each(function() {
|
|
$(this).remove();
|
|
});
|
|
}
|
|
},
|
|
|
|
listPlaylists: function(data) {
|
|
$("#userpl_list").data("entries", data);
|
|
formatUserPlaylistList();
|
|
},
|
|
|
|
emoteList: function (data) {
|
|
loadEmotes(data);
|
|
EMOTELIST.handleChange();
|
|
CSEMOTELIST.handleChange();
|
|
},
|
|
|
|
updateEmote: function (data) {
|
|
data.regex = new RegExp(data.source, "gi");
|
|
var found = false;
|
|
for (let i = 0; i < CHANNEL.emotes.length; i++) {
|
|
if (CHANNEL.emotes[i].name === data.name) {
|
|
found = true;
|
|
CHANNEL.emotes[i] = data;
|
|
break;
|
|
}
|
|
}
|
|
for (let i = 0; i < CHANNEL.badEmotes.length; i++) {
|
|
if (CHANNEL.badEmotes[i].name === data.name) {
|
|
CHANNEL.badEmotes[i] = data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
CHANNEL.emotes.push(data);
|
|
if (/\s/g.test(data.name)) {
|
|
CHANNEL.badEmotes.push(data);
|
|
} else {
|
|
CHANNEL.emoteMap[data.name] = data;
|
|
}
|
|
} else {
|
|
CHANNEL.emoteMap[data.name] = data;
|
|
}
|
|
|
|
EMOTELIST.handleChange();
|
|
CSEMOTELIST.handleChange();
|
|
},
|
|
|
|
renameEmote: function (data) {
|
|
var badBefore = /\s/g.test(data.old);
|
|
var badAfter = /\s/g.test(data.name);
|
|
var oldName = data.old;
|
|
delete data.old;
|
|
|
|
data.regex = new RegExp(data.source, "gi");
|
|
|
|
for (var i = 0; i < CHANNEL.emotes.length; i++) {
|
|
if (CHANNEL.emotes[i].name === oldName) {
|
|
CHANNEL.emotes[i] = data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now bad
|
|
if(badAfter){
|
|
// But wasn't bad before: Add it to bad list
|
|
if(!badBefore){
|
|
CHANNEL.badEmotes.push(data);
|
|
delete CHANNEL.emoteMap[oldName];
|
|
// Was bad before too: Update
|
|
} else {
|
|
for (let i = 0; i < CHANNEL.badEmotes.length; i++) {
|
|
if (CHANNEL.badEmotes[i].name === oldName) {
|
|
CHANNEL.badEmotes[i] = data;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Not bad now
|
|
} else {
|
|
// But was bad before: Drop from list
|
|
if(badBefore){
|
|
for (let i = 0; i < CHANNEL.badEmotes.length; i++) {
|
|
if (CHANNEL.badEmotes[i].name === oldName) {
|
|
CHANNEL.badEmotes.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
delete CHANNEL.emoteMap[oldName];
|
|
}
|
|
CHANNEL.emoteMap[data.name] = data;
|
|
}
|
|
|
|
EMOTELIST.handleChange();
|
|
CSEMOTELIST.handleChange();
|
|
},
|
|
|
|
removeEmote: function (data) {
|
|
var found = -1;
|
|
for (let i = 0; i < CHANNEL.emotes.length; i++) {
|
|
if (CHANNEL.emotes[i].name === data.name) {
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found !== -1) {
|
|
var row = $("code:contains('" + data.name + "')").parent().parent();
|
|
row.hide("fade", row.remove.bind(row));
|
|
CHANNEL.emotes.splice(found, 1);
|
|
delete CHANNEL.emoteMap[data.name];
|
|
for (let i = 0; i < CHANNEL.badEmotes.length; i++) {
|
|
if (CHANNEL.badEmotes[i].name === data.name) {
|
|
CHANNEL.badEmotes.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
warnLargeChandump: function (data) {
|
|
function toHumanReadable(size) {
|
|
if (size > 1048576) {
|
|
return Math.floor((size / 1048576) * 100) / 100 + "MiB";
|
|
} else if (size > 1024) {
|
|
return Math.floor((size / 1024) * 100) / 100 + "KiB";
|
|
} else {
|
|
return size + "B";
|
|
}
|
|
}
|
|
|
|
if ($("#chandumptoobig").length > 0) {
|
|
$("#chandumptoobig").remove();
|
|
}
|
|
|
|
errDialog("This channel currently exceeds the maximum size of " +
|
|
toHumanReadable(data.limit) + " (channel size is " +
|
|
toHumanReadable(data.actual) + "). Please reduce the size by removing " +
|
|
"unneeded playlist items, filters, and/or emotes. Changes to the channel " +
|
|
"will not be saved until the size is reduced to under the limit.")
|
|
.attr("id", "chandumptoobig");
|
|
},
|
|
|
|
partitionChange: function (socketConfig) {
|
|
window.socket.disconnect();
|
|
HAS_CONNECTED_BEFORE = false;
|
|
ioServerConnect(socketConfig);
|
|
setupCallbacks();
|
|
},
|
|
|
|
validationError: function (error) {
|
|
var target = $(error.target);
|
|
target.parent().find(".text-danger").remove();
|
|
|
|
var formGroup = target.parent();
|
|
while (!formGroup.hasClass("form-group") && formGroup.length > 0) {
|
|
formGroup = formGroup.parent();
|
|
}
|
|
|
|
if (formGroup.length > 0) {
|
|
formGroup.addClass("has-error");
|
|
}
|
|
|
|
$("<p/>").addClass("text-danger")
|
|
.text(error.message)
|
|
.insertAfter(target);
|
|
},
|
|
|
|
validationPassed: function (data) {
|
|
var target = $(data.target);
|
|
target.parent().find(".text-danger").remove();
|
|
|
|
var formGroup = target.parent();
|
|
while (!formGroup.hasClass("form-group") && formGroup.length > 0) {
|
|
formGroup = formGroup.parent();
|
|
}
|
|
|
|
if (formGroup.length > 0) {
|
|
formGroup.removeClass("has-error");
|
|
}
|
|
},
|
|
|
|
clearVoteskipVote: function () {
|
|
if (CHANNEL.opts.allow_voteskip && hasPermission("voteskip")) {
|
|
$("#voteskip").attr("disabled", false);
|
|
}
|
|
}
|
|
};
|
|
|
|
window.Callbacks = Callbacks;
|
|
|
|
var SOCKET_DEBUG = {
|
|
enabled: (localStorage.getItem('cytube_socket_debug') === 'true'),
|
|
omit: (((data)=>{
|
|
const frames = data === null ? [] : JSON.parse(data);
|
|
return frames;
|
|
})(localStorage.getItem('cytube_socket_omissions')))
|
|
};
|
|
|
|
function setupCallbacks() {
|
|
for(var key in Callbacks) {
|
|
(function(key) {
|
|
socket.on(key, function(data) {
|
|
if (SOCKET_DEBUG.enabled && !SOCKET_DEBUG.omit.includes(key)) {
|
|
console.log(key, data);
|
|
}
|
|
try {
|
|
Callbacks[key](data);
|
|
} catch (e) {
|
|
if (SOCKET_DEBUG.enabled) {
|
|
console.log("EXCEPTION: " + e + "\n" + e.stack);
|
|
}
|
|
}
|
|
});
|
|
})(key);
|
|
}
|
|
|
|
socket.on("connect_error", function (error) {
|
|
// If the socket has connected at least once during this
|
|
// session and now gets a connect error, it is likely because
|
|
// the server is down temporarily and not because of any configuration
|
|
// issue. Therefore, omit the warning message about refreshing.
|
|
if (HAS_CONNECTED_BEFORE) {
|
|
return;
|
|
}
|
|
|
|
SOCKETIO_CONNECT_ERROR_COUNT++;
|
|
if (SOCKETIO_CONNECT_ERROR_COUNT >= 3 &&
|
|
$("#socketio-connect-error").length === 0) {
|
|
var message = "Failed to connect to the server. Try clearing your " +
|
|
"cache and refreshing the page.";
|
|
makeAlert("Error", message, "alert-danger")
|
|
.attr("id", "socketio-connect-error")
|
|
.appendTo($("#announcements"));
|
|
}
|
|
});
|
|
}
|
|
|
|
function ioServerConnect(socketConfig) {
|
|
if (socketConfig.error) {
|
|
makeAlert("Error", "Socket.io configuration returned error: " +
|
|
socketConfig.error, "alert-danger")
|
|
.appendTo($("#announcements"));
|
|
return;
|
|
}
|
|
|
|
var servers;
|
|
if (socketConfig.alt && socketConfig.alt.length > 0 &&
|
|
localStorage.useAltServer === "true") {
|
|
servers = socketConfig.alt;
|
|
console.log("Using alt servers: " + JSON.stringify(servers));
|
|
} else {
|
|
servers = socketConfig.servers;
|
|
}
|
|
|
|
var chosenServer = null;
|
|
servers.forEach(function (server) {
|
|
if (chosenServer === null) {
|
|
chosenServer = server;
|
|
} else if (server.secure && !chosenServer.secure) {
|
|
chosenServer = server;
|
|
} else if (!server.ipv6Only && chosenServer.ipv6Only) {
|
|
chosenServer = server;
|
|
}
|
|
});
|
|
|
|
console.log("Connecting to " + JSON.stringify(chosenServer));
|
|
|
|
if (chosenServer === null) {
|
|
makeAlert("Error",
|
|
"Socket.io configuration was unable to find a suitable server",
|
|
"alert-danger")
|
|
.appendTo($("#announcements"));
|
|
}
|
|
|
|
var opts = {
|
|
secure: chosenServer.secure,
|
|
withCredentials: true // enable cookies for auth
|
|
};
|
|
|
|
window.socket = io(chosenServer.url, opts);
|
|
}
|
|
|
|
var USING_LETS_ENCRYPT = false;
|
|
|
|
function initSocketIO(socketConfig) {
|
|
function genericConnectionError() {
|
|
var message = "The socket.io library could not be loaded from <code>" +
|
|
source + "</code>. Ensure that it is not being blocked " +
|
|
"by a script blocking extension or firewall and try again.";
|
|
makeAlert("Error", message, "alert-danger")
|
|
.appendTo($("#announcements"));
|
|
Callbacks.disconnect();
|
|
}
|
|
|
|
if (typeof io === "undefined") {
|
|
var script = document.getElementById("socketio-js");
|
|
var source = "unknown";
|
|
if (script) {
|
|
source = script.src;
|
|
}
|
|
|
|
if (/^https/.test(source) && location.protocol === "http:"
|
|
&& USING_LETS_ENCRYPT) {
|
|
checkLetsEncrypt(socketConfig, genericConnectionError);
|
|
return;
|
|
}
|
|
|
|
genericConnectionError();
|
|
return;
|
|
}
|
|
|
|
ioServerConnect(socketConfig);
|
|
setupCallbacks();
|
|
}
|
|
|
|
function checkLetsEncrypt(socketConfig, nonLetsEncryptError) {
|
|
var servers = socketConfig.servers.filter(function (server) {
|
|
return !server.secure && !server.ipv6Only;
|
|
});
|
|
|
|
if (servers.length === 0) {
|
|
nonLetsEncryptError();
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
url: servers[0].url + "/socket.io/socket.io.js",
|
|
dataType: "script",
|
|
timeout: 10000
|
|
}).done(function () {
|
|
var message = "Your browser cannot connect securely because it does " +
|
|
"not support the newer Let's Encrypt certificate " +
|
|
"authority. Click below to acknowledge and continue " +
|
|
"connecting over an unencrypted connection. See " +
|
|
"<a href=\"https://community.letsencrypt.org/t/which-browsers-and-operating-systems-support-lets-encrypt/4394\" target=\"_blank\">here</a> " +
|
|
"for more details.";
|
|
var connectionAlert = makeAlert("Error", message, "alert-danger")
|
|
.appendTo($("#announcements"));
|
|
|
|
var button = document.createElement("button");
|
|
button.className = "btn btn-default";
|
|
button.textContent = "Connect Anyways";
|
|
|
|
var alertBox = connectionAlert.find(".alert")[0];
|
|
alertBox.appendChild(document.createElement("hr"));
|
|
alertBox.appendChild(button);
|
|
|
|
button.onclick = function connectAnyways() {
|
|
ioServerConnect({
|
|
servers: servers
|
|
});
|
|
setupCallbacks();
|
|
};
|
|
}).error(function () {
|
|
nonLetsEncryptError();
|
|
});
|
|
}
|
|
|
|
(function () {
|
|
$.getJSON("/socketconfig/" + CHANNEL.name + ".json")
|
|
.done(function (socketConfig) {
|
|
initSocketIO(socketConfig);
|
|
}).fail(function () {
|
|
makeAlert("Error", "Failed to retrieve socket.io configuration. " +
|
|
"Please try again in a few minutes.",
|
|
"alert-danger")
|
|
.appendTo($("#announcements"));
|
|
Callbacks.disconnect();
|
|
});
|
|
})();
|