2016-07-23 02:22:15 +00:00
function makeAlert ( title , text , klass , textOnly ) {
2013-06-11 23:51:00 +00:00
if ( ! klass ) {
klass = "alert-info" ;
}
2014-01-26 20:15:50 +00:00
var wrap = $ ( "<div/>" ) . addClass ( "col-md-12" ) ;
2013-06-11 23:51:00 +00:00
var al = $ ( "<div/>" ) . addClass ( "alert" )
. addClass ( klass )
2014-01-26 20:15:50 +00:00
. appendTo ( wrap ) ;
2016-07-23 02:22:15 +00:00
textOnly ? al . text ( text ) : al . html ( text ) ;
2013-06-11 23:51:00 +00:00
$ ( "<br/>" ) . prependTo ( al ) ;
$ ( "<strong/>" ) . text ( title ) . prependTo ( al ) ;
$ ( "<button/>" ) . addClass ( "close pull-right" ) . html ( "×" )
. click ( function ( ) {
2014-01-14 06:52:56 +00:00
al . hide ( "fade" , function ( ) {
2014-01-26 20:15:50 +00:00
wrap . remove ( ) ;
2013-06-11 23:51:00 +00:00
} ) ;
} )
. prependTo ( al ) ;
2014-01-26 20:15:50 +00:00
return wrap ;
2013-06-11 23:51:00 +00:00
}
function formatURL ( data ) {
switch ( data . type ) {
case "yt" :
2017-04-12 04:57:05 +00:00
return "https://youtube.com/watch?v=" + data . id ;
2013-06-11 23:51:00 +00:00
case "vi" :
2017-04-12 04:57:05 +00:00
return "https://vimeo.com/" + data . id ;
2013-06-11 23:51:00 +00:00
case "dm" :
2017-04-12 04:57:05 +00:00
return "https://dailymotion.com/video/" + data . id ;
2013-06-11 23:51:00 +00:00
case "sc" :
return data . id ;
case "li" :
2017-04-12 04:57:05 +00:00
return "https://livestream.com/" + data . id ;
2013-06-11 23:51:00 +00:00
case "tw" :
2017-04-12 04:57:05 +00:00
return "https://twitch.tv/" + data . id ;
2013-06-11 23:51:00 +00:00
case "rt" :
return data . id ;
2013-12-19 04:50:19 +00:00
case "gd" :
2014-05-01 01:40:25 +00:00
return "https://docs.google.com/file/d/" + data . id ;
2014-06-04 04:21:00 +00:00
case "fi" :
return data . id ;
2016-08-09 03:34:03 +00:00
case "hl" :
return data . id ;
2016-08-03 05:35:00 +00:00
case "sb" :
return "https://streamable.com/" + data . id ;
2017-05-27 18:49:27 +00:00
case "tc" :
return "https://clips.twitch.tv/" + data . id ;
2017-08-09 03:35:17 +00:00
case "cm" :
return data . id ;
2021-08-20 03:46:38 +00:00
case "cu" :
return data . meta . embed . src ;
2013-06-11 23:51:00 +00:00
default :
return "#" ;
}
}
2013-08-07 21:55:39 +00:00
function findUserlistItem ( name ) {
var children = $ ( "#userlist .userlist_item" ) ;
2013-08-22 00:20:26 +00:00
if ( children . length == 0 )
return null ;
name = name . toLowerCase ( ) ;
2013-08-26 03:06:32 +00:00
// WARNING: Incoming hax because of jQuery and bootstrap bullshit
var keys = Object . keys ( children ) ;
for ( var k in keys ) {
var i = keys [ k ] ;
if ( isNaN ( parseInt ( i ) ) ) {
2013-08-22 00:20:26 +00:00
continue ;
2013-08-26 03:06:32 +00:00
}
var child = children [ i ] ;
2013-08-22 00:20:26 +00:00
if ( $ ( child . children [ 1 ] ) . text ( ) . toLowerCase ( ) == name )
2013-08-07 21:55:39 +00:00
return $ ( child ) ;
}
return null ;
}
2013-11-09 04:12:17 +00:00
function formatUserlistItem ( div ) {
var data = {
name : div . data ( "name" ) || "" ,
rank : div . data ( "rank" ) ,
profile : div . data ( "profile" ) || { image : "" , text : "" } ,
2013-11-09 18:33:18 +00:00
leader : div . data ( "leader" ) || false ,
2013-11-15 01:50:17 +00:00
icon : div . data ( "icon" ) || false ,
2013-11-09 04:12:17 +00:00
} ;
2013-06-11 23:51:00 +00:00
var name = $ ( div . children ( ) [ 1 ] ) ;
name . removeClass ( ) ;
name . css ( "font-style" , "" ) ;
name . addClass ( getNameColor ( data . rank ) ) ;
div . find ( ".profile-box" ) . remove ( ) ;
2017-06-17 04:37:30 +00:00
var meta = div . data ( ) . meta || { } ; // Not sure how this could happen.
if ( meta . afk ) {
2015-01-07 20:58:36 +00:00
div . addClass ( "userlist_afk" ) ;
} else {
div . removeClass ( "userlist_afk" ) ;
}
2017-06-17 04:37:30 +00:00
if ( meta . muted ) {
2015-01-07 20:58:36 +00:00
div . addClass ( "userlist_muted" ) ;
} else {
div . removeClass ( "userlist_muted" ) ;
}
2017-06-17 04:37:30 +00:00
if ( meta . smuted ) {
2015-01-07 20:58:36 +00:00
div . addClass ( "userlist_smuted" ) ;
} else {
div . removeClass ( "userlist_smuted" ) ;
}
2013-08-29 04:30:54 +00:00
var profile = null ;
2015-10-20 03:28:33 +00:00
/ *
* 2015 - 10 - 19
* Prevent rendering unnecessary duplicates of the profile box when
* a user ' s status changes .
* /
name . unbind ( "mouseenter" ) ;
name . unbind ( "mousemove" ) ;
name . unbind ( "mouseleave" ) ;
2013-06-11 23:51:00 +00:00
name . mouseenter ( function ( ev ) {
2013-08-29 04:30:54 +00:00
if ( profile )
profile . remove ( ) ;
2014-11-11 21:48:34 +00:00
var top = ev . clientY + 5 ;
var horiz = ev . clientX ;
2013-06-11 23:51:00 +00:00
profile = $ ( "<div/>" )
2014-02-27 23:26:49 +00:00
. addClass ( "profile-box linewrap" )
2014-01-26 00:12:49 +00:00
. css ( "top" , top + "px" )
2013-06-11 23:51:00 +00:00
. appendTo ( div ) ;
2014-11-11 21:48:34 +00:00
2013-06-11 23:51:00 +00:00
if ( data . profile . image ) {
$ ( "<img/>" ) . addClass ( "profile-image" )
. attr ( "src" , data . profile . image )
. appendTo ( profile ) ;
}
$ ( "<strong/>" ) . text ( data . name ) . appendTo ( profile ) ;
2014-01-08 04:47:00 +00:00
var meta = div . data ( "meta" ) || { } ;
2020-07-15 12:54:58 +00:00
if ( meta . ip && USEROPTS . show _ip _in _tooltip ) {
2014-01-08 04:47:00 +00:00
$ ( "<br/>" ) . appendTo ( profile ) ;
$ ( "<em/>" ) . text ( meta . ip ) . appendTo ( profile ) ;
}
if ( meta . aliases ) {
2014-01-26 06:13:33 +00:00
$ ( "<br/>" ) . appendTo ( profile ) ;
$ ( "<em/>" ) . text ( "aliases: " + meta . aliases . join ( ", " ) ) . appendTo ( profile ) ;
2014-01-08 04:47:00 +00:00
}
2014-01-26 06:13:33 +00:00
$ ( "<hr/>" ) . css ( "margin-top" , "5px" ) . css ( "margin-bottom" , "5px" ) . appendTo ( profile ) ;
2013-06-11 23:51:00 +00:00
$ ( "<p/>" ) . text ( data . profile . text ) . appendTo ( profile ) ;
2014-11-11 21:48:34 +00:00
if ( $ ( "body" ) . hasClass ( "synchtube" ) ) horiz -= profile . outerWidth ( ) ;
profile . css ( "left" , horiz + "px" )
2013-06-11 23:51:00 +00:00
} ) ;
name . mousemove ( function ( ev ) {
2014-11-11 21:48:34 +00:00
var top = ev . clientY + 5 ;
var horiz = ev . clientX ;
if ( $ ( "body" ) . hasClass ( "synchtube" ) ) horiz -= profile . outerWidth ( ) ;
profile . css ( "left" , horiz + "px" )
. css ( "top" , top + "px" ) ;
2013-06-11 23:51:00 +00:00
} ) ;
name . mouseleave ( function ( ) {
profile . remove ( ) ;
} ) ;
2013-12-19 04:50:19 +00:00
var icon = div . children ( ) [ 0 ] ;
icon . innerHTML = "" ;
2013-06-11 23:51:00 +00:00
// denote current leader with a star
if ( data . leader ) {
2013-12-19 04:50:19 +00:00
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-star-empty" ) . appendTo ( icon ) ;
2013-06-11 23:51:00 +00:00
}
2017-06-16 05:09:09 +00:00
if ( div . data ( ) . meta . afk ) {
2013-06-11 23:51:00 +00:00
name . css ( "font-style" , "italic" ) ;
2013-12-19 04:50:19 +00:00
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-time" ) . appendTo ( icon ) ;
2013-06-11 23:51:00 +00:00
}
2013-11-09 18:33:18 +00:00
if ( data . icon ) {
2014-01-19 02:18:00 +00:00
$ ( "<span/>" ) . addClass ( "glyphicon " + data . icon ) . prependTo ( icon ) ;
2013-06-25 14:18:33 +00:00
}
2013-06-11 23:51:00 +00:00
}
function getNameColor ( rank ) {
if ( rank >= Rank . Siteadmin )
return "userlist_siteadmin" ;
2013-06-18 14:46:28 +00:00
else if ( rank >= Rank . Admin )
2013-06-11 23:51:00 +00:00
return "userlist_owner" ;
else if ( rank >= Rank . Moderator )
return "userlist_op" ;
else if ( rank == Rank . Guest )
return "userlist_guest" ;
else
return "" ;
}
2013-11-09 04:12:17 +00:00
function addUserDropdown ( entry ) {
var name = entry . data ( "name" ) ,
rank = entry . data ( "rank" ) ,
2014-01-19 22:50:14 +00:00
leader = entry . data ( "leader" ) ,
meta = entry . data ( "meta" ) || { } ;
2013-06-11 23:51:00 +00:00
entry . find ( ".user-dropdown" ) . remove ( ) ;
2013-08-07 20:14:22 +00:00
var menu = $ ( "<div/>" )
. addClass ( "user-dropdown" )
. appendTo ( entry )
. hide ( ) ;
2013-06-11 23:51:00 +00:00
$ ( "<strong/>" ) . text ( name ) . appendTo ( menu ) ;
$ ( "<br/>" ) . appendTo ( menu ) ;
2013-08-07 20:14:22 +00:00
2014-01-12 05:55:52 +00:00
var btngroup = $ ( "<div/>" ) . addClass ( "btn-group-vertical" ) . appendTo ( menu ) ;
2013-08-31 03:12:28 +00:00
2013-08-07 20:14:22 +00:00
/* ignore button */
2021-03-23 05:49:11 +00:00
if ( name !== CLIENT . name ) {
var ignore = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
. appendTo ( btngroup )
. click ( function ( ) {
if ( IGNORED . indexOf ( name ) == - 1 ) {
ignore . text ( "Unignore User" ) ;
IGNORED . push ( name ) ;
entry . addClass ( "userlist-ignored" ) ;
} else {
ignore . text ( "Ignore User" ) ;
IGNORED . splice ( IGNORED . indexOf ( name ) , 1 ) ;
entry . removeClass ( "userlist-ignored" ) ;
}
setOpt ( "ignorelist" , IGNORED ) ;
} ) ;
if ( IGNORED . indexOf ( name ) == - 1 ) {
entry . removeClass ( "userlist-ignored" ) ;
ignore . text ( "Ignore User" ) ;
} else {
entry . addClass ( "userlist-ignored" ) ;
ignore . text ( "Unignore User" ) ;
}
2013-06-25 14:06:01 +00:00
}
2013-08-07 20:14:22 +00:00
2014-02-15 07:40:14 +00:00
/* pm button */
2014-02-15 18:29:05 +00:00
if ( name !== CLIENT . name ) {
var pm = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
. text ( "Private Message" )
. appendTo ( btngroup )
. click ( function ( ) {
initPm ( name ) . find ( ".panel-heading" ) . click ( ) ;
menu . hide ( ) ;
} ) ;
}
2014-02-15 07:40:14 +00:00
2014-01-23 22:03:50 +00:00
/* give/remove leader (moderator+ only) */
if ( hasPermission ( "leaderctl" ) ) {
2014-01-12 05:55:52 +00:00
var ldr = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
. appendTo ( btngroup ) ;
2013-08-07 20:14:22 +00:00
if ( leader ) {
ldr . text ( "Remove Leader" ) ;
ldr . click ( function ( ) {
socket . emit ( "assignLeader" , {
name : ""
} ) ;
} ) ;
} else {
ldr . text ( "Give Leader" ) ;
ldr . click ( function ( ) {
2013-06-11 23:51:00 +00:00
socket . emit ( "assignLeader" , {
name : name
} ) ;
2013-08-07 20:14:22 +00:00
} ) ;
}
}
/* kick button */
if ( hasPermission ( "kick" ) ) {
2014-01-12 05:55:52 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
2013-08-07 20:14:22 +00:00
. text ( "Kick" )
. click ( function ( ) {
2014-01-20 23:35:55 +00:00
var reason = prompt ( "Enter kick reason (optional)" ) ;
2015-02-22 02:34:25 +00:00
if ( reason === null ) {
return ;
}
2013-08-07 20:14:22 +00:00
socket . emit ( "chatMsg" , {
2014-05-21 03:11:40 +00:00
msg : "/kick " + name + " " + reason ,
meta : { }
2013-06-11 23:51:00 +00:00
} ) ;
} )
2014-01-12 05:55:52 +00:00
. appendTo ( btngroup ) ;
2013-06-11 23:51:00 +00:00
}
2013-08-07 20:14:22 +00:00
2014-01-19 22:50:14 +00:00
/* mute buttons */
if ( hasPermission ( "mute" ) ) {
var mute = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
. text ( "Mute" )
. click ( function ( ) {
socket . emit ( "chatMsg" , {
2014-05-21 03:11:40 +00:00
msg : "/mute " + name ,
meta : { }
2014-01-19 22:50:14 +00:00
} ) ;
} )
. appendTo ( btngroup ) ;
var smute = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
. text ( "Shadow Mute" )
. click ( function ( ) {
socket . emit ( "chatMsg" , {
2014-05-21 03:11:40 +00:00
msg : "/smute " + name ,
meta : { }
2014-01-19 22:50:14 +00:00
} ) ;
} )
. appendTo ( btngroup ) ;
var unmute = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
. text ( "Unmute" )
. click ( function ( ) {
socket . emit ( "chatMsg" , {
2014-05-21 03:11:40 +00:00
msg : "/unmute " + name ,
meta : { }
2014-01-19 22:50:14 +00:00
} ) ;
} )
. appendTo ( btngroup ) ;
if ( meta . muted ) {
mute . hide ( ) ;
smute . hide ( ) ;
} else {
unmute . hide ( ) ;
}
}
2013-08-07 20:14:22 +00:00
/* ban buttons */
2013-06-11 23:51:00 +00:00
if ( hasPermission ( "ban" ) ) {
2014-01-12 05:55:52 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
2013-06-11 23:51:00 +00:00
. text ( "Name Ban" )
2013-08-07 20:14:22 +00:00
. click ( function ( ) {
2014-01-20 23:35:55 +00:00
var reason = prompt ( "Enter ban reason (optional)" ) ;
2015-02-22 02:34:25 +00:00
if ( reason === null ) {
return ;
}
2013-06-11 23:51:00 +00:00
socket . emit ( "chatMsg" , {
2014-05-21 03:11:40 +00:00
msg : "/ban " + name + " " + reason ,
meta : { }
2013-06-11 23:51:00 +00:00
} ) ;
} )
2014-01-12 05:55:52 +00:00
. appendTo ( btngroup ) ;
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
2013-06-11 23:51:00 +00:00
. text ( "IP Ban" )
2013-08-07 20:14:22 +00:00
. click ( function ( ) {
2014-01-20 23:35:55 +00:00
var reason = prompt ( "Enter ban reason (optional)" ) ;
2015-02-22 02:34:25 +00:00
if ( reason === null ) {
return ;
}
2013-06-11 23:51:00 +00:00
socket . emit ( "chatMsg" , {
2014-05-21 03:11:40 +00:00
msg : "/ipban " + name + " " + reason ,
meta : { }
2013-06-11 23:51:00 +00:00
} ) ;
} )
2014-01-12 05:55:52 +00:00
. appendTo ( btngroup ) ;
2013-06-11 23:51:00 +00:00
}
2014-04-30 22:40:16 +00:00
var showdd = function ( ev ) {
2014-08-27 23:49:00 +00:00
// Workaround for Chrome
if ( ev . shiftKey ) return true ;
2013-06-11 23:51:00 +00:00
ev . preventDefault ( ) ;
if ( menu . css ( "display" ) == "none" ) {
2013-10-17 04:10:59 +00:00
$ ( ".user-dropdown" ) . hide ( ) ;
$ ( document ) . bind ( "mouseup.userlist-ddown" , function ( e ) {
if ( menu . has ( e . target ) . length === 0 &&
entry . parent ( ) . has ( e . target ) . length === 0 ) {
menu . hide ( ) ;
$ ( document ) . unbind ( "mouseup.userlist-ddown" ) ;
}
} ) ;
2013-06-11 23:51:00 +00:00
menu . show ( ) ;
2014-02-28 06:23:41 +00:00
menu . css ( "top" , entry . position ( ) . top ) ;
2013-08-07 20:14:22 +00:00
} else {
2013-06-11 23:51:00 +00:00
menu . hide ( ) ;
}
return false ;
2014-04-30 22:40:16 +00:00
} ;
entry . contextmenu ( showdd ) ;
entry . click ( showdd ) ;
2013-06-11 23:51:00 +00:00
}
2013-08-08 14:39:58 +00:00
function calcUserBreakdown ( ) {
var breakdown = {
"Site Admins" : 0 ,
"Channel Admins" : 0 ,
"Moderators" : 0 ,
"Regular Users" : 0 ,
"Guests" : 0 ,
2013-09-04 22:47:24 +00:00
"Anonymous" : 0 ,
2013-08-08 14:39:58 +00:00
"AFK" : 0
} ;
2013-09-04 22:47:24 +00:00
var total = 0 ;
2013-08-08 14:39:58 +00:00
$ ( "#userlist .userlist_item" ) . each ( function ( index , item ) {
2013-11-09 04:12:17 +00:00
var data = {
rank : $ ( item ) . data ( "rank" )
} ;
2013-11-16 05:44:53 +00:00
2013-08-08 14:39:58 +00:00
if ( data . rank >= 255 )
breakdown [ "Site Admins" ] ++ ;
else if ( data . rank >= 3 )
breakdown [ "Channel Admins" ] ++ ;
else if ( data . rank == 2 )
breakdown [ "Moderators" ] ++ ;
else if ( data . rank >= 1 )
breakdown [ "Regular Users" ] ++ ;
else
breakdown [ "Guests" ] ++ ;
2013-09-04 22:47:24 +00:00
total ++ ;
2017-06-16 05:09:09 +00:00
if ( $ ( item ) . data ( ) . meta . afk )
2013-08-08 14:39:58 +00:00
breakdown [ "AFK" ] ++ ;
} ) ;
2013-08-31 03:12:28 +00:00
2013-09-04 22:47:24 +00:00
breakdown [ "Anonymous" ] = CHANNEL . usercount - total ;
2013-08-08 14:39:58 +00:00
return breakdown ;
}
2013-08-08 14:57:46 +00:00
function sortUserlist ( ) {
var slice = Array . prototype . slice ;
var list = slice . call ( $ ( "#userlist .userlist_item" ) ) ;
list . sort ( function ( a , b ) {
2013-11-09 04:12:17 +00:00
var r1 = $ ( a ) . data ( "rank" ) ;
var r2 = $ ( b ) . data ( "rank" ) ;
2013-12-19 04:50:19 +00:00
var afk1 = $ ( a ) . find ( ".glyphicon-time" ) . length > 0 ;
var afk2 = $ ( b ) . find ( ".glyphicon-time" ) . length > 0 ;
2013-08-08 14:57:46 +00:00
var name1 = a . children [ 1 ] . innerHTML . toLowerCase ( ) ;
var name2 = b . children [ 1 ] . innerHTML . toLowerCase ( ) ;
if ( USEROPTS . sort _afk ) {
if ( afk1 && ! afk2 )
return 1 ;
if ( ! afk1 && afk2 )
return - 1 ;
}
if ( USEROPTS . sort _rank ) {
if ( r1 < r2 )
return 1 ;
if ( r1 > r2 )
return - 1 ;
}
return name1 === name2 ? 0 : ( name1 < name2 ? - 1 : 1 ) ;
} ) ;
list . forEach ( function ( item ) {
$ ( item ) . detach ( ) ;
} ) ;
list . forEach ( function ( item ) {
$ ( item ) . appendTo ( $ ( "#userlist" ) ) ;
} ) ;
}
2013-06-11 23:51:00 +00:00
/* queue stuff */
2013-07-03 21:29:49 +00:00
function scrollQueue ( ) {
var li = playlistFind ( PL _CURRENT ) ;
if ( ! li )
return ;
li = $ ( li ) ;
$ ( "#queue" ) . scrollTop ( 0 ) ;
var scroll = li . position ( ) . top - $ ( "#queue" ) . position ( ) . top ;
$ ( "#queue" ) . scrollTop ( scroll ) ;
}
2013-06-29 22:09:20 +00:00
function makeQueueEntry ( item , addbtns ) {
var video = item . media ;
2013-06-11 23:51:00 +00:00
var li = $ ( "<li/>" ) ;
li . addClass ( "queue_entry" ) ;
2013-06-29 22:09:20 +00:00
li . addClass ( "pluid-" + item . uid ) ;
li . data ( "uid" , item . uid ) ;
2013-06-11 23:51:00 +00:00
li . data ( "media" , video ) ;
2013-06-29 22:09:20 +00:00
li . data ( "temp" , item . temp ) ;
2013-06-11 23:51:00 +00:00
if ( video . thumb ) {
$ ( "<img/>" ) . attr ( "src" , video . thumb . url )
. css ( "float" , "left" )
. css ( "clear" , "both" )
. appendTo ( li ) ;
}
var title = $ ( "<a/>" ) . addClass ( "qe_title" ) . appendTo ( li )
. text ( video . title )
. attr ( "href" , formatURL ( video ) )
. attr ( "target" , "_blank" ) ;
var time = $ ( "<span/>" ) . addClass ( "qe_time" ) . appendTo ( li ) ;
time . text ( video . duration ) ;
var clear = $ ( "<div/>" ) . addClass ( "qe_clear" ) . appendTo ( li ) ;
2013-06-29 22:09:20 +00:00
if ( item . temp ) {
2013-06-11 23:51:00 +00:00
li . addClass ( "queue_temp" ) ;
}
if ( addbtns )
addQueueButtons ( li ) ;
return li ;
}
2013-06-29 22:09:20 +00:00
function makeSearchEntry ( video ) {
var li = $ ( "<li/>" ) ;
li . addClass ( "queue_entry" ) ;
li . data ( "media" , video ) ;
if ( video . thumb ) {
$ ( "<img/>" ) . attr ( "src" , video . thumb . url )
. css ( "float" , "left" )
. css ( "clear" , "both" )
. appendTo ( li ) ;
}
var title = $ ( "<a/>" ) . addClass ( "qe_title" ) . appendTo ( li )
. text ( video . title )
. attr ( "href" , formatURL ( video ) )
. attr ( "target" , "_blank" ) ;
var time = $ ( "<span/>" ) . addClass ( "qe_time" ) . appendTo ( li ) ;
time . text ( video . duration ) ;
var clear = $ ( "<div/>" ) . addClass ( "qe_clear" ) . appendTo ( li ) ;
return li ;
}
2013-06-11 23:51:00 +00:00
function addQueueButtons ( li ) {
li . find ( ".btn-group" ) . remove ( ) ;
var menu = $ ( "<div/>" ) . addClass ( "btn-group" ) . appendTo ( li ) ;
// Play
if ( hasPermission ( "playlistjump" ) ) {
2013-12-19 04:50:19 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default qbtn-play" )
2013-12-19 17:14:48 +00:00
. html ( "<span class='glyphicon glyphicon-play'></span>Play" )
2013-06-11 23:51:00 +00:00
. click ( function ( ) {
2013-06-29 22:09:20 +00:00
socket . emit ( "jumpTo" , li . data ( "uid" ) ) ;
2013-06-11 23:51:00 +00:00
} )
. appendTo ( menu ) ;
}
// Queue next
2013-07-28 18:10:47 +00:00
if ( hasPermission ( "playlistmove" ) ) {
2013-12-19 04:50:19 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default qbtn-next" )
2013-12-19 17:14:48 +00:00
. html ( "<span class='glyphicon glyphicon-share-alt'></span>Queue Next" )
2013-06-11 23:51:00 +00:00
. click ( function ( ) {
socket . emit ( "moveMedia" , {
2013-06-29 22:09:20 +00:00
from : li . data ( "uid" ) ,
2013-10-04 03:11:47 +00:00
after : PL _CURRENT
2013-06-11 23:51:00 +00:00
} ) ;
} )
. appendTo ( menu ) ;
}
// Temp/Untemp
if ( hasPermission ( "settemp" ) ) {
2013-06-29 22:09:20 +00:00
var tempstr = li . data ( "temp" ) ? "Make Permanent" : "Make Temporary" ;
2013-12-19 04:50:19 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default qbtn-tmp" )
2013-12-19 17:14:48 +00:00
. html ( "<span class='glyphicon glyphicon-flag'></span>" + tempstr )
2013-06-11 23:51:00 +00:00
. click ( function ( ) {
socket . emit ( "setTemp" , {
2013-06-29 22:09:20 +00:00
uid : li . data ( "uid" ) ,
2013-07-09 17:11:44 +00:00
temp : ! li . data ( "temp" )
2013-06-11 23:51:00 +00:00
} ) ;
} )
. appendTo ( menu ) ;
}
// Delete
if ( hasPermission ( "playlistdelete" ) ) {
2013-12-19 04:50:19 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default qbtn-delete" )
2013-12-19 17:14:48 +00:00
. html ( "<span class='glyphicon glyphicon-trash'></span>Delete" )
2013-06-11 23:51:00 +00:00
. click ( function ( ) {
2013-06-29 22:09:20 +00:00
socket . emit ( "delete" , li . data ( "uid" ) ) ;
2013-06-11 23:51:00 +00:00
} )
. appendTo ( menu ) ;
}
2013-06-20 21:15:41 +00:00
if ( USEROPTS . qbtn _hide && ! USEROPTS . qbtn _idontlikechange
|| menu . find ( ".btn" ) . length == 0 )
2013-06-20 02:49:49 +00:00
menu . hide ( ) ;
2013-06-11 23:51:00 +00:00
2013-06-20 20:50:12 +00:00
// I DON'T LIKE CHANGE
if ( USEROPTS . qbtn _idontlikechange ) {
menu . addClass ( "pull-left" ) ;
menu . detach ( ) . prependTo ( li ) ;
menu . find ( ".btn" ) . each ( function ( ) {
// Clear icon
2013-12-25 21:18:21 +00:00
var icon = $ ( this ) . find ( ".glyphicon" ) ;
2013-06-20 20:50:12 +00:00
$ ( this ) . html ( "" ) ;
icon . appendTo ( this ) ;
} ) ;
menu . find ( ".qbtn-play" ) . addClass ( "btn-success" ) ;
menu . find ( ".qbtn-delete" ) . addClass ( "btn-danger" ) ;
}
2013-06-20 21:15:41 +00:00
else if ( menu . find ( ".btn" ) . length != 0 ) {
2013-06-25 14:38:19 +00:00
li . unbind ( "contextmenu" ) ;
2013-06-20 20:50:12 +00:00
li . contextmenu ( function ( ev ) {
2014-07-08 04:48:23 +00:00
// Allow shift+click to open context menu
// (Chrome workaround, works by default on Firefox)
if ( ev . shiftKey ) return true ;
2013-06-20 20:50:12 +00:00
ev . preventDefault ( ) ;
if ( menu . css ( "display" ) == "none" )
menu . show ( "blind" ) ;
else
menu . hide ( "blind" ) ;
return false ;
} ) ;
}
2013-06-11 23:51:00 +00:00
}
function rebuildPlaylist ( ) {
2013-06-25 03:33:00 +00:00
var qli = $ ( "#queue li" ) ;
if ( qli . length == 0 )
return ;
2013-06-27 02:44:48 +00:00
REBUILDING = Math . random ( ) + "" ;
var r = REBUILDING ;
2013-06-25 02:45:44 +00:00
var i = 0 ;
qli . each ( function ( ) {
var li = $ ( this ) ;
2013-06-27 02:44:48 +00:00
( function ( i , r ) {
2013-06-25 14:38:19 +00:00
setTimeout ( function ( ) {
2013-06-27 02:44:48 +00:00
// Stop if another rebuild is running
if ( REBUILDING != r )
return ;
2013-06-25 14:38:19 +00:00
addQueueButtons ( li ) ;
if ( i == qli . length - 1 ) {
2013-09-02 17:09:14 +00:00
scrollQueue ( ) ;
2013-06-25 14:38:19 +00:00
REBUILDING = false ;
}
} , 10 * i ) ;
2013-06-27 02:44:48 +00:00
} ) ( i , r ) ;
2013-06-25 02:45:44 +00:00
i ++ ;
2013-06-11 23:51:00 +00:00
} ) ;
}
/* menus */
2013-08-31 17:37:37 +00:00
/* user settings menu */
2013-12-25 21:18:21 +00:00
function showUserOptions ( ) {
if ( CLIENT . rank < 2 ) {
$ ( "a[href='#us-mod']" ) . parent ( ) . hide ( ) ;
} else {
$ ( "a[href='#us-mod']" ) . parent ( ) . show ( ) ;
}
$ ( "#us-theme" ) . val ( USEROPTS . theme ) ;
$ ( "#us-layout" ) . val ( USEROPTS . layout ) ;
$ ( "#us-no-channelcss" ) . prop ( "checked" , USEROPTS . ignore _channelcss ) ;
$ ( "#us-no-channeljs" ) . prop ( "checked" , USEROPTS . ignore _channeljs ) ;
$ ( "#us-synch" ) . prop ( "checked" , USEROPTS . synch ) ;
2014-04-13 02:54:17 +00:00
$ ( "#us-synch-accuracy" ) . val ( USEROPTS . sync _accuracy ) ;
2013-12-25 21:18:21 +00:00
$ ( "#us-wmode-transparent" ) . prop ( "checked" , USEROPTS . wmode _transparent ) ;
$ ( "#us-hidevideo" ) . prop ( "checked" , USEROPTS . hidevid ) ;
$ ( "#us-playlistbuttons" ) . prop ( "checked" , USEROPTS . qbtn _hide ) ;
$ ( "#us-oldbtns" ) . prop ( "checked" , USEROPTS . qbtn _idontlikechange ) ;
2014-01-19 02:18:00 +00:00
$ ( "#us-default-quality" ) . val ( USEROPTS . default _quality || "auto" ) ;
2013-12-25 21:18:21 +00:00
$ ( "#us-chat-timestamp" ) . prop ( "checked" , USEROPTS . show _timestamps ) ;
$ ( "#us-sort-rank" ) . prop ( "checked" , USEROPTS . sort _rank ) ;
$ ( "#us-sort-afk" ) . prop ( "checked" , USEROPTS . sort _afk ) ;
2014-05-26 20:22:20 +00:00
$ ( "#us-blink-title" ) . val ( USEROPTS . blink _title ) ;
$ ( "#us-ping-sound" ) . val ( USEROPTS . boop ) ;
2018-10-07 20:55:34 +00:00
$ ( "#us-notifications" ) . val ( USEROPTS . notifications ) ;
2013-12-25 21:18:21 +00:00
$ ( "#us-sendbtn" ) . prop ( "checked" , USEROPTS . chatbtn ) ;
2014-10-24 04:21:44 +00:00
$ ( "#us-no-emotes" ) . prop ( "checked" , USEROPTS . no _emotes ) ;
2016-07-12 05:19:39 +00:00
$ ( "#us-strip-image" ) . prop ( "checked" , USEROPTS . strip _image ) ;
2017-01-11 06:26:46 +00:00
$ ( "#us-chat-tab-method" ) . val ( USEROPTS . chat _tab _method ) ;
2014-01-08 04:47:00 +00:00
2013-12-25 21:18:21 +00:00
$ ( "#us-modflair" ) . prop ( "checked" , USEROPTS . modhat ) ;
2014-04-01 16:52:20 +00:00
$ ( "#us-shadowchat" ) . prop ( "checked" , USEROPTS . show _shadowchat ) ;
2020-07-15 12:54:58 +00:00
$ ( "#us-show-ip-in-tooltip" ) . prop ( "checked" , USEROPTS . show _ip _in _tooltip ) ;
2013-12-25 21:18:21 +00:00
2014-07-02 03:11:54 +00:00
formatScriptAccessPrefs ( ) ;
2013-12-25 21:18:21 +00:00
$ ( "a[href='#us-general']" ) . click ( ) ;
$ ( "#useroptions" ) . modal ( ) ;
2013-06-11 23:51:00 +00:00
}
2013-12-25 21:18:21 +00:00
function saveUserOptions ( ) {
USEROPTS . theme = $ ( "#us-theme" ) . val ( ) ;
2014-01-30 04:50:14 +00:00
createCookie ( "cytube-theme" , USEROPTS . theme , 1000 ) ;
2013-12-25 21:18:21 +00:00
USEROPTS . layout = $ ( "#us-layout" ) . val ( ) ;
USEROPTS . ignore _channelcss = $ ( "#us-no-channelcss" ) . prop ( "checked" ) ;
USEROPTS . ignore _channeljs = $ ( "#us-no-channeljs" ) . prop ( "checked" ) ;
2020-07-15 12:54:58 +00:00
USEROPTS . show _ip _in _tooltip = $ ( "#us-show-ip-in-tooltip" ) . prop ( "checked" ) ;
2013-12-25 21:18:21 +00:00
USEROPTS . synch = $ ( "#us-synch" ) . prop ( "checked" ) ;
2014-04-13 02:54:17 +00:00
USEROPTS . sync _accuracy = parseFloat ( $ ( "#us-synch-accuracy" ) . val ( ) ) || 2 ;
2013-12-25 21:18:21 +00:00
USEROPTS . wmode _transparent = $ ( "#us-wmode-transparent" ) . prop ( "checked" ) ;
USEROPTS . hidevid = $ ( "#us-hidevideo" ) . prop ( "checked" ) ;
USEROPTS . qbtn _hide = $ ( "#us-playlistbuttons" ) . prop ( "checked" ) ;
USEROPTS . qbtn _idontlikechange = $ ( "#us-oldbtns" ) . prop ( "checked" ) ;
2014-01-19 02:18:00 +00:00
USEROPTS . default _quality = $ ( "#us-default-quality" ) . val ( ) ;
2013-12-25 21:18:21 +00:00
USEROPTS . show _timestamps = $ ( "#us-chat-timestamp" ) . prop ( "checked" ) ;
USEROPTS . sort _rank = $ ( "#us-sort-rank" ) . prop ( "checked" ) ;
USEROPTS . sort _afk = $ ( "#us-sort-afk" ) . prop ( "checked" ) ;
2014-05-26 20:22:20 +00:00
USEROPTS . blink _title = $ ( "#us-blink-title" ) . val ( ) ;
USEROPTS . boop = $ ( "#us-ping-sound" ) . val ( ) ;
2018-10-07 20:55:34 +00:00
USEROPTS . notifications = $ ( "#us-notifications" ) . val ( ) ;
2013-12-25 21:18:21 +00:00
USEROPTS . chatbtn = $ ( "#us-sendbtn" ) . prop ( "checked" ) ;
2014-10-24 04:21:44 +00:00
USEROPTS . no _emotes = $ ( "#us-no-emotes" ) . prop ( "checked" ) ;
2016-07-12 05:19:39 +00:00
USEROPTS . strip _image = $ ( "#us-strip-image" ) . prop ( "checked" ) ;
2017-01-11 06:26:46 +00:00
USEROPTS . chat _tab _method = $ ( "#us-chat-tab-method" ) . val ( ) ;
2013-12-25 21:18:21 +00:00
if ( CLIENT . rank >= 2 ) {
USEROPTS . modhat = $ ( "#us-modflair" ) . prop ( "checked" ) ;
2014-04-01 16:52:20 +00:00
USEROPTS . show _shadowchat = $ ( "#us-shadowchat" ) . prop ( "checked" ) ;
2013-12-25 21:18:21 +00:00
}
storeOpts ( ) ;
2014-01-30 04:50:14 +00:00
applyOpts ( ) ;
2013-12-25 21:18:21 +00:00
}
function storeOpts ( ) {
2013-06-11 23:51:00 +00:00
for ( var key in USEROPTS ) {
2013-07-03 02:40:40 +00:00
setOpt ( key , USEROPTS [ key ] ) ;
2013-06-11 23:51:00 +00:00
}
}
function applyOpts ( ) {
2014-01-30 04:50:14 +00:00
if ( $ ( "#usertheme" ) . attr ( "href" ) !== USEROPTS . theme ) {
2014-12-19 19:39:10 +00:00
var old = $ ( "#usertheme" ) . attr ( "id" , "usertheme_old" ) ;
2014-02-02 18:41:41 +00:00
var theme = USEROPTS . theme ;
if ( theme === "default" ) {
2016-07-08 11:31:32 +00:00
theme = DEFAULT _THEME ;
2014-01-30 04:50:14 +00:00
}
2014-02-02 18:41:41 +00:00
$ ( "<link/>" ) . attr ( "rel" , "stylesheet" )
. attr ( "type" , "text/css" )
. attr ( "id" , "usertheme" )
. attr ( "href" , theme )
2014-12-19 19:39:10 +00:00
. attr ( "onload" , "$('#usertheme_old').remove()" )
2014-02-02 18:41:41 +00:00
. appendTo ( $ ( "head" ) ) ;
2014-01-30 04:50:14 +00:00
fixWeirdButtonAlignmentIssue ( ) ;
2013-06-11 23:51:00 +00:00
}
2014-01-26 06:13:33 +00:00
switch ( USEROPTS . layout ) {
case "synchtube-fluid" :
fluidLayout ( ) ;
2013-06-11 23:51:00 +00:00
case "synchtube" :
synchtubeLayout ( ) ;
break ;
case "fluid" :
fluidLayout ( ) ;
break ;
2014-01-26 20:15:50 +00:00
case "hd" :
hdLayout ( ) ;
break ;
2013-06-11 23:51:00 +00:00
default :
2014-03-29 21:57:53 +00:00
compactLayout ( ) ;
2013-06-11 23:51:00 +00:00
break ;
}
if ( USEROPTS . hidevid ) {
2014-02-06 16:37:00 +00:00
removeVideo ( ) ;
2013-06-11 23:51:00 +00:00
}
$ ( "#chatbtn" ) . remove ( ) ;
if ( USEROPTS . chatbtn ) {
2013-12-25 21:18:21 +00:00
var btn = $ ( "<button/>" ) . addClass ( "btn btn-default btn-block" )
2013-06-11 23:51:00 +00:00
. text ( "Send" )
. attr ( "id" , "chatbtn" )
. appendTo ( $ ( "#chatwrap" ) ) ;
btn . click ( function ( ) {
if ( $ ( "#chatline" ) . val ( ) . trim ( ) ) {
socket . emit ( "chatMsg" , {
2014-05-21 03:11:40 +00:00
msg : $ ( "#chatline" ) . val ( ) ,
meta : { }
2013-06-11 23:51:00 +00:00
} ) ;
$ ( "#chatline" ) . val ( "" ) ;
}
} ) ;
}
2013-09-21 07:22:51 +00:00
if ( USEROPTS . modhat ) {
$ ( "#modflair" ) . removeClass ( "label-default" )
. addClass ( "label-success" ) ;
} else {
$ ( "#modflair" ) . removeClass ( "label-success" )
. addClass ( "label-default" ) ;
}
2018-10-07 20:55:34 +00:00
if ( USEROPTS . notifications !== "never" ) {
if ( "Notification" in window ) {
Notification . requestPermission ( ) . then ( function ( permission ) {
if ( permission !== "granted" ) {
USEROPTS . notifications = "never" ;
}
} ) ;
}
else {
USEROPTS . notifications = "never" ;
}
2020-03-14 00:43:25 +00:00
}
2013-06-11 23:51:00 +00:00
}
2016-08-08 05:07:52 +00:00
function parseTimeout ( t ) {
var m ;
if ( m = t . match ( /^(\d+):(\d+):(\d+)$/ ) ) {
// HH:MM:SS
return parseInt ( m [ 1 ] , 10 ) * 3600 + parseInt ( m [ 2 ] , 10 ) * 60 + parseInt ( m [ 3 ] , 10 ) ;
} else if ( m = t . match ( /^(\d+):(\d+)$/ ) ) {
// MM:SS
return parseInt ( m [ 1 ] , 10 ) * 60 + parseInt ( m [ 2 ] , 10 ) ;
} else if ( m = t . match ( /^(\d+)$/ ) ) {
// Seconds
return parseInt ( m [ 1 ] , 10 ) ;
} else {
throw new Error ( "Invalid timeout value '" + t + "'" ) ;
2016-05-21 23:52:48 +00:00
}
2016-08-08 05:07:52 +00:00
}
2016-05-21 23:52:48 +00:00
2016-08-08 05:07:52 +00:00
function showPollMenu ( ) {
2013-06-11 23:51:00 +00:00
$ ( "#pollwrap .poll-menu" ) . remove ( ) ;
var menu = $ ( "<div/>" ) . addClass ( "well poll-menu" )
2013-12-19 17:14:48 +00:00
. prependTo ( $ ( "#pollwrap" ) ) ;
2013-06-11 23:51:00 +00:00
2013-12-19 17:14:48 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-sm btn-danger pull-right" )
2013-06-11 23:51:00 +00:00
. text ( "Cancel" )
. appendTo ( menu )
. click ( function ( ) {
menu . remove ( ) ;
} ) ;
$ ( "<strong/>" ) . text ( "Title" ) . appendTo ( menu ) ;
2013-12-19 17:14:48 +00:00
var title = $ ( "<input/>" ) . addClass ( "form-control" )
2017-03-21 04:37:32 +00:00
. attr ( "maxlength" , "255" )
2013-12-19 17:14:48 +00:00
. attr ( "type" , "text" )
2013-06-11 23:51:00 +00:00
. appendTo ( menu ) ;
2014-02-09 05:58:27 +00:00
$ ( "<strong/>" ) . text ( "Timeout (optional)" ) . appendTo ( menu ) ;
2016-05-21 23:52:48 +00:00
$ ( "<p/>" ) . text ( "If you specify a timeout, the poll will automatically " +
"be closed after that amount of time. You can either " +
"specify the number of seconds or use the format " +
"minutes:seconds. Examples: 90 (90 seconds), 5:30 " +
"(5 minutes, 30 seconds)" )
. addClass ( "text-muted" )
. appendTo ( menu ) ;
2014-02-09 05:58:27 +00:00
var timeout = $ ( "<input/>" ) . addClass ( "form-control" )
. attr ( "type" , "text" )
. appendTo ( menu ) ;
2016-05-21 23:52:48 +00:00
var timeoutError = null ;
2014-02-09 05:58:27 +00:00
2014-11-16 17:15:08 +00:00
var checkboxOuter = $ ( "<div/>" ) . addClass ( "checkbox" ) . appendTo ( menu ) ;
2016-05-21 23:52:48 +00:00
var lbl = $ ( "<label/>" ) . text ( "Hide poll results until it closes" )
2014-11-16 17:15:08 +00:00
. appendTo ( checkboxOuter ) ;
2013-09-12 01:22:00 +00:00
var hidden = $ ( "<input/>" ) . attr ( "type" , "checkbox" )
2014-11-16 17:15:08 +00:00
. prependTo ( lbl ) ;
2013-09-12 01:22:00 +00:00
2021-08-12 04:13:59 +00:00
var retainVotesOuter = $ ( "<div/>" ) . addClass ( "checkbox" ) . appendTo ( menu ) ;
var retainVotesLbl = $ ( "<label/>" ) . text ( "Keep poll vote after user leaves" )
. appendTo ( retainVotesOuter ) ;
var retainVotes = $ ( "<input/>" ) . attr ( "type" , "checkbox" )
. prependTo ( retainVotesLbl ) ;
2013-06-11 23:51:00 +00:00
$ ( "<strong/>" ) . text ( "Options" ) . appendTo ( menu ) ;
2013-12-19 17:14:48 +00:00
var addbtn = $ ( "<button/>" ) . addClass ( "btn btn-sm btn-default" )
2013-06-11 23:51:00 +00:00
. text ( "Add Option" )
. appendTo ( menu ) ;
function addOption ( ) {
2013-12-19 17:14:48 +00:00
$ ( "<input/>" ) . addClass ( "form-control" )
. attr ( "type" , "text" )
2017-03-21 04:37:32 +00:00
. attr ( "maxlength" , "255" )
2013-06-11 23:51:00 +00:00
. addClass ( "poll-menu-option" )
. insertBefore ( addbtn ) ;
}
addbtn . click ( addOption ) ;
addOption ( ) ;
addOption ( ) ;
2013-12-19 17:14:48 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-default btn-block" )
2013-06-11 23:51:00 +00:00
. text ( "Open Poll" )
. appendTo ( menu )
. click ( function ( ) {
2016-05-21 23:52:48 +00:00
var t = timeout . val ( ) . trim ( ) ;
if ( t ) {
try {
t = parseTimeout ( t ) ;
} catch ( e ) {
if ( timeoutError ) {
timeoutError . remove ( ) ;
}
timeoutError = $ ( "<p/>" ) . addClass ( "text-danger" ) . text ( e . message ) ;
timeoutError . insertAfter ( timeout ) ;
timeout . focus ( ) ;
return ;
}
} else {
t = undefined ;
}
2014-02-09 05:58:27 +00:00
var opts = [ ] ;
2013-06-11 23:51:00 +00:00
menu . find ( ".poll-menu-option" ) . each ( function ( ) {
if ( $ ( this ) . val ( ) != "" )
opts . push ( $ ( this ) . val ( ) ) ;
} ) ;
socket . emit ( "newPoll" , {
title : title . val ( ) ,
2013-09-12 01:22:00 +00:00
opts : opts ,
2014-02-09 05:58:27 +00:00
obscured : hidden . prop ( "checked" ) ,
2021-08-12 04:13:59 +00:00
retainVotes : retainVotes . prop ( "checked" ) ,
2016-05-21 23:52:48 +00:00
timeout : t
2017-03-21 04:37:32 +00:00
} , function ack ( result ) {
if ( result . error ) {
modalAlert ( {
title : 'Error creating poll' ,
textContent : result . error . message
} ) ;
} else {
menu . remove ( ) ;
}
2013-06-11 23:51:00 +00:00
} ) ;
} ) ;
}
function scrollChat ( ) {
2015-12-06 01:57:33 +00:00
scrollAndIgnoreEvent ( $ ( "#messagebuffer" ) . prop ( "scrollHeight" ) ) ;
2015-11-29 17:28:03 +00:00
$ ( "#newmessages-indicator" ) . remove ( ) ;
2013-06-11 23:51:00 +00:00
}
2015-12-06 01:57:33 +00:00
function scrollAndIgnoreEvent ( top ) {
IGNORE _SCROLL _EVENT = true ;
$ ( "#messagebuffer" ) . scrollTop ( top ) ;
}
2013-06-11 23:51:00 +00:00
function hasPermission ( key ) {
if ( key . indexOf ( "playlist" ) == 0 && CHANNEL . openqueue ) {
var key2 = "o" + key ;
var v = CHANNEL . perms [ key2 ] ;
if ( typeof v == "number" && CLIENT . rank >= v ) {
return true ;
}
}
var v = CHANNEL . perms [ key ] ;
if ( typeof v != "number" ) {
return false ;
}
return CLIENT . rank >= v ;
}
2013-06-20 18:54:15 +00:00
function setVisible ( selector , bool ) {
// I originally added this check because of a race condition
// Now it seems to work without but I don't trust it
if ( $ ( selector ) && $ ( selector ) . attr ( "id" ) != selector . substring ( 1 ) ) {
setTimeout ( function ( ) {
setVisible ( selector , bool ) ;
} , 100 ) ;
return ;
}
var disp = bool ? "" : "none" ;
$ ( selector ) . css ( "display" , disp ) ;
}
2014-01-15 06:16:29 +00:00
function setParentVisible ( selector , bool ) {
var disp = bool ? "" : "none" ;
$ ( selector ) . parent ( ) . css ( "display" , disp ) ;
}
2013-06-20 18:43:37 +00:00
function handleModPermissions ( ) {
2014-01-09 23:16:09 +00:00
$ ( "#cs-chanranks-adm" ) . attr ( "disabled" , CLIENT . rank < 4 ) ;
$ ( "#cs-chanranks-owner" ) . attr ( "disabled" , CLIENT . rank < 4 ) ;
2013-06-20 18:43:37 +00:00
/* update channel controls */
2014-01-15 06:16:29 +00:00
$ ( "#cs-pagetitle" ) . val ( CHANNEL . opts . pagetitle ) ;
$ ( "#cs-pagetitle" ) . attr ( "disabled" , CLIENT . rank < 3 ) ;
$ ( "#cs-externalcss" ) . val ( CHANNEL . opts . externalcss ) ;
$ ( "#cs-externalcss" ) . attr ( "disabled" , CLIENT . rank < 3 ) ;
$ ( "#cs-externaljs" ) . val ( CHANNEL . opts . externaljs ) ;
$ ( "#cs-externaljs" ) . attr ( "disabled" , CLIENT . rank < 3 ) ;
2014-01-16 17:53:34 +00:00
$ ( "#cs-chat_antiflood" ) . prop ( "checked" , CHANNEL . opts . chat _antiflood ) ;
2013-12-07 23:30:34 +00:00
if ( "chat_antiflood_params" in CHANNEL . opts ) {
2014-01-16 17:53:34 +00:00
$ ( "#cs-chat_antiflood_burst" ) . val ( CHANNEL . opts . chat _antiflood _params . burst ) ;
$ ( "#cs-chat_antiflood_sustained" ) . val ( CHANNEL . opts . chat _antiflood _params . sustained ) ;
2013-11-25 22:20:15 +00:00
}
2014-01-15 06:16:29 +00:00
$ ( "#cs-show_public" ) . prop ( "checked" , CHANNEL . opts . show _public ) ;
$ ( "#cs-show_public" ) . attr ( "disabled" , CLIENT . rank < 3 ) ;
$ ( "#cs-password" ) . val ( CHANNEL . opts . password || "" ) ;
$ ( "#cs-password" ) . attr ( "disabled" , CLIENT . rank < 3 ) ;
$ ( "#cs-enable_link_regex" ) . prop ( "checked" , CHANNEL . opts . enable _link _regex ) ;
$ ( "#cs-afk_timeout" ) . val ( CHANNEL . opts . afk _timeout ) ;
$ ( "#cs-allow_voteskip" ) . prop ( "checked" , CHANNEL . opts . allow _voteskip ) ;
$ ( "#cs-voteskip_ratio" ) . val ( CHANNEL . opts . voteskip _ratio ) ;
2014-09-02 22:30:41 +00:00
$ ( "#cs-allow_dupes" ) . prop ( "checked" , CHANNEL . opts . allow _dupes ) ;
2014-09-02 22:28:16 +00:00
$ ( "#cs-torbanned" ) . prop ( "checked" , CHANNEL . opts . torbanned ) ;
2018-04-07 18:24:52 +00:00
$ ( "#cs-block_anonymous_users" ) . prop ( "checked" , CHANNEL . opts . block _anonymous _users ) ;
2014-09-02 22:28:16 +00:00
$ ( "#cs-allow_ascii_control" ) . prop ( "checked" , CHANNEL . opts . allow _ascii _control ) ;
2014-10-06 16:32:25 +00:00
$ ( "#cs-playlist_max_per_user" ) . val ( CHANNEL . opts . playlist _max _per _user || 0 ) ;
2017-04-04 04:18:40 +00:00
$ ( "#cs-playlist_max_duration_per_user" ) . val ( formatTime ( CHANNEL . opts . playlist _max _duration _per _user ) ) ;
2016-08-08 05:07:52 +00:00
$ ( "#cs-new_user_chat_delay" ) . val ( formatTime ( CHANNEL . opts . new _user _chat _delay || 0 ) ) ;
$ ( "#cs-new_user_chat_link_delay" ) . val ( formatTime ( CHANNEL . opts . new _user _chat _link _delay || 0 ) ) ;
2017-04-04 04:18:40 +00:00
$ ( "#cs-maxlength" ) . val ( formatTime ( CHANNEL . opts . maxlength ) ) ;
2014-01-15 06:16:29 +00:00
$ ( "#cs-csstext" ) . val ( CHANNEL . css ) ;
$ ( "#cs-jstext" ) . val ( CHANNEL . js ) ;
2015-01-09 02:06:41 +00:00
$ ( "#cs-motdtext" ) . val ( CHANNEL . motd ) ;
2014-01-15 06:16:29 +00:00
setParentVisible ( "a[href='#cs-motdeditor']" , hasPermission ( "motdedit" ) ) ;
2014-02-28 05:38:22 +00:00
setParentVisible ( "a[href='#cs-permedit']" , CLIENT . rank >= 3 ) ;
2014-01-15 06:16:29 +00:00
setParentVisible ( "a[href='#cs-banlist']" , hasPermission ( "ban" ) ) ;
setParentVisible ( "a[href='#cs-csseditor']" , CLIENT . rank >= 3 ) ;
setParentVisible ( "a[href='#cs-jseditor']" , CLIENT . rank >= 3 ) ;
2014-02-28 05:38:22 +00:00
setParentVisible ( "a[href='#cs-chatfilters']" , hasPermission ( "filteredit" ) ) ;
setParentVisible ( "a[href='#cs-emotes']" , hasPermission ( "emoteedit" ) ) ;
2014-01-15 06:16:29 +00:00
setParentVisible ( "a[href='#cs-chanranks']" , CLIENT . rank >= 3 ) ;
setParentVisible ( "a[href='#cs-chanlog']" , CLIENT . rank >= 3 ) ;
2014-01-23 22:03:50 +00:00
$ ( "#cs-chatfilters-import" ) . attr ( "disabled" , ! hasPermission ( "filterimport" ) ) ;
2014-02-28 05:38:22 +00:00
$ ( "#cs-emotes-import" ) . attr ( "disabled" , ! hasPermission ( "filterimport" ) ) ;
2013-06-20 18:43:37 +00:00
}
2013-06-11 23:51:00 +00:00
function handlePermissionChange ( ) {
2013-12-19 17:14:48 +00:00
if ( CLIENT . rank >= 2 ) {
handleModPermissions ( ) ;
2013-06-11 23:51:00 +00:00
}
2014-01-24 04:59:08 +00:00
$ ( "#qlockbtn" ) . attr ( "disabled" , ! hasPermission ( "playlistlock" ) ) ;
2014-01-19 07:45:20 +00:00
setVisible ( "#showchansettings" , CLIENT . rank >= 2 ) ;
2013-12-19 17:14:48 +00:00
setVisible ( "#playlistmanagerwrap" , CLIENT . rank >= 1 ) ;
2013-09-21 07:22:51 +00:00
setVisible ( "#modflair" , CLIENT . rank >= 2 ) ;
2013-12-19 17:14:48 +00:00
setVisible ( "#guestlogin" , CLIENT . rank < 0 ) ;
setVisible ( "#chatline" , CLIENT . rank >= 0 ) ;
2014-02-18 01:06:49 +00:00
setVisible ( "#queue" , hasPermission ( "seeplaylist" ) ) ;
setVisible ( "#plmeta" , hasPermission ( "seeplaylist" ) ) ;
$ ( "#getplaylist" ) . attr ( "disabled" , ! hasPermission ( "seeplaylist" ) ) ;
2013-09-21 07:22:51 +00:00
2014-09-05 01:53:18 +00:00
setVisible ( "#showplaylistmanager" , hasPermission ( "seeplaylist" ) ) ;
2014-01-14 00:31:12 +00:00
setVisible ( "#showmediaurl" , hasPermission ( "playlistadd" ) ) ;
setVisible ( "#showcustomembed" , hasPermission ( "playlistaddcustom" ) ) ;
2013-06-11 23:51:00 +00:00
$ ( "#queue_next" ) . attr ( "disabled" , ! hasPermission ( "playlistnext" ) ) ;
2013-06-23 18:35:40 +00:00
if ( hasPermission ( "playlistadd" ) ||
hasPermission ( "playlistmove" ) ||
hasPermission ( "playlistjump" ) ||
hasPermission ( "playlistdelete" ) ||
hasPermission ( "settemp" ) ) {
2013-07-30 00:06:01 +00:00
if ( USEROPTS . first _visit && $ ( "#plonotification" ) . length == 0 ) {
2013-06-23 18:35:40 +00:00
var al = makeAlert ( "Playlist Options" , [
"From the Options menu, you can choose to automatically" ,
" hide the buttons on each entry (and show them when" ,
" you right click). You can also choose to use the old" ,
" style of playlist buttons." ,
"<br>" ] . join ( "" ) )
2013-07-30 00:06:01 +00:00
. attr ( "id" , "plonotification" )
2014-02-02 18:41:41 +00:00
. insertAfter ( $ ( "#queuefail" ) ) ;
2013-06-23 18:35:40 +00:00
al . find ( ".close" ) . remove ( ) ;
$ ( "<button/>" ) . addClass ( "btn btn-primary" )
. text ( "Dismiss" )
2014-02-02 18:41:41 +00:00
. appendTo ( al . find ( ".alert" ) )
2013-06-23 18:35:40 +00:00
. click ( function ( ) {
USEROPTS . first _visit = false ;
2013-12-25 21:18:21 +00:00
storeOpts ( ) ;
2014-01-14 06:52:56 +00:00
al . hide ( "fade" , function ( ) {
2013-06-23 18:35:40 +00:00
al . remove ( ) ;
} ) ;
} ) ;
}
}
2013-06-11 23:51:00 +00:00
if ( hasPermission ( "playlistmove" ) ) {
$ ( "#queue" ) . sortable ( "enable" ) ;
$ ( "#queue" ) . addClass ( "queue_sortable" ) ;
}
else {
$ ( "#queue" ) . sortable ( "disable" ) ;
$ ( "#queue" ) . removeClass ( "queue_sortable" ) ;
}
setVisible ( "#clearplaylist" , hasPermission ( "playlistclear" ) ) ;
setVisible ( "#shuffleplaylist" , hasPermission ( "playlistshuffle" ) ) ;
2014-02-18 00:56:36 +00:00
if ( ! hasPermission ( "addnontemp" ) ) {
$ ( ".add-temp" ) . prop ( "checked" , true ) ;
$ ( ".add-temp" ) . attr ( "disabled" , true ) ;
} else {
$ ( ".add-temp" ) . attr ( "disabled" , false ) ;
}
2013-06-11 23:51:00 +00:00
2014-01-30 04:50:14 +00:00
fixWeirdButtonAlignmentIssue ( ) ;
2014-01-19 02:18:00 +00:00
2013-06-11 23:51:00 +00:00
setVisible ( "#newpollbtn" , hasPermission ( "pollctl" ) ) ;
2013-09-12 03:16:56 +00:00
$ ( "#voteskip" ) . attr ( "disabled" , ! hasPermission ( "voteskip" ) ||
! CHANNEL . opts . allow _voteskip ) ;
2013-06-11 23:51:00 +00:00
2013-06-19 23:41:49 +00:00
$ ( "#pollwrap .active" ) . find ( ".btn-danger" ) . remove ( ) ;
2013-06-11 23:51:00 +00:00
if ( hasPermission ( "pollctl" ) ) {
2013-06-19 23:41:49 +00:00
var poll = $ ( "#pollwrap .active" ) ;
2013-06-11 23:51:00 +00:00
if ( poll . length > 0 ) {
$ ( "<button/>" ) . addClass ( "btn btn-danger pull-right" )
. text ( "End Poll" )
. insertAfter ( poll . find ( ".close" ) )
. click ( function ( ) {
socket . emit ( "closePoll" ) ;
} ) ;
}
}
2013-06-19 23:41:49 +00:00
var poll = $ ( "#pollwrap .active" ) ;
2013-06-11 23:51:00 +00:00
if ( poll . length > 0 ) {
poll . find ( ".btn" ) . attr ( "disabled" , ! hasPermission ( "pollvote" ) ) ;
}
var users = $ ( "#userlist" ) . children ( ) ;
for ( var i = 0 ; i < users . length ; i ++ ) {
2013-11-09 04:12:17 +00:00
addUserDropdown ( $ ( users [ i ] ) ) ;
2013-06-11 23:51:00 +00:00
}
2013-06-20 19:02:53 +00:00
$ ( "#chatline" ) . attr ( "disabled" , ! hasPermission ( "chat" ) ) ;
2021-03-23 05:58:38 +00:00
if ( ! hasPermission ( "chat" ) ) {
$ ( "#chatline" ) . attr ( "placeholder" , "Chat permissions are restricted on this channel" ) ;
} else {
$ ( "#chatline" ) . attr ( "placeholder" , "" ) ;
}
2013-06-11 23:51:00 +00:00
rebuildPlaylist ( ) ;
}
2014-01-30 04:50:14 +00:00
function fixWeirdButtonAlignmentIssue ( ) {
// Weird things happen to the alignment in chromium when I toggle visibility
// of the above buttons
// This fixes it?
var wtf = $ ( "#videocontrols" ) . removeClass ( "pull-right" ) ;
setTimeout ( function ( ) {
wtf . addClass ( "pull-right" ) ;
} , 1 ) ;
}
2013-06-11 23:51:00 +00:00
/* search stuff */
function clearSearchResults ( ) {
$ ( "#library" ) . html ( "" ) ;
2013-06-22 23:02:55 +00:00
$ ( "#search_clear" ) . remove ( ) ;
2013-07-31 15:05:07 +00:00
var p = $ ( "#library" ) . data ( "paginator" ) ;
if ( p ) {
p . paginator . html ( "" ) ;
2013-06-11 23:51:00 +00:00
}
}
2017-07-15 21:41:37 +00:00
function addLibraryButtons ( li , item , source ) {
2013-06-11 23:51:00 +00:00
var btns = $ ( "<div/>" ) . addClass ( "btn-group" )
. addClass ( "pull-left" )
. prependTo ( li ) ;
2017-07-15 21:41:37 +00:00
var id = item . id ;
var type = item . type ;
2013-09-23 21:25:45 +00:00
2013-06-11 23:51:00 +00:00
if ( hasPermission ( "playlistadd" ) ) {
if ( hasPermission ( "playlistnext" ) ) {
2013-12-19 17:14:48 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
2013-06-11 23:51:00 +00:00
. text ( "Next" )
. click ( function ( ) {
socket . emit ( "queue" , {
id : id ,
pos : "next" ,
2014-02-09 06:24:20 +00:00
type : type ,
temp : $ ( ".add-temp" ) . prop ( "checked" )
2013-06-11 23:51:00 +00:00
} ) ;
} )
. appendTo ( btns ) ;
}
2013-12-19 17:14:48 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
2013-06-11 23:51:00 +00:00
. text ( "End" )
. click ( function ( ) {
socket . emit ( "queue" , {
id : id ,
pos : "end" ,
2014-02-09 06:24:20 +00:00
type : type ,
temp : $ ( ".add-temp" ) . prop ( "checked" )
2013-06-11 23:51:00 +00:00
} ) ;
} )
. appendTo ( btns ) ;
}
2017-01-24 05:16:39 +00:00
if ( hasPermission ( "deletefromchannellib" ) && source === "library" ) {
2013-12-19 17:14:48 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-danger" )
. html ( "<span class='glyphicon glyphicon-trash'></span>" )
2013-06-11 23:51:00 +00:00
. click ( function ( ) {
socket . emit ( "uncache" , {
id : id
} ) ;
2014-01-14 06:52:56 +00:00
li . hide ( "fade" , function ( ) {
2013-06-11 23:51:00 +00:00
li . remove ( ) ;
} ) ;
} )
. appendTo ( btns ) ;
}
}
/* queue stuff */
2013-10-01 18:35:29 +00:00
var AsyncQueue = function ( ) {
this . _q = [ ] ;
this . _lock = false ;
this . _tm = 0 ;
} ;
AsyncQueue . prototype . next = function ( ) {
if ( this . _q . length > 0 ) {
if ( ! this . lock ( ) )
return ;
var item = this . _q . shift ( ) ;
var fn = item [ 0 ] , tm = item [ 1 ] ;
this . _tm = Date . now ( ) + item [ 1 ] ;
fn ( this ) ;
}
} ;
2013-07-03 15:26:10 +00:00
2013-10-01 18:35:29 +00:00
AsyncQueue . prototype . lock = function ( ) {
if ( this . _lock ) {
if ( this . _tm > 0 && Date . now ( ) > this . _tm ) {
this . _tm = 0 ;
return true ;
2013-07-03 15:26:10 +00:00
}
2013-10-01 18:35:29 +00:00
return false ;
}
this . _lock = true ;
return true ;
} ;
AsyncQueue . prototype . release = function ( ) {
var self = this ;
if ( ! self . _lock )
return false ;
self . _lock = false ;
self . next ( ) ;
return true ;
} ;
AsyncQueue . prototype . queue = function ( fn ) {
var self = this ;
self . _q . push ( [ fn , 20000 ] ) ;
self . next ( ) ;
} ;
AsyncQueue . prototype . reset = function ( ) {
this . _q = [ ] ;
this . _lock = false ;
} ;
var PL _ACTION _QUEUE = new AsyncQueue ( ) ;
2013-07-03 15:26:10 +00:00
// Because jQuery UI does weird things
2017-03-26 18:04:02 +00:00
// 2017-03-26: Does it really though? I have no idea if this is still needed.
2013-07-03 15:26:10 +00:00
function playlistFind ( uid ) {
var children = document . getElementById ( "queue" ) . children ;
for ( var i in children ) {
2017-03-26 18:04:02 +00:00
if ( typeof children [ i ] . className != "string" )
2013-07-03 15:26:10 +00:00
continue ;
2017-03-26 18:04:02 +00:00
if ( children [ i ] . className . split ( " " ) . indexOf ( "pluid-" + uid ) > 0 )
2013-07-03 15:26:10 +00:00
return children [ i ] ;
}
return false ;
}
2013-10-01 18:35:29 +00:00
function playlistMove ( from , after , cb ) {
2013-06-29 22:09:20 +00:00
var lifrom = $ ( ".pluid-" + from ) ;
2013-10-01 18:35:29 +00:00
if ( lifrom . length == 0 ) {
cb ( false ) ;
return ;
}
2013-06-29 22:09:20 +00:00
2013-06-11 23:51:00 +00:00
var q = $ ( "#queue" ) ;
2013-06-29 22:09:20 +00:00
if ( after === "prepend" ) {
2014-01-23 04:43:17 +00:00
lifrom . hide ( "blind" , function ( ) {
2013-06-27 22:15:29 +00:00
lifrom . detach ( ) ;
lifrom . prependTo ( q ) ;
2014-01-23 04:43:17 +00:00
lifrom . show ( "blind" , cb ) ;
2013-06-27 22:15:29 +00:00
} ) ;
}
2013-06-29 22:09:20 +00:00
else if ( after === "append" ) {
2014-01-23 04:43:17 +00:00
lifrom . hide ( "blind" , function ( ) {
2013-06-29 22:09:20 +00:00
lifrom . detach ( ) ;
lifrom . appendTo ( q ) ;
2014-01-23 04:43:17 +00:00
lifrom . show ( "blind" , cb ) ;
2013-06-29 22:09:20 +00:00
} ) ;
}
2013-06-27 22:15:29 +00:00
else {
2013-06-29 22:09:20 +00:00
var liafter = $ ( ".pluid-" + after ) ;
2013-10-01 18:35:29 +00:00
if ( liafter . length == 0 ) {
cb ( false ) ;
return ;
}
2014-01-23 04:43:17 +00:00
lifrom . hide ( "blind" , function ( ) {
2013-06-27 22:15:29 +00:00
lifrom . detach ( ) ;
lifrom . insertAfter ( liafter ) ;
2014-01-23 04:43:17 +00:00
lifrom . show ( "blind" , cb ) ;
2013-06-27 22:15:29 +00:00
} ) ;
}
2013-06-11 23:51:00 +00:00
}
2014-08-13 18:49:32 +00:00
2013-06-11 23:51:00 +00:00
function parseMediaLink ( url ) {
2022-01-19 14:00:00 +00:00
function parseShortCode ( url ) {
const type = url . slice ( 0 , 2 ) ;
let id = url . slice ( 3 ) ;
switch ( type ) {
// So we still trim DailyMotion URLs
case 'dm' :
return { type , id : id . match ( /([^\?&#_]+)/ ) [ 1 ] } ;
// Raw files need to keep the query string
case 'fi' :
case 'cm' :
return { type , id } ;
// Generic for the rest.
default :
return { type , id : id . match ( /([^\?&#]+)/ ) [ 1 ] } ;
2019-03-28 04:28:46 +00:00
}
}
2022-01-19 14:00:00 +00:00
const indeterminable = ( ) => {
return new Error (
'Could not determine video type. ' +
'Check https://git.io/fjtOK for a list of supported media providers.'
) ;
2017-02-24 04:10:57 +00:00
}
2022-01-19 14:00:00 +00:00
const livestream = ( ) => {
return new Error (
'This format of Livestream.com link is not supported. ' +
'You must use one that has the numeric account ID.'
) ;
2013-06-11 23:51:00 +00:00
}
2022-01-19 14:00:00 +00:00
if ( typeof url != 'string' ) {
2013-06-11 23:51:00 +00:00
return {
2022-01-19 14:00:00 +00:00
id : null ,
type : null
2013-06-11 23:51:00 +00:00
} ;
}
2022-01-19 14:00:00 +00:00
/* Shorthand URIs */
if ( url . match ( /^[a-z]{2}:/ ) ) {
return parseShortCode ( url ) ;
2013-06-11 23:51:00 +00:00
}
2022-01-19 14:00:00 +00:00
var data
try {
data = new URL ( url ) ;
} catch ( err ) {
throw indeterminable ( ) ;
2015-02-16 08:33:44 +00:00
}
2022-01-19 14:00:00 +00:00
if ( data . protocol == 'rtmp:' ) {
return { type : 'rt' , id : url } ;
2013-06-11 23:51:00 +00:00
}
2022-01-19 14:00:00 +00:00
if ( data . pathname . match ( /\.m3u8$/ ) ) {
return { type : 'hl' , id : url } ;
2013-06-11 23:51:00 +00:00
}
2022-01-19 14:00:00 +00:00
if ( data . pathname . match ( /\.json$/ ) ) {
return { type : 'cm' , id : url } ;
2013-06-11 23:51:00 +00:00
}
2013-06-12 03:37:12 +00:00
2022-01-19 14:00:00 +00:00
switch ( data . hostname . replace ( 'www.' , '' ) ) {
case 'youtube.com' :
if ( data . pathname == '/watch' ) {
return { type : 'yt' , id : data . searchParams . get ( 'v' ) }
}
if ( data . pathname . startsWith ( '/shorts/' ) ) {
return { type : 'yt' , id : data . pathname . slice ( 8 , 19 ) }
}
if ( data . pathname == '/playlist' ) {
return { type : 'yp' , id : data . searchParams . get ( 'list' ) }
}
case 'youtu.be' :
return { type : 'yt' , id : data . pathname . slice ( 1 ) }
2013-11-07 23:19:36 +00:00
2022-01-19 14:00:00 +00:00
case 'twitch.tv' :
if ( data . pathname . includes ( '/clip/' ) ) {
return { type : 'tc' , id : data . pathname . split ( '/' ) . pop ( ) }
}
if ( data . pathname . startsWith ( '/videos/' ) ) {
return { type : 'tv' , id : ` v ${ data . pathname . split ( '/' ) . pop ( ) } ` }
}
return { type : 'tw' , id : data . pathname . slice ( 1 ) }
case 'clips.twitch.tv' :
return { type : 'tc' , id : data . pathname . slice ( 1 ) }
case 'livestream.com' :
if ( data . pathname . startsWith ( '/accounts/' ) ) {
const pattern = new RegExp ( '/accounts/(?<account>[0-9]+)/events/(?<event>[0-9]+)' ) ;
const m = data . pathname . match ( pattern ) ;
if ( m . groups . account && m . groups . event ) {
return { type : 'li' , id : ` ${ m . groups . account } ; ${ m . groups . event } ` } ;
}
} else {
throw livestream ( ) ;
}
2016-08-07 04:14:52 +00:00
2022-01-19 14:00:00 +00:00
case 'vimeo.com' :
return { type : 'vi' , id : data . pathname . slice ( 1 ) }
case 'dailymotion.com' :
return { type : 'dm' , id : data . pathname . slice ( '7' ) }
case 'soundcloud.com' :
return { type : 'sc' , id : url }
case 'streamable.com' :
return { type : 'sb' , id : data . pathname . slice ( 1 ) }
case 'docs.google.com' :
case 'drive.google.com' :
if ( data . pathname . startsWith ( '/file/' ) ) {
return { type : 'gd' , id : data . pathname . slice ( '8' ) . split ( '/' ) . shift ( ) }
}
if ( data . pathname == '/open' ) {
return { type : 'gd' , id : data . searchParams . get ( 'id' ) }
}
2016-08-03 05:35:00 +00:00
}
2022-01-19 14:00:00 +00:00
/* Raw file (server will check) */
if ( data . protocol . match ( /^http/ ) ) {
2015-12-13 19:18:28 +00:00
return {
2022-01-19 14:00:00 +00:00
id : url ,
2015-12-13 19:18:28 +00:00
type : "fi"
} ;
}
2014-06-04 04:21:00 +00:00
2022-01-19 14:00:00 +00:00
throw indeterminable ( ) ;
2013-06-11 23:51:00 +00:00
}
2022-01-19 14:00:00 +00:00
2013-06-11 23:51:00 +00:00
function sendVideoUpdate ( ) {
2013-11-15 04:23:33 +00:00
if ( ! CLIENT . leader ) {
return ;
}
2013-08-06 03:30:16 +00:00
PLAYER . getTime ( function ( seconds ) {
2013-06-11 23:51:00 +00:00
socket . emit ( "mediaUpdate" , {
2015-07-05 20:29:06 +00:00
id : PLAYER . mediaId ,
2013-06-11 23:51:00 +00:00
currentTime : seconds ,
paused : PLAYER . paused ,
2015-07-05 20:29:06 +00:00
type : PLAYER . mediaType
2013-06-11 23:51:00 +00:00
} ) ;
} ) ;
}
/* chat */
2016-07-12 05:19:39 +00:00
function stripImages ( msg ) {
if ( ! USEROPTS . strip _image ) {
return msg ;
}
return msg . replace ( IMAGE _MATCH , function ( match , img ) {
2016-08-07 03:44:15 +00:00
return CHANNEL . opts . enable _link _regex ?
2016-07-12 05:19:39 +00:00
'<a target="_blank" href="' + img + '">' + img + '</a>' : img ;
} ) ;
}
2014-02-15 07:40:14 +00:00
function formatChatMessage ( data , last ) {
2013-11-19 19:45:24 +00:00
// Backwards compat
if ( ! data . meta || data . msgclass ) {
data . meta = {
2013-11-19 22:14:33 +00:00
addClass : data . msgclass ,
// And the award for "variable name most like Java source code" goes to...
addClassToNameAndTimestamp : data . msgclass
2013-11-19 19:45:24 +00:00
} ;
}
2013-11-18 17:11:00 +00:00
// Phase 1: Determine whether to show the username or not
2014-02-15 07:40:14 +00:00
var skip = data . username === last . name ;
2013-11-18 17:11:00 +00:00
if ( data . meta . addClass === "server-whisper" )
2013-11-17 19:12:56 +00:00
skip = true ;
// Prevent impersonation by abuse of the bold filter
if ( data . msg . match ( /^\s*<strong>\w+\s*:\s*<\/strong>\s*/ ) )
skip = false ;
2013-11-18 17:11:00 +00:00
if ( data . meta . forceShowName )
skip = false ;
2016-07-12 05:19:39 +00:00
data . msg = stripImages ( data . msg ) ;
2014-02-10 05:53:46 +00:00
data . msg = execEmotes ( data . msg ) ;
2014-02-15 07:40:14 +00:00
last . name = data . username ;
2013-11-17 19:12:56 +00:00
var div = $ ( "<div/>" ) ;
2013-11-18 17:11:00 +00:00
/ * d r i n k i s a s p e c i a l c a s e b e c a u s e t h e e n t i r e c o n t a i n e r g e t s t h e c l a s s , n o t
just the message * /
2013-11-17 21:32:19 +00:00
if ( data . meta . addClass === "drink" ) {
div . addClass ( "drink" ) ;
data . meta . addClass = "" ;
}
2013-11-18 17:11:00 +00:00
// Add timestamps (unless disabled)
2013-11-17 19:12:56 +00:00
if ( USEROPTS . show _timestamps ) {
var time = $ ( "<span/>" ) . addClass ( "timestamp" ) . appendTo ( div ) ;
var timestamp = new Date ( data . time ) . toTimeString ( ) . split ( " " ) [ 0 ] ;
time . text ( "[" + timestamp + "] " ) ;
2013-11-19 22:14:33 +00:00
if ( data . meta . addClass && data . meta . addClassToNameAndTimestamp ) {
2013-11-17 19:12:56 +00:00
time . addClass ( data . meta . addClass ) ;
}
}
2013-11-18 17:11:00 +00:00
// Add username
2013-11-17 19:12:56 +00:00
var name = $ ( "<span/>" ) ;
if ( ! skip ) {
name . appendTo ( div ) ;
}
$ ( "<strong/>" ) . addClass ( "username" ) . text ( data . username + ": " ) . appendTo ( name ) ;
if ( data . meta . modflair ) {
name . addClass ( getNameColor ( data . meta . modflair ) ) ;
}
2013-11-19 22:14:33 +00:00
if ( data . meta . addClass && data . meta . addClassToNameAndTimestamp ) {
2013-11-17 19:12:56 +00:00
name . addClass ( data . meta . addClass ) ;
}
if ( data . meta . superadminflair ) {
name . addClass ( "label" )
. addClass ( data . meta . superadminflair . labelclass ) ;
2014-01-26 06:01:36 +00:00
$ ( "<span/>" ) . addClass ( data . meta . superadminflair . icon )
. addClass ( "glyphicon" )
2014-01-26 20:15:50 +00:00
. css ( "margin-right" , "3px" )
2013-11-17 19:12:56 +00:00
. prependTo ( name ) ;
}
2013-11-18 17:11:00 +00:00
// Add the message itself
2013-11-17 19:12:56 +00:00
var message = $ ( "<span/>" ) . appendTo ( div ) ;
message [ 0 ] . innerHTML = data . msg ;
2013-11-18 17:11:00 +00:00
// For /me the username is part of the message
2013-11-17 19:12:56 +00:00
if ( data . meta . action ) {
name . remove ( ) ;
message [ 0 ] . innerHTML = data . username + " " + data . msg ;
}
if ( data . meta . addClass ) {
message . addClass ( data . meta . addClass ) ;
}
2014-04-01 16:52:20 +00:00
if ( data . meta . shadow ) {
div . addClass ( "chat-shadow" ) ;
}
2013-11-17 19:12:56 +00:00
return div ;
}
2013-06-11 23:51:00 +00:00
function addChatMessage ( data ) {
2013-11-18 17:11:00 +00:00
if ( IGNORED . indexOf ( data . username ) !== - 1 ) {
2013-06-11 23:51:00 +00:00
return ;
}
2014-04-01 16:52:20 +00:00
if ( data . meta . shadow && ! USEROPTS . show _shadowchat ) {
return ;
}
2015-12-06 01:57:33 +00:00
var msgBuf = $ ( "#messagebuffer" ) ;
2016-07-12 05:19:39 +00:00
var div = formatChatMessage ( data , LASTCHAT ) ;
2013-11-18 17:11:00 +00:00
// Incoming: a bunch of crap for the feature where if you hover over
// a message, it highlights messages from that user
2015-02-06 04:23:54 +00:00
var safeUsername = data . username . replace ( /[^\w-]/g , '\\$' ) ;
2014-11-25 01:32:06 +00:00
div . addClass ( "chat-msg-" + safeUsername ) ;
2015-12-06 01:57:33 +00:00
div . appendTo ( msgBuf ) ;
2013-06-11 23:51:00 +00:00
div . mouseover ( function ( ) {
2014-11-25 01:32:06 +00:00
$ ( ".chat-msg-" + safeUsername ) . addClass ( "nick-hover" ) ;
2013-06-11 23:51:00 +00:00
} ) ;
div . mouseleave ( function ( ) {
2014-11-25 01:32:06 +00:00
$ ( ".nick-hover" ) . removeClass ( "nick-hover" ) ;
2013-06-11 23:51:00 +00:00
} ) ;
2015-12-06 01:57:33 +00:00
var oldHeight = msgBuf . prop ( "scrollHeight" ) ;
2015-11-29 17:28:03 +00:00
var numRemoved = trimChatBuffer ( ) ;
if ( SCROLLCHAT ) {
2013-06-11 23:51:00 +00:00
scrollChat ( ) ;
2015-11-29 17:28:03 +00:00
} else {
var newMessageDiv = $ ( "#newmessages-indicator" ) ;
if ( ! newMessageDiv . length ) {
newMessageDiv = $ ( "<div/>" ) . attr ( "id" , "newmessages-indicator" )
. insertBefore ( $ ( "#chatline" ) ) ;
2015-11-29 18:29:56 +00:00
var bgHack = $ ( "<span/>" ) . attr ( "id" , "newmessages-indicator-bghack" )
. appendTo ( newMessageDiv ) ;
2015-11-29 17:28:03 +00:00
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-chevron-down" )
2015-11-29 18:29:56 +00:00
. appendTo ( bgHack ) ;
$ ( "<span/>" ) . text ( "New Messages Below" ) . appendTo ( bgHack ) ;
2015-11-29 17:28:03 +00:00
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-chevron-down" )
2015-11-29 18:29:56 +00:00
. appendTo ( bgHack ) ;
2015-12-06 01:57:33 +00:00
newMessageDiv . click ( function ( ) {
SCROLLCHAT = true ;
scrollChat ( ) ;
} ) ;
2015-11-29 17:28:03 +00:00
}
if ( numRemoved > 0 ) {
2015-12-06 01:57:33 +00:00
IGNORE _SCROLL _EVENT = true ;
var diff = oldHeight - msgBuf . prop ( "scrollHeight" ) ;
scrollAndIgnoreEvent ( msgBuf . scrollTop ( ) - diff ) ;
2015-11-29 17:28:03 +00:00
}
}
2014-05-26 20:22:20 +00:00
2015-12-06 01:57:33 +00:00
div . find ( "img" ) . load ( function ( ) {
if ( SCROLLCHAT ) {
scrollChat ( ) ;
} else if ( $ ( this ) . position ( ) . top < 0 ) {
scrollAndIgnoreEvent ( msgBuf . scrollTop ( ) + $ ( this ) . height ( ) ) ;
}
} ) ;
2014-05-26 20:22:20 +00:00
var isHighlight = false ;
if ( CLIENT . name && data . username != CLIENT . name ) {
2019-08-02 03:02:37 +00:00
if ( highlightsMe ( data . msg ) ) {
2013-06-11 23:51:00 +00:00
div . addClass ( "nick-highlight" ) ;
2014-05-26 20:22:20 +00:00
isHighlight = true ;
}
}
2018-10-07 20:55:34 +00:00
pingMessage ( isHighlight , data . username , $ ( div . children ( ) [ 2 ] ) . text ( ) ) ;
2014-05-26 20:22:20 +00:00
}
2019-08-02 03:02:37 +00:00
function highlightsMe ( message ) {
// TODO: distinguish between text and HTML attributes as noted in #819
return message . match ( new RegExp ( "(^|\\b)" + CLIENT . name + "($|\\b)" , "gi" ) ) ;
}
2015-06-04 04:57:51 +00:00
function trimChatBuffer ( ) {
var maxSize = window . CHATMAXSIZE ;
if ( ! maxSize || typeof maxSize !== "number" )
maxSize = parseInt ( maxSize || 100 , 10 ) || 100 ;
var buffer = document . getElementById ( "messagebuffer" ) ;
var count = buffer . childNodes . length - maxSize ;
for ( var i = 0 ; i < count ; i ++ ) {
buffer . firstChild . remove ( ) ;
}
2015-11-29 17:28:03 +00:00
return count ;
2015-06-04 04:57:51 +00:00
}
2018-10-07 20:55:34 +00:00
function pingMessage ( isHighlight , notificationTitle , notificationBody ) {
2014-05-26 20:22:20 +00:00
if ( ! FOCUSED ) {
if ( ! TITLE _BLINK && ( USEROPTS . blink _title === "always" ||
USEROPTS . blink _title === "onlyping" && isHighlight ) ) {
TITLE _BLINK = setInterval ( function ( ) {
if ( document . title == "*Chat*" )
document . title = PAGETITLE ;
else
document . title = "*Chat*" ;
} , 1000 ) ;
}
if ( USEROPTS . boop === "always" || ( USEROPTS . boop === "onlyping" &&
isHighlight ) ) {
CHATSOUND . play ( ) ;
2013-06-11 23:51:00 +00:00
}
2018-10-07 20:55:34 +00:00
if ( USEROPTS . notifications === "always" || ( USEROPTS . notifications === "onlyping" &&
isHighlight ) ) {
showDesktopNotification ( notificationTitle , notificationBody ) ;
}
2013-06-11 23:51:00 +00:00
}
}
2018-10-07 20:55:34 +00:00
function showDesktopNotification ( notificationTitle , notificationBody )
{
new Notification ( notificationTitle , { body : notificationBody , icon : null } ) ;
}
2013-06-11 23:51:00 +00:00
/* layouts */
2016-01-04 06:53:29 +00:00
function undoHDLayout ( ) {
$ ( "body" ) . removeClass ( "hd" ) ;
$ ( "#drinkbar" ) . detach ( ) . removeClass ( ) . addClass ( "col-lg-12 col-md-12" )
. appendTo ( "#drinkbarwrap" ) ;
$ ( "#chatwrap" ) . detach ( ) . removeClass ( ) . addClass ( "col-lg-5 col-md-5" )
. appendTo ( "#main" ) ;
$ ( "#videowrap" ) . detach ( ) . removeClass ( ) . addClass ( "col-lg-7 col-md-7" )
. appendTo ( "#main" ) ;
$ ( "#leftcontrols" ) . detach ( ) . removeClass ( ) . addClass ( "col-lg-5 col-md-5" )
. prependTo ( "#controlsrow" ) ;
$ ( "#plcontrol" ) . detach ( ) . appendTo ( "#rightcontrols" ) ;
$ ( "#videocontrols" ) . detach ( ) . appendTo ( "#rightcontrols" ) ;
$ ( "#playlistrow" ) . prepend ( '<div id="leftpane" class="col-lg-5 col-md-5" />' ) ;
$ ( "#leftpane" ) . append ( '<div id="leftpane-inner" class="row" />' ) ;
$ ( "#pollwrap" ) . detach ( ) . removeClass ( ) . addClass ( "col-lg-12 col-md-12" )
. appendTo ( "#leftpane-inner" ) ;
$ ( "#playlistmanagerwrap" ) . detach ( ) . removeClass ( ) . addClass ( "col-lg-12 col-md-12" )
. css ( "margin-top" , "10px" )
. appendTo ( "#leftpane-inner" ) ;
$ ( "#rightpane" ) . detach ( ) . removeClass ( ) . addClass ( "col-lg-7 col-md-7" )
. appendTo ( "#playlistrow" ) ;
$ ( "nav" ) . addClass ( "navbar-fixed-top" ) ;
$ ( "#mainpage" ) . css ( "padding-top" , "60px" ) ;
$ ( "#queue" ) . css ( "max-height" , "500px" ) ;
$ ( "#messagebuffer, #userlist" ) . css ( "max-height" , "" ) ;
}
2014-03-29 21:57:53 +00:00
function compactLayout ( ) {
/* Undo synchtube layout */
if ( $ ( "body" ) . hasClass ( "synchtube" ) ) {
2015-02-05 07:14:51 +00:00
$ ( "body" ) . removeClass ( "synchtube" )
2014-03-29 21:57:53 +00:00
$ ( "#chatwrap" ) . detach ( ) . insertBefore ( $ ( "#videowrap" ) ) ;
$ ( "#leftcontrols" ) . detach ( ) . insertBefore ( $ ( "#rightcontrols" ) ) ;
$ ( "#leftpane" ) . detach ( ) . insertBefore ( $ ( "#rightpane" ) ) ;
$ ( "#userlist" ) . css ( "float" , "left" ) ;
2015-02-05 07:14:51 +00:00
if ( $ ( "#userlisttoggle" ) . hasClass ( "glyphicon-chevron-left" ) ) {
$ ( "#userlisttoggle" ) . removeClass ( "glyphicon-chevron-left" ) . addClass ( "glyphicon-chevron-right" )
}
2015-02-05 07:27:25 +00:00
$ ( "#userlisttoggle" ) . removeClass ( "pull-right" ) . addClass ( "pull-left" )
2014-03-29 21:57:53 +00:00
}
/* Undo fluid layout */
2014-03-29 23:46:53 +00:00
if ( $ ( "body" ) . hasClass ( "fluid" ) ) {
2015-02-05 07:14:51 +00:00
$ ( "body" ) . removeClass ( "fluid" )
2014-03-29 23:46:53 +00:00
$ ( ".container-fluid" ) . removeClass ( "container-fluid" ) . addClass ( "container" ) ;
}
2014-03-29 21:57:53 +00:00
/* Undo HD layout */
if ( $ ( "body" ) . hasClass ( "hd" ) ) {
2016-01-04 06:53:29 +00:00
undoHDLayout ( ) ;
2014-03-29 21:57:53 +00:00
}
2014-11-12 01:48:08 +00:00
$ ( "body" ) . addClass ( "compact" ) ;
2014-11-13 01:56:29 +00:00
handleVideoResize ( ) ;
2014-03-29 21:57:53 +00:00
}
2013-06-11 23:51:00 +00:00
function fluidLayout ( ) {
2016-01-04 06:53:29 +00:00
if ( $ ( "body" ) . hasClass ( "hd" ) ) {
undoHDLayout ( ) ;
}
2014-01-26 03:29:56 +00:00
$ ( ".container" ) . removeClass ( "container" ) . addClass ( "container-fluid" ) ;
2014-02-16 19:27:01 +00:00
$ ( "footer .container-fluid" ) . removeClass ( "container-fluid" ) . addClass ( "container" ) ;
2014-01-26 20:15:50 +00:00
$ ( "body" ) . addClass ( "fluid" ) ;
2014-11-13 01:56:29 +00:00
handleVideoResize ( ) ;
2013-06-11 23:51:00 +00:00
}
function synchtubeLayout ( ) {
2016-01-04 06:53:29 +00:00
if ( $ ( "body" ) . hasClass ( "hd" ) ) {
undoHDLayout ( ) ;
}
2015-02-05 07:14:51 +00:00
if ( $ ( "#userlisttoggle" ) . hasClass ( "glyphicon-chevron-right" ) ) {
$ ( "#userlisttoggle" ) . removeClass ( "glyphicon-chevron-right" ) . addClass ( "glyphicon-chevron-left" )
}
2015-02-05 07:27:25 +00:00
$ ( "#userlisttoggle" ) . removeClass ( "pull-left" ) . addClass ( "pull-right" )
2013-06-11 23:51:00 +00:00
$ ( "#videowrap" ) . detach ( ) . insertBefore ( $ ( "#chatwrap" ) ) ;
2013-12-20 03:33:24 +00:00
$ ( "#rightcontrols" ) . detach ( ) . insertBefore ( $ ( "#leftcontrols" ) ) ;
$ ( "#rightpane" ) . detach ( ) . insertBefore ( $ ( "#leftpane" ) ) ;
2013-08-30 20:49:30 +00:00
$ ( "#userlist" ) . css ( "float" , "right" ) ;
2014-01-26 20:15:50 +00:00
$ ( "body" ) . addClass ( "synchtube" ) ;
}
2016-01-04 06:53:29 +00:00
/ *
* "HD" is kind of a misnomer . Should be renamed at some point .
* /
2014-01-26 20:15:50 +00:00
function hdLayout ( ) {
var videowrap = $ ( "#videowrap" ) ,
chatwrap = $ ( "#chatwrap" ) ,
playlist = $ ( "#rightpane" )
videowrap . detach ( ) . insertAfter ( $ ( "#drinkbar" ) )
. removeClass ( )
. addClass ( "col-md-8 col-md-offset-2" ) ;
playlist . detach ( ) . insertBefore ( chatwrap )
. removeClass ( )
. addClass ( "col-md-6" ) ;
chatwrap . removeClass ( )
. addClass ( "col-md-6" ) ;
var ch = "320px" ;
$ ( "#messagebuffer" ) . css ( "max-height" , ch ) ;
$ ( "#userlist" ) . css ( "max-height" , ch ) ;
$ ( "#queue" ) . css ( "max-height" , "312px" ) ;
$ ( "#leftcontrols" ) . detach ( )
. insertAfter ( chatwrap )
. removeClass ( )
. addClass ( "col-md-6" ) ;
$ ( "#playlistmanagerwrap" ) . detach ( )
. insertBefore ( $ ( "#leftcontrols" ) )
. css ( "margin-top" , "0" )
. removeClass ( )
. addClass ( "col-md-6" ) ;
$ ( "#showplaylistmanager" ) . addClass ( "btn-sm" ) ;
var plcontrolwrap = $ ( "<div/>" ) . addClass ( "col-md-12" )
. prependTo ( $ ( "#rightpane-inner" ) ) ;
$ ( "#plcontrol" ) . detach ( ) . appendTo ( plcontrolwrap ) ;
2014-01-30 04:50:14 +00:00
$ ( "#videocontrols" ) . detach ( )
2014-01-26 20:15:50 +00:00
. appendTo ( plcontrolwrap ) ;
$ ( "#controlswrap" ) . remove ( ) ;
$ ( "#pollwrap" ) . detach ( )
. insertAfter ( $ ( "#leftcontrols" ) )
. removeClass ( )
. addClass ( "col-md-6 col-md-offset-6" ) ;
2014-01-26 20:18:29 +00:00
$ ( "#leftpane" ) . remove ( ) ;
2014-01-26 20:15:50 +00:00
$ ( "nav.navbar-fixed-top" ) . removeClass ( "navbar-fixed-top" ) ;
$ ( "#mainpage" ) . css ( "padding-top" , "0" ) ;
$ ( "body" ) . addClass ( "hd" ) ;
2014-11-13 01:56:29 +00:00
handleVideoResize ( ) ;
2013-06-11 23:51:00 +00:00
}
function chatOnly ( ) {
2014-02-16 23:54:33 +00:00
var chat = $ ( "#chatwrap" ) . detach ( ) ;
2014-02-06 16:37:00 +00:00
removeVideo ( ) ;
2014-02-16 23:54:33 +00:00
$ ( "#wrap" ) . remove ( ) ;
$ ( "footer" ) . remove ( ) ;
chat . prependTo ( $ ( "body" ) ) ;
chat . css ( {
"min-height" : "100%" ,
"min-width" : "100%" ,
margin : "0" ,
padding : "0"
} ) ;
$ ( "<span/>" ) . addClass ( "label label-default pull-right pointer" )
. text ( "User Options" )
. appendTo ( $ ( "#chatheader" ) )
. click ( showUserOptions ) ;
$ ( "<span/>" ) . addClass ( "label label-default pull-right pointer" )
2014-02-28 19:55:29 +00:00
. attr ( "id" , "showchansettings" )
2014-02-16 23:54:33 +00:00
. text ( "Channel Settings" )
. appendTo ( $ ( "#chatheader" ) )
. click ( function ( ) {
$ ( "#channeloptions" ) . modal ( ) ;
} ) ;
2015-05-14 16:42:26 +00:00
$ ( "<span/>" ) . addClass ( "label label-default pull-right pointer" )
. text ( "Emote List" )
. appendTo ( $ ( "#chatheader" ) )
. click ( function ( ) {
2021-07-23 04:02:40 +00:00
EMOTELISTMODAL . modal ( ) ;
2015-05-14 16:42:26 +00:00
} ) ;
2014-02-28 19:55:29 +00:00
setVisible ( "#showchansettings" , CLIENT . rank >= 2 ) ;
2015-05-14 16:42:26 +00:00
2014-02-16 23:54:33 +00:00
$ ( "body" ) . addClass ( "chatOnly" ) ;
2014-11-11 04:43:49 +00:00
handleWindowResize ( ) ;
2013-06-11 23:51:00 +00:00
}
2013-06-17 22:16:59 +00:00
2014-11-11 04:43:49 +00:00
function handleWindowResize ( ) {
2014-02-16 23:54:33 +00:00
if ( $ ( "body" ) . hasClass ( "chatOnly" ) ) {
var h = $ ( "body" ) . outerHeight ( ) - $ ( "#chatline" ) . outerHeight ( ) -
$ ( "#chatheader" ) . outerHeight ( ) ;
$ ( "#messagebuffer" ) . outerHeight ( h ) ;
$ ( "#userlist" ) . outerHeight ( h ) ;
return ;
2014-11-11 04:43:49 +00:00
} else {
handleVideoResize ( ) ;
2014-02-16 23:54:33 +00:00
}
2017-05-25 15:04:19 +00:00
scrollChat ( ) ;
2014-11-11 04:43:49 +00:00
}
2014-01-26 03:48:57 +00:00
2014-11-11 04:43:49 +00:00
function handleVideoResize ( ) {
2014-11-15 22:52:18 +00:00
if ( $ ( "#ytapiplayer" ) . length === 0 ) return ;
2014-11-11 04:43:49 +00:00
var intv , ticks = 0 ;
var resize = function ( ) {
if ( ++ ticks > 10 ) clearInterval ( intv ) ;
2015-05-14 18:14:45 +00:00
if ( $ ( "#ytapiplayer" ) . parent ( ) . outerHeight ( ) <= 0 ) return ;
2014-11-11 04:43:49 +00:00
clearInterval ( intv ) ;
2014-01-26 20:15:50 +00:00
2014-11-16 17:19:14 +00:00
var responsiveFrame = $ ( "#ytapiplayer" ) . parent ( ) ;
var height = responsiveFrame . outerHeight ( ) - $ ( "#chatline" ) . outerHeight ( ) - 2 ;
2014-11-11 04:43:49 +00:00
$ ( "#messagebuffer" ) . height ( height ) ;
$ ( "#userlist" ) . height ( height ) ;
2014-11-12 01:48:08 +00:00
2014-11-16 17:19:14 +00:00
$ ( "#ytapiplayer" ) . attr ( "height" , VHEIGHT = responsiveFrame . outerHeight ( ) ) ;
$ ( "#ytapiplayer" ) . attr ( "width" , VWIDTH = responsiveFrame . outerWidth ( ) ) ;
2014-11-11 04:43:49 +00:00
} ;
2014-01-26 20:15:50 +00:00
2014-11-11 04:43:49 +00:00
if ( $ ( "#ytapiplayer" ) . height ( ) > 0 ) resize ( ) ;
2014-11-12 01:48:08 +00:00
else intv = setInterval ( resize , 500 ) ;
2014-01-26 03:48:57 +00:00
}
2014-11-11 04:43:49 +00:00
$ ( window ) . resize ( handleWindowResize ) ;
2014-11-12 01:48:08 +00:00
handleWindowResize ( ) ;
2014-01-26 03:48:57 +00:00
2015-05-06 19:14:56 +00:00
function removeVideo ( event ) {
2014-02-06 16:37:00 +00:00
try {
PLAYER . setVolume ( 0 ) ;
} catch ( e ) {
}
$ ( "#videowrap" ) . remove ( ) ;
2014-02-16 23:54:33 +00:00
$ ( "#chatwrap" ) . removeClass ( "col-lg-5 col-md-5" ) . addClass ( "col-md-12" ) ;
2015-05-06 19:14:56 +00:00
if ( event ) event . preventDefault ( ) ;
2014-02-06 16:37:00 +00:00
}
2013-06-17 22:16:59 +00:00
/* channel administration stuff */
function genPermissionsEditor ( ) {
2014-01-20 18:16:30 +00:00
$ ( "#cs-permedit" ) . html ( "" ) ;
2013-06-17 22:16:59 +00:00
var form = $ ( "<form/>" ) . addClass ( "form-horizontal" )
. attr ( "action" , "javascript:void(0)" )
2014-01-20 18:16:30 +00:00
. appendTo ( $ ( "#cs-permedit" ) ) ;
2013-06-17 22:16:59 +00:00
function makeOption ( text , key , permset , defval ) {
2014-01-20 18:16:30 +00:00
var group = $ ( "<div/>" ) . addClass ( "form-group" )
. appendTo ( form ) ;
$ ( "<label/>" ) . addClass ( "control-label col-sm-4" )
2013-06-17 22:16:59 +00:00
. text ( text )
. appendTo ( group ) ;
2014-01-20 18:16:30 +00:00
var controls = $ ( "<div/>" ) . addClass ( "col-sm-8" )
2013-06-17 22:16:59 +00:00
. appendTo ( group ) ;
2014-01-20 18:16:30 +00:00
var select = $ ( "<select/>" ) . addClass ( "form-control" )
. appendTo ( controls )
. data ( "key" , key ) ;
for ( var i = 0 ; i < permset . length ; i ++ ) {
2013-06-17 22:16:59 +00:00
$ ( "<option/>" ) . attr ( "value" , permset [ i ] [ 1 ] )
. text ( permset [ i ] [ 0 ] )
2014-01-20 18:16:30 +00:00
. attr ( "selected" , defval === permset [ i ] [ 1 ] )
2013-06-17 22:16:59 +00:00
. appendTo ( select ) ;
}
}
2014-01-20 18:16:30 +00:00
function addDivider ( text , nonewline ) {
$ ( "<hr/>" ) . appendTo ( form ) ;
if ( ! nonewline ) {
$ ( "<h3/>" ) . text ( text ) . appendTo ( form ) ;
}
2013-06-17 22:16:59 +00:00
}
var standard = [
[ "Anonymous" , "-1" ] ,
[ "Guest" , "0" ] ,
[ "Registered" , "1" ] ,
[ "Leader" , "1.5" ] ,
[ "Moderator" , "2" ] ,
2013-09-21 05:30:47 +00:00
[ "Channel Admin" , "3" ] ,
[ "Nobody" , "1000000" ]
2013-06-17 22:16:59 +00:00
] ;
2013-06-22 23:02:55 +00:00
var noanon = [
[ "Guest" , "0" ] ,
[ "Registered" , "1" ] ,
[ "Leader" , "1.5" ] ,
[ "Moderator" , "2" ] ,
2013-09-21 05:30:47 +00:00
[ "Channel Admin" , "3" ] ,
[ "Nobody" , "1000000" ]
2013-06-22 23:02:55 +00:00
] ;
2013-06-17 22:16:59 +00:00
var modleader = [
[ "Leader" , "1.5" ] ,
[ "Moderator" , "2" ] ,
2013-09-21 05:30:47 +00:00
[ "Channel Admin" , "3" ] ,
[ "Nobody" , "1000000" ]
2013-06-17 22:16:59 +00:00
] ;
var modplus = [
[ "Moderator" , "2" ] ,
2013-09-21 05:30:47 +00:00
[ "Channel Admin" , "3" ] ,
[ "Nobody" , "1000000" ]
2013-06-17 22:16:59 +00:00
] ;
2014-01-20 18:16:30 +00:00
$ ( "<h3/>" ) . text ( "Open playlist permissions" ) . appendTo ( form ) ;
2013-06-17 22:16:59 +00:00
makeOption ( "Add to playlist" , "oplaylistadd" , standard , CHANNEL . perms . oplaylistadd + "" ) ;
makeOption ( "Add/move to next" , "oplaylistnext" , standard , CHANNEL . perms . oplaylistnext + "" ) ;
makeOption ( "Move playlist items" , "oplaylistmove" , standard , CHANNEL . perms . oplaylistmove + "" ) ;
makeOption ( "Delete playlist items" , "oplaylistdelete" , standard , CHANNEL . perms . oplaylistdelete + "" ) ;
makeOption ( "Jump to video" , "oplaylistjump" , standard , CHANNEL . perms . oplaylistjump + "" ) ;
makeOption ( "Queue playlist" , "oplaylistaddlist" , standard , CHANNEL . perms . oplaylistaddlist + "" ) ;
addDivider ( "General playlist permissions" ) ;
2014-02-18 01:06:49 +00:00
makeOption ( "View the playlist" , "seeplaylist" , standard , CHANNEL . perms . seeplaylist + "" ) ;
2013-06-17 22:16:59 +00:00
makeOption ( "Add to playlist" , "playlistadd" , standard , CHANNEL . perms . playlistadd + "" ) ;
makeOption ( "Add/move to next" , "playlistnext" , standard , CHANNEL . perms . playlistnext + "" ) ;
makeOption ( "Move playlist items" , "playlistmove" , standard , CHANNEL . perms . playlistmove + "" ) ;
makeOption ( "Delete playlist items" , "playlistdelete" , standard , CHANNEL . perms . playlistdelete + "" ) ;
makeOption ( "Jump to video" , "playlistjump" , standard , CHANNEL . perms . playlistjump + "" ) ;
makeOption ( "Queue playlist" , "playlistaddlist" , standard , CHANNEL . perms . playlistaddlist + "" ) ;
makeOption ( "Queue livestream" , "playlistaddlive" , standard , CHANNEL . perms . playlistaddlive + "" ) ;
2013-08-08 22:25:56 +00:00
makeOption ( "Embed custom media" , "playlistaddcustom" , standard , CHANNEL . perms . playlistaddcustom + "" ) ;
2014-06-04 04:21:00 +00:00
makeOption ( "Add raw video file" , "playlistaddrawfile" , standard , CHANNEL . perms . playlistaddrawfile + "" ) ;
2013-07-04 23:11:13 +00:00
makeOption ( "Exceed maximum media length" , "exceedmaxlength" , standard , CHANNEL . perms . exceedmaxlength + "" ) ;
2017-04-04 04:18:40 +00:00
makeOption ( "Exceed maximum total media length" , "exceedmaxdurationperuser" , standard , CHANNEL . perms . exceedmaxdurationperuser + "" ) ;
2014-10-06 16:32:25 +00:00
makeOption ( "Exceed maximum number of videos per user" , "exceedmaxitems" , standard , CHANNEL . perms . exceedmaxitems + "" ) ;
2013-06-17 22:16:59 +00:00
makeOption ( "Add nontemporary media" , "addnontemp" , standard , CHANNEL . perms . addnontemp + "" ) ;
makeOption ( "Temp/untemp playlist item" , "settemp" , standard , CHANNEL . perms . settemp + "" ) ;
2014-01-23 22:03:50 +00:00
makeOption ( "Lock/unlock playlist" , "playlistlock" , modleader , CHANNEL . perms . playlistlock + "" ) ;
2013-06-17 22:16:59 +00:00
makeOption ( "Shuffle playlist" , "playlistshuffle" , standard , CHANNEL . perms . playlistshuffle + "" ) ;
makeOption ( "Clear playlist" , "playlistclear" , standard , CHANNEL . perms . playlistclear + "" ) ;
2017-01-24 05:16:39 +00:00
makeOption ( "Delete from channel library" , "deletefromchannellib" , standard , CHANNEL . perms . deletefromchannellib + "" ) ;
2013-06-17 22:16:59 +00:00
addDivider ( "Polls" ) ;
makeOption ( "Open/Close poll" , "pollctl" , modleader , CHANNEL . perms . pollctl + "" ) ;
makeOption ( "Vote" , "pollvote" , standard , CHANNEL . perms . pollvote + "" ) ;
2013-09-12 01:22:00 +00:00
makeOption ( "View hidden poll results" , "viewhiddenpoll" , standard , CHANNEL . perms . viewhiddenpoll + "" ) ;
2013-09-12 03:16:56 +00:00
makeOption ( "Voteskip" , "voteskip" , standard , CHANNEL . perms . voteskip + "" ) ;
2014-09-07 03:59:28 +00:00
makeOption ( "View voteskip results" , "viewvoteskip" , standard , CHANNEL . perms . viewvoteskip + "" ) ;
2013-06-17 22:16:59 +00:00
addDivider ( "Moderation" ) ;
2014-01-23 22:03:50 +00:00
makeOption ( "Assign/Remove leader" , "leaderctl" , modplus , CHANNEL . perms . leaderctl + "" ) ;
2013-06-25 14:18:33 +00:00
makeOption ( "Mute users" , "mute" , modleader , CHANNEL . perms . mute + "" ) ;
2013-06-17 22:16:59 +00:00
makeOption ( "Kick users" , "kick" , modleader , CHANNEL . perms . kick + "" ) ;
makeOption ( "Ban users" , "ban" , modplus , CHANNEL . perms . ban + "" ) ;
makeOption ( "Edit MOTD" , "motdedit" , modplus , CHANNEL . perms . motdedit + "" ) ;
makeOption ( "Edit chat filters" , "filteredit" , modplus , CHANNEL . perms . filteredit + "" ) ;
2014-01-23 22:03:50 +00:00
makeOption ( "Import chat filters" , "filterimport" , modplus , CHANNEL . perms . filterimport + "" ) ;
2014-02-15 06:12:11 +00:00
makeOption ( "Edit chat emotes" , "emoteedit" , modplus , CHANNEL . perms . emoteedit + "" ) ;
makeOption ( "Import chat emotes" , "emoteimport" , modplus , CHANNEL . perms . emoteimport + "" ) ;
2013-06-17 22:16:59 +00:00
addDivider ( "Misc" ) ;
makeOption ( "Drink calls" , "drink" , modleader , CHANNEL . perms . drink + "" ) ;
2013-06-22 23:02:55 +00:00
makeOption ( "Chat" , "chat" , noanon , CHANNEL . perms . chat + "" ) ;
2014-07-11 03:03:47 +00:00
makeOption ( "Clear Chat" , "chatclear" , modleader , CHANNEL . perms . chatclear + "" ) ;
2013-06-17 22:16:59 +00:00
2014-01-20 18:16:30 +00:00
var sgroup = $ ( "<div/>" ) . addClass ( "form-group" ) . appendTo ( form ) ;
var sgroupinner = $ ( "<div/>" ) . addClass ( "col-sm-8 col-sm-offset-4" ) . appendTo ( sgroup ) ;
var submit = $ ( "<button/>" ) . addClass ( "btn btn-primary" ) . appendTo ( sgroupinner ) ;
2013-06-17 22:16:59 +00:00
submit . text ( "Save" ) ;
submit . click ( function ( ) {
var perms = { } ;
2014-01-20 18:16:30 +00:00
form . find ( "select" ) . each ( function ( ) {
2013-06-17 22:16:59 +00:00
perms [ $ ( this ) . data ( "key" ) ] = parseFloat ( $ ( this ) . val ( ) ) ;
} ) ;
socket . emit ( "setPermissions" , perms ) ;
} ) ;
2014-01-20 18:16:30 +00:00
var msggroup = $ ( "<div/>" ) . addClass ( "form-group" ) . insertAfter ( sgroup ) ;
var msginner = $ ( "<div/>" ) . addClass ( "col-sm-8 col-sm-offset-4" ) . appendTo ( msggroup ) ;
var text = $ ( "<span/>" ) . addClass ( "text-info" ) . text ( "Permissions updated" )
. appendTo ( msginner ) ;
setTimeout ( function ( ) {
msggroup . hide ( "fade" , function ( ) {
msggroup . remove ( ) ;
} ) ;
} , 5000 ) ;
2013-06-17 22:16:59 +00:00
}
2013-06-18 14:46:28 +00:00
2013-07-28 14:49:12 +00:00
function waitUntilDefined ( obj , key , fn ) {
if ( typeof obj [ key ] === "undefined" ) {
setTimeout ( function ( ) {
waitUntilDefined ( obj , key , fn ) ;
} , 100 ) ;
return ;
}
fn ( ) ;
}
2013-08-06 03:11:56 +00:00
2022-01-19 13:30:00 +00:00
function chatDialog ( div , zin = 'auto' ) {
2013-11-25 22:20:15 +00:00
var parent = $ ( "<div/>" ) . addClass ( "profile-box" )
2014-01-26 03:29:56 +00:00
. css ( {
padding : "10px" ,
2020-03-29 16:35:09 +00:00
"z-index" : zin ,
2014-01-26 03:29:56 +00:00
position : "absolute"
} )
2014-07-02 03:11:54 +00:00
. appendTo ( $ ( "#chatwrap" ) ) ;
2013-11-25 22:20:15 +00:00
div . appendTo ( parent ) ;
var cw = $ ( "#chatwrap" ) . width ( ) ;
var ch = $ ( "#chatwrap" ) . height ( ) ;
2014-07-02 03:11:54 +00:00
var x = cw / 2 - parent . width ( ) / 2 ;
var y = ch / 2 - parent . height ( ) / 2 ;
2013-11-25 22:20:15 +00:00
parent . css ( "left" , x + "px" ) ;
parent . css ( "top" , y + "px" ) ;
return parent ;
}
2013-10-17 04:22:37 +00:00
function errDialog ( err ) {
var div = $ ( "<div/>" ) . addClass ( "profile-box" )
. css ( "padding" , "10px" )
. text ( err )
. appendTo ( $ ( "body" ) ) ;
$ ( "<br/>" ) . appendTo ( div ) ;
2013-12-20 03:33:24 +00:00
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
2013-10-17 04:22:37 +00:00
. css ( "width" , "100%" )
. text ( "OK" )
. click ( function ( ) { div . remove ( ) ; } )
. appendTo ( div ) ;
var cw = $ ( "#chatwrap" ) . width ( ) ;
var ch = $ ( "#chatwrap" ) . height ( ) ;
var cp = $ ( "#chatwrap" ) . offset ( ) ;
var x = cp . left + cw / 2 - div . width ( ) / 2 ;
var y = cp . top + ch / 2 - div . height ( ) / 2 ;
2014-01-30 04:50:14 +00:00
div . css ( "left" , x + "px" )
. css ( "top" , y + "px" )
. css ( "position" , "absolute" ) ;
return div ;
2013-10-17 04:22:37 +00:00
}
2013-11-11 04:26:30 +00:00
2016-12-11 07:23:57 +00:00
/ * *
* 2016 - 12 - 08
* I * promise * that one day I will actually split this file into submodules
* - cal
* /
2017-07-25 05:35:15 +00:00
/ * *
* modalAlert accepts options { title , textContent , htmlContent }
* All are optional
* /
2016-12-11 07:23:57 +00:00
function modalAlert ( options ) {
if ( typeof options !== "object" || options === null ) {
throw new Error ( "modalAlert() called without required parameter" ) ;
}
var modal = makeModal ( ) ;
modal . addClass ( "cytube-modal-alert" ) ;
modal . removeClass ( "fade" ) ;
modal . find ( ".modal-dialog" ) . addClass ( "modal-dialog-nonfluid" ) ;
if ( options . title ) {
$ ( "<h3/>" ) . text ( options . title ) . appendTo ( modal . find ( ".modal-header" ) ) ;
}
var contentDiv = $ ( "<div/>" ) . addClass ( "modal-body" ) ;
if ( options . htmlContent ) {
contentDiv . html ( options . htmlContent ) ;
} else if ( options . textContent ) {
contentDiv . text ( options . textContent ) ;
}
contentDiv . appendTo ( modal . find ( ".modal-content" ) ) ;
var footer = $ ( "<div/>" ) . addClass ( "modal-footer" ) ;
var okButton = $ ( "<button/>" ) . addClass ( "btn btn-primary" )
. attr ( { "data-dismiss" : "modal" } )
2018-08-18 20:14:37 +00:00
. text ( options . dismissText || "OK" )
2016-12-11 07:23:57 +00:00
. appendTo ( footer ) ;
footer . appendTo ( modal . find ( ".modal-content" ) ) ;
modal . appendTo ( document . body ) ;
modal . modal ( ) ;
}
2013-11-11 04:26:30 +00:00
function queueMessage ( data , type ) {
if ( ! data )
data = { link : null } ;
if ( ! data . msg || data . msg === true ) {
data . msg = "Queue failed. Check your link to make sure it is valid." ;
}
2013-12-20 03:33:24 +00:00
var ltype = "label-danger" ;
2013-11-11 04:26:30 +00:00
var title = "Error" ;
2013-12-20 03:33:24 +00:00
if ( type === "alert-warning" ) {
2013-11-11 04:26:30 +00:00
ltype = "label-warning" ;
title = "Warning" ;
2013-12-20 03:33:24 +00:00
}
2013-11-11 04:26:30 +00:00
2014-02-28 19:49:17 +00:00
var alerts = $ ( ".qfalert.qf-" + type + " .alert" ) ;
2013-11-11 04:26:30 +00:00
for ( var i = 0 ; i < alerts . length ; i ++ ) {
var al = $ ( alerts [ i ] ) ;
2015-12-21 06:35:24 +00:00
if ( al . data ( "reason" ) === data . msg ) {
2013-11-11 04:26:30 +00:00
var tag = al . find ( "." + ltype ) ;
if ( tag . length > 0 ) {
var morelinks = al . find ( ".qflinks" ) ;
$ ( "<a/>" ) . attr ( "href" , data . link )
. attr ( "target" , "_blank" )
. text ( data . link )
. appendTo ( morelinks ) ;
$ ( "<br/>" ) . appendTo ( morelinks ) ;
var count = parseInt ( tag . text ( ) . match ( /\d+/ ) [ 0 ] ) + 1 ;
tag . text ( tag . text ( ) . replace ( /\d+/ , "" + count ) ) ;
} else {
var tag = $ ( "<span/>" )
. addClass ( "label pull-right pointer " + ltype )
. text ( "+ 1 more" )
. appendTo ( al ) ;
var morelinks = $ ( "<div/>" )
. addClass ( "qflinks" )
. appendTo ( al )
. hide ( ) ;
$ ( "<a/>" ) . attr ( "href" , data . link )
. attr ( "target" , "_blank" )
. text ( data . link )
. appendTo ( morelinks ) ;
$ ( "<br/>" ) . appendTo ( morelinks ) ;
tag . click ( function ( ) {
morelinks . toggle ( ) ;
} ) ;
}
return ;
}
}
var text = data . msg ;
2016-03-22 06:28:21 +00:00
text = text . replace ( /(https?:[^ ]+)/g , "<a href='$1' target='_blank'>$1</a>" ) ;
2013-11-11 04:26:30 +00:00
if ( typeof data . link === "string" ) {
text += "<br><a href='" + data . link + "' target='_blank'>" +
data . link + "</a>" ;
}
2015-12-21 06:35:24 +00:00
var newAlert = makeAlert ( title , text , type )
2015-03-28 00:08:58 +00:00
. addClass ( "linewrap qfalert qf-" + type )
2016-05-20 03:51:39 +00:00
. prependTo ( $ ( "#queuefail" ) ) ;
2015-12-21 06:35:24 +00:00
newAlert . find ( ".alert" ) . data ( "reason" , data . msg ) ;
2013-11-11 04:26:30 +00:00
}
2013-11-14 04:36:43 +00:00
2014-02-08 18:45:07 +00:00
function setupChanlogFilter ( data ) {
data = data . split ( "\n" ) . filter ( function ( ln ) {
return ln . indexOf ( "[" ) === 0 && ln . indexOf ( "]" ) > 0 ;
} ) ;
2013-11-16 05:44:53 +00:00
2014-02-08 18:45:07 +00:00
var log = $ ( "#cs-chanlog-text" ) ;
var select = $ ( "#cs-chanlog-filter" ) ;
select . html ( "" ) ;
log . data ( "lines" , data ) ;
2013-11-16 05:44:53 +00:00
2014-02-08 18:45:07 +00:00
var keys = { } ;
data . forEach ( function ( ln ) {
2016-05-21 23:18:52 +00:00
var m = ln . match ( /^\[.*?\] \[(\w+?)\].*$/ ) ;
if ( m ) {
keys [ m [ 1 ] ] = true ;
}
2014-02-08 18:45:07 +00:00
} ) ;
2013-11-16 05:44:53 +00:00
2014-02-08 18:45:07 +00:00
Object . keys ( keys ) . forEach ( function ( key ) {
$ ( "<option/>" ) . attr ( "value" , key ) . text ( key ) . appendTo ( select ) ;
} ) ;
2014-02-13 05:33:42 +00:00
$ ( "<option/>" ) . attr ( "value" , "chat" ) . text ( "chat" ) . prependTo ( select ) ;
2014-02-08 18:45:07 +00:00
}
2013-11-16 05:44:53 +00:00
2014-02-08 18:45:07 +00:00
function filterChannelLog ( ) {
var log = $ ( "#cs-chanlog-text" ) ;
var filter = $ ( "#cs-chanlog-filter" ) . val ( ) ;
var getKey = function ( ln ) {
var left = ln . indexOf ( "[" , 1 ) ;
var right = ln . indexOf ( "]" , left ) ;
2014-02-13 05:33:42 +00:00
if ( left === - 1 ) {
return false ;
}
2014-02-08 18:45:07 +00:00
return ln . substring ( left + 1 , right ) ;
} ;
2013-11-16 05:44:53 +00:00
2014-02-08 18:45:07 +00:00
var getTimestamp = function ( ln ) {
var right = ln . indexOf ( "]" ) ;
return ln . substring ( 1 , right ) ;
} ;
2013-11-16 05:44:53 +00:00
2014-02-08 18:45:07 +00:00
var getMessage = function ( ln ) {
var right = ln . indexOf ( "]" ) ;
return ln . substring ( right + 2 ) ;
} ;
2013-11-16 05:44:53 +00:00
2014-02-08 18:45:07 +00:00
var show = [ ] ;
( log . data ( "lines" ) || [ ] ) . forEach ( function ( ln ) {
2014-02-13 05:33:42 +00:00
var key = getKey ( ln ) ;
if ( ! filter || ! key && filter . indexOf ( "chat" ) !== - 1 ) {
show . push ( ln ) ;
} else if ( filter . indexOf ( key ) >= 0 ) {
2014-02-08 18:45:07 +00:00
show . push ( ln ) ;
2013-11-16 05:44:53 +00:00
}
} ) ;
2014-02-08 18:45:07 +00:00
log . text ( show . join ( "\n" ) ) ;
log . scrollTop ( log . prop ( "scrollHeight" ) ) ;
2013-11-16 02:23:57 +00:00
}
2013-12-15 03:59:47 +00:00
function makeModal ( ) {
var wrap = $ ( "<div/>" ) . addClass ( "modal fade" ) ;
var dialog = $ ( "<div/>" ) . addClass ( "modal-dialog" ) . appendTo ( wrap ) ;
var content = $ ( "<div/>" ) . addClass ( "modal-content" ) . appendTo ( dialog ) ;
var head = $ ( "<div/>" ) . addClass ( "modal-header" ) . appendTo ( content ) ;
$ ( "<button/>" ) . addClass ( "close" )
. attr ( "data-dismiss" , "modal" )
. attr ( "data-hidden" , "true" )
. html ( "×" )
. appendTo ( head ) ;
2014-01-19 02:18:00 +00:00
wrap . on ( "hidden.bs.modal" , function ( ) {
2013-12-15 03:59:47 +00:00
wrap . remove ( ) ;
} ) ;
return wrap ;
}
2014-01-09 23:16:09 +00:00
function formatCSModList ( ) {
var tbl = $ ( "#cs-chanranks table" ) ;
tbl . find ( "tbody" ) . remove ( ) ;
var entries = tbl . data ( "entries" ) || [ ] ;
entries . sort ( function ( a , b ) {
if ( a . rank === b . rank ) {
var x = a . name . toLowerCase ( ) ;
var y = b . name . toLowerCase ( ) ;
return y == x ? 0 : ( x < y ? - 1 : 1 ) ;
}
return b . rank - a . rank ;
} ) ;
entries . forEach ( function ( entry ) {
var tr = $ ( "<tr/>" ) . addClass ( "cs-chanrank-tr-" + entry . name ) ;
var name = $ ( "<td/>" ) . text ( entry . name ) . appendTo ( tr ) ;
name . addClass ( getNameColor ( entry . rank ) ) ;
var rankwrap = $ ( "<td/>" ) ;
var rank = $ ( "<span/>" ) . text ( entry . rank ) . appendTo ( rankwrap ) ;
var dd = $ ( "<div/>" ) . addClass ( "btn-group" ) ;
var toggle = $ ( "<button/>" )
. addClass ( "btn btn-xs btn-default dropdown-toggle" )
. attr ( "data-toggle" , "dropdown" )
. html ( "Edit <span class=caret></span>" )
. appendTo ( dd ) ;
2014-03-01 23:37:59 +00:00
if ( CLIENT . rank <= entry . rank && ! ( CLIENT . rank === 4 && entry . rank === 4 ) ) {
2014-01-09 23:16:09 +00:00
toggle . addClass ( "disabled" ) ;
}
var menu = $ ( "<ul/>" ) . addClass ( "dropdown-menu" )
. attr ( "role" , "menu" )
. appendTo ( dd ) ;
var ranks = [
{ name : "Remove Moderator" , rank : 1 } ,
{ name : "Moderator" , rank : 2 } ,
2014-02-26 20:37:51 +00:00
{ name : "Admin" , rank : 3 } ,
{ name : "Owner" , rank : 4 } ,
{ name : "Founder" , rank : 5 }
2014-01-09 23:16:09 +00:00
] ;
ranks . forEach ( function ( r ) {
var li = $ ( "<li/>" ) . appendTo ( menu ) ;
var a = $ ( "<a/>" )
. addClass ( getNameColor ( r . rank ) )
. attr ( "href" , "javascript:void(0)" )
. text ( r . name )
. appendTo ( li ) ;
if ( r . rank !== entry . rank ) {
a . click ( function ( ) {
socket . emit ( "setChannelRank" , {
2014-05-21 03:11:40 +00:00
name : entry . name ,
2014-01-09 23:16:09 +00:00
rank : r . rank
} ) ;
} ) ;
} else {
2014-02-26 20:37:51 +00:00
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-ok" )
2014-01-09 23:16:09 +00:00
. appendTo ( a ) ;
li . addClass ( "disabled" ) ;
}
if ( r . rank > CLIENT . rank || ( CLIENT . rank < 4 && r . rank === CLIENT . rank ) ) {
li . addClass ( "disabled" ) ;
}
} ) ;
dd . css ( "margin-right" , "10px" ) . prependTo ( rankwrap ) ;
rankwrap . appendTo ( tr ) ;
tr . appendTo ( tbl ) ;
} ) ;
}
2014-01-09 23:43:07 +00:00
function formatCSBanlist ( ) {
var tbl = $ ( "#cs-banlist table" ) ;
tbl . find ( "tbody" ) . remove ( ) ;
var entries = tbl . data ( "entries" ) || [ ] ;
var sparse = { } ;
for ( var i = 0 ; i < entries . length ; i ++ ) {
if ( ! ( entries [ i ] . name in sparse ) ) {
sparse [ entries [ i ] . name ] = [ ] ;
}
sparse [ entries [ i ] . name ] . push ( entries [ i ] ) ;
}
var flat = [ ] ;
for ( var name in sparse ) {
flat . push ( {
name : name ,
bans : sparse [ name ]
} ) ;
}
flat . sort ( function ( a , b ) {
var x = a . name . toLowerCase ( ) ,
y = b . name . toLowerCase ( ) ;
return x === y ? 0 : ( x > y ? 1 : - 1 ) ;
} ) ;
var addBanRow = function ( entry , after ) {
var tr = $ ( "<tr/>" ) ;
if ( after ) {
tr . insertAfter ( after ) ;
} else {
tr . appendTo ( tbl ) ;
}
var unban = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-danger" )
. appendTo ( $ ( "<td/>" ) . appendTo ( tr ) ) ;
2014-01-12 05:55:52 +00:00
unban . click ( function ( ) {
socket . emit ( "unban" , {
id : entry . id ,
name : entry . name
} ) ;
} ) ;
2014-01-09 23:43:07 +00:00
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-remove-circle" ) . appendTo ( unban ) ;
$ ( "<td/>" ) . text ( entry . ip ) . appendTo ( tr ) ;
$ ( "<td/>" ) . text ( entry . name ) . appendTo ( tr ) ;
$ ( "<td/>" ) . text ( entry . bannedby ) . appendTo ( tr ) ;
2014-08-18 16:49:03 +00:00
tr . attr ( "title" , "Ban Reason: " + entry . reason ) ;
2014-01-09 23:43:07 +00:00
return tr ;
} ;
flat . forEach ( function ( person ) {
var bans = person . bans ;
var name = person . name ;
var first = addBanRow ( bans . shift ( ) ) ;
if ( bans . length > 0 ) {
var showmore = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-default pull-right" ) ;
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-list" ) . appendTo ( showmore ) ;
showmore . appendTo ( first . find ( "td" ) [ 1 ] ) ;
showmore . click ( function ( ) {
if ( showmore . data ( "elems" ) ) {
showmore . data ( "elems" ) . forEach ( function ( e ) {
e . remove ( ) ;
} ) ;
showmore . data ( "elems" , null ) ;
} else {
var elems = [ ] ;
bans . forEach ( function ( b ) {
elems . push ( addBanRow ( b , first ) ) ;
} ) ;
showmore . data ( "elems" , elems ) ;
}
} ) ;
}
} ) ;
}
2014-01-16 17:53:34 +00:00
2014-04-13 07:14:34 +00:00
function checkEntitiesInStr ( str ) {
var entities = {
"&" : "&" ,
"<" : "<" ,
">" : ">" ,
'"' : """ ,
"'" : "'" ,
2014-05-21 05:41:21 +00:00
"\\(" : "(" ,
"\\)" : ")"
2014-04-13 07:14:34 +00:00
} ;
2014-05-21 05:41:21 +00:00
var m = str . match ( /([&<>"'])|(\\\()|(\\\))/ ) ;
2014-04-13 07:14:34 +00:00
if ( m && m [ 1 ] in entities ) {
2014-05-21 05:41:21 +00:00
return { src : m [ 1 ] . replace ( /^\\/ , "" ) , replace : entities [ m [ 1 ] ] } ;
2014-04-13 07:14:34 +00:00
} else {
return false ;
}
}
2014-01-16 17:53:34 +00:00
function formatCSChatFilterList ( ) {
var tbl = $ ( "#cs-chatfilters table" ) ;
tbl . find ( "tbody" ) . remove ( ) ;
tbl . find ( ".ui-sortable" ) . remove ( ) ;
var entries = tbl . data ( "entries" ) || [ ] ;
entries . forEach ( function ( f ) {
var tr = $ ( "<tr/>" ) . appendTo ( tbl ) ;
2014-01-16 22:20:08 +00:00
var controlgroup = $ ( "<div/>" ) . addClass ( "btn-group" )
. appendTo ( $ ( "<td/>" ) . appendTo ( tr ) ) ;
2014-01-16 17:53:34 +00:00
var control = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
. attr ( "title" , "Edit this filter" )
2014-01-16 22:20:08 +00:00
. appendTo ( controlgroup ) ;
2014-01-16 17:53:34 +00:00
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-list" ) . appendTo ( control ) ;
2014-01-16 22:20:08 +00:00
var del = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-danger" )
. appendTo ( controlgroup ) ;
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-trash" ) . appendTo ( del ) ;
del . click ( function ( ) {
socket . emit ( "removeFilter" , f ) ;
} ) ;
2014-01-16 17:53:34 +00:00
var name = $ ( "<code/>" ) . text ( f . name ) . appendTo ( $ ( "<td/>" ) . appendTo ( tr ) ) ;
var activetd = $ ( "<td/>" ) . appendTo ( tr ) ;
var active = $ ( "<input/>" ) . attr ( "type" , "checkbox" )
. prop ( "checked" , f . active )
. appendTo ( activetd )
. change ( function ( ) {
f . active = $ ( this ) . prop ( "checked" ) ;
socket . emit ( "updateFilter" , f ) ;
} ) ;
2014-01-16 22:20:08 +00:00
var reset = function ( ) {
control . data ( "editor" ) && control . data ( "editor" ) . remove ( ) ;
control . data ( "editor" , null ) ;
control . parent ( ) . find ( ".btn-success" ) . remove ( ) ;
var tbody = $ ( tbl . children ( ) [ 1 ] ) ;
if ( tbody . find ( ".filter-edit-row" ) . length === 0 ) {
tbody . sortable ( "enable" ) ;
}
} ;
2014-01-16 17:53:34 +00:00
control . click ( function ( ) {
2014-01-16 22:20:08 +00:00
if ( control . data ( "editor" ) ) {
return reset ( ) ;
}
$ ( tbl . children ( ) [ 1 ] ) . sortable ( "disable" ) ;
var tr2 = $ ( "<tr/>" ) . insertAfter ( tr ) . addClass ( "filter-edit-row" ) ;
2014-01-16 17:53:34 +00:00
var wrap = $ ( "<td/>" ) . attr ( "colspan" , "3" ) . appendTo ( tr2 ) ;
var form = $ ( "<form/>" ) . addClass ( "form-inline" ) . attr ( "role" , "form" )
. attr ( "action" , "javascript:void(0)" )
. appendTo ( wrap ) ;
var addTextbox = function ( placeholder ) {
var div = $ ( "<div/>" ) . addClass ( "form-group" ) . appendTo ( form )
2014-11-13 01:56:29 +00:00
. css ( "margin-right" , "10px" ) ;
2014-01-16 17:53:34 +00:00
var input = $ ( "<input/>" ) . addClass ( "form-control" )
. attr ( "type" , "text" )
. attr ( "placeholder" , placeholder )
. attr ( "title" , placeholder )
. appendTo ( div ) ;
return input ;
} ;
var regex = addTextbox ( "Filter regex" ) . val ( f . source ) ;
var flags = addTextbox ( "Regex flags" ) . val ( f . flags ) ;
var replace = addTextbox ( "Replacement text" ) . val ( f . replace ) ;
var checkwrap = $ ( "<div/>" ) . addClass ( "checkbox" ) . appendTo ( form ) ;
var checklbl = $ ( "<label/>" ) . text ( "Filter Links" ) . appendTo ( checkwrap ) ;
2014-01-16 22:20:08 +00:00
var filterlinks = $ ( "<input/>" ) . attr ( "type" , "checkbox" )
. prependTo ( checklbl )
. prop ( "checked" , f . filterlinks ) ;
2014-01-16 17:53:34 +00:00
var save = $ ( "<button/>" ) . addClass ( "btn btn-xs btn-success" )
2014-01-16 22:20:08 +00:00
. attr ( "title" , "Save changes" )
2014-01-16 17:53:34 +00:00
. insertAfter ( control ) ;
2014-01-16 22:20:08 +00:00
$ ( "<span/>" ) . addClass ( "glyphicon glyphicon-floppy-save" ) . appendTo ( save ) ;
save . click ( function ( ) {
f . source = regex . val ( ) ;
2014-04-13 07:14:34 +00:00
var entcheck = checkEntitiesInStr ( f . source ) ;
if ( entcheck ) {
alert ( "Warning: " + entcheck . src + " will be replaced by " +
entcheck . replace + " in the message preprocessor. This " +
"regular expression may not match what you intended it to " +
"match." ) ;
}
2014-01-16 22:20:08 +00:00
f . flags = flags . val ( ) ;
f . replace = replace . val ( ) ;
f . filterlinks = filterlinks . prop ( "checked" ) ;
socket . emit ( "updateFilter" , f ) ;
2014-12-28 16:12:37 +00:00
socket . once ( "updateFilterSuccess" , function ( ) {
reset ( ) ;
} ) ;
2014-01-16 22:20:08 +00:00
} ) ;
control . data ( "editor" , tr2 ) ;
2014-01-16 17:53:34 +00:00
} ) ;
} ) ;
$ ( tbl . children ( ) [ 1 ] ) . sortable ( {
start : function ( ev , ui ) {
FILTER _FROM = ui . item . prevAll ( ) . length ;
} ,
update : function ( ev , ui ) {
FILTER _TO = ui . item . prevAll ( ) . length ;
if ( FILTER _TO != FILTER _FROM ) {
socket . emit ( "moveFilter" , {
from : FILTER _FROM ,
to : FILTER _TO
} ) ;
}
}
} ) ;
}
2014-02-02 18:41:41 +00:00
function formatTime ( sec ) {
var h = Math . floor ( sec / 3600 ) + "" ;
var m = Math . floor ( ( sec % 3600 ) / 60 ) + "" ;
var s = sec % 60 + "" ;
if ( h . length < 2 ) {
h = "0" + h ;
}
if ( m . length < 2 ) {
m = "0" + m ;
}
if ( s . length < 2 ) {
s = "0" + s ;
}
if ( h === "00" ) {
return [ m , s ] . join ( ":" ) ;
} else {
return [ h , m , s ] . join ( ":" ) ;
}
}
function formatUserPlaylistList ( ) {
var list = $ ( "#userpl_list" ) . data ( "entries" ) || [ ] ;
list . sort ( function ( a , b ) {
var x = a . name . toLowerCase ( ) ;
var y = b . name . toLowerCase ( ) ;
return x == y ? 0 : ( x < y ? - 1 : 1 ) ;
} ) ;
$ ( "#userpl_list" ) . html ( "" ) ;
list . forEach ( function ( pl ) {
var li = $ ( "<li/>" ) . addClass ( "queue_entry" ) . appendTo ( $ ( "#userpl_list" ) ) ;
var title = $ ( "<span/>" ) . addClass ( "qe_title" ) . appendTo ( li )
. text ( pl . name ) ;
var time = $ ( "<span/>" ) . addClass ( "pull-right" ) . appendTo ( li )
. text ( pl . count + " items, playtime " + formatTime ( pl . duration ) ) ;
var clear = $ ( "<div/>" ) . addClass ( "qe_clear" ) . appendTo ( li ) ;
var btns = $ ( "<div/>" ) . addClass ( "btn-group pull-left" ) . prependTo ( li ) ;
if ( hasPermission ( "playlistadd" ) ) {
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
. text ( "End" )
. appendTo ( btns )
. click ( function ( ) {
socket . emit ( "queuePlaylist" , {
name : pl . name ,
2014-02-28 14:43:04 +00:00
pos : "end" ,
temp : $ ( ".add-temp" ) . prop ( "checked" )
2014-02-02 18:41:41 +00:00
} ) ;
} ) ;
}
if ( hasPermission ( "playlistadd" ) && hasPermission ( "playlistnext" ) ) {
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-default" )
. text ( "Next" )
. prependTo ( btns )
. click ( function ( ) {
socket . emit ( "queuePlaylist" , {
name : pl . name ,
2014-02-28 14:43:04 +00:00
pos : "next" ,
temp : $ ( ".add-temp" ) . prop ( "checked" )
2014-02-02 18:41:41 +00:00
} ) ;
} ) ;
}
$ ( "<button/>" ) . addClass ( "btn btn-xs btn-danger" )
. html ( "<span class='glyphicon glyphicon-trash'></span>" )
. attr ( "title" , "Delete playlist" )
. appendTo ( btns )
. click ( function ( ) {
2015-04-20 20:09:21 +00:00
var really = confirm ( "Are you sure you want to delete" +
" this playlist? This cannot be undone." ) ;
if ( ! really ) {
return ;
}
2014-02-02 18:41:41 +00:00
socket . emit ( "deletePlaylist" , {
name : pl . name
} ) ;
} ) ;
} ) ;
}
2014-02-10 05:53:46 +00:00
function loadEmotes ( data ) {
2017-08-05 19:22:58 +00:00
function sanitizeText ( str ) {
str = str . replace ( /&/g , "&" )
. replace ( /</g , "<" )
. replace ( />/g , ">" )
. replace ( /"/g , """ ) ;
return str ;
}
2014-02-10 05:53:46 +00:00
CHANNEL . emotes = [ ] ;
2017-03-02 05:16:55 +00:00
CHANNEL . emoteMap = { } ;
CHANNEL . badEmotes = [ ] ;
2014-02-10 05:53:46 +00:00
data . forEach ( function ( e ) {
2015-12-06 02:52:39 +00:00
if ( e . image && e . name ) {
e . regex = new RegExp ( e . source , "gi" ) ;
CHANNEL . emotes . push ( e ) ;
2017-03-02 05:16:55 +00:00
if ( /\s/g . test ( e . name ) ) {
// Emotes with spaces can't be hashmapped
CHANNEL . badEmotes . push ( e ) ;
} else {
2017-08-05 19:22:58 +00:00
CHANNEL . emoteMap [ sanitizeText ( e . name ) ] = e ;
2017-03-02 05:16:55 +00:00
}
2015-12-06 02:52:39 +00:00
} else {
console . error ( "Rejecting invalid emote: " + JSON . stringify ( e ) ) ;
}
2014-02-10 05:53:46 +00:00
} ) ;
}
function execEmotes ( msg ) {
if ( USEROPTS . no _emotes ) {
return msg ;
}
2017-03-02 05:16:55 +00:00
if ( CyTube . featureFlag && CyTube . featureFlag . efficientEmotes ) {
2017-03-04 07:59:07 +00:00
return execEmotesEfficient ( msg ) ;
2017-03-02 05:16:55 +00:00
}
2014-02-10 05:53:46 +00:00
CHANNEL . emotes . forEach ( function ( e ) {
2014-02-16 07:33:38 +00:00
msg = msg . replace ( e . regex , '$1<img class="channel-emote" src="' +
2014-03-27 16:03:27 +00:00
e . image + '" title="' + e . name + '">' ) ;
2014-02-10 05:53:46 +00:00
} ) ;
return msg ;
}
2014-02-15 07:40:14 +00:00
2017-03-02 05:16:55 +00:00
function execEmotesEfficient ( msg ) {
CHANNEL . badEmotes . forEach ( function ( e ) {
msg = msg . replace ( e . regex , '$1<img class="channel-emote" src="' +
e . image + '" title="' + e . name + '">' ) ;
} ) ;
msg = msg . replace ( /[^\s]+/g , function ( m ) {
if ( CHANNEL . emoteMap . hasOwnProperty ( m ) ) {
var e = CHANNEL . emoteMap [ m ] ;
return '<img class="channel-emote" src="' + e . image + '" title="' + e . name + '">' ;
} else {
return m ;
}
} ) ;
return msg ;
}
2014-02-15 07:40:14 +00:00
function initPm ( user ) {
if ( $ ( "#pm-" + user ) . length > 0 ) {
return $ ( "#pm-" + user ) ;
}
var pm = $ ( "<div/>" ) . addClass ( "panel panel-default pm-panel" )
. appendTo ( $ ( "#pmbar" ) )
. data ( "last" , { name : "" } )
. attr ( "id" , "pm-" + user ) ;
var title = $ ( "<div/>" ) . addClass ( "panel-heading" ) . text ( user ) . appendTo ( pm ) ;
var close = $ ( "<button/>" ) . addClass ( "close pull-right" )
. html ( "×" )
. appendTo ( title ) . click ( function ( ) {
pm . remove ( ) ;
$ ( "#pm-placeholder-" + user ) . remove ( ) ;
} ) ;
var body = $ ( "<div/>" ) . addClass ( "panel-body" ) . appendTo ( pm ) . hide ( ) ;
var placeholder ;
title . click ( function ( ) {
body . toggle ( ) ;
pm . removeClass ( "panel-primary" ) . addClass ( "panel-default" ) ;
if ( ! body . is ( ":hidden" ) ) {
placeholder = $ ( "<div/>" ) . addClass ( "pm-panel-placeholder" )
. attr ( "id" , "pm-placeholder-" + user )
. insertAfter ( pm ) ;
var left = pm . position ( ) . left ;
pm . css ( "position" , "absolute" )
. css ( "bottom" , "0px" )
. css ( "left" , left ) ;
} else {
pm . css ( "position" , "" ) ;
$ ( "#pm-placeholder-" + user ) . remove ( ) ;
}
} ) ;
var buffer = $ ( "<div/>" ) . addClass ( "pm-buffer linewrap" ) . appendTo ( body ) ;
$ ( "<hr/>" ) . appendTo ( body ) ;
var input = $ ( "<input/>" ) . addClass ( "form-control pm-input" ) . attr ( "type" , "text" )
2018-11-16 06:48:30 +00:00
. attr ( "maxlength" , 320 )
2014-02-15 07:40:14 +00:00
. appendTo ( body ) ;
2015-02-13 19:40:58 +00:00
input . keydown ( function ( ev ) {
2014-02-15 07:40:14 +00:00
if ( ev . keyCode === 13 ) {
2015-04-24 02:49:01 +00:00
if ( CHATTHROTTLE ) {
return ;
}
2014-02-15 07:40:14 +00:00
var meta = { } ;
var msg = input . val ( ) ;
if ( msg . trim ( ) === "" ) {
return ;
}
if ( USEROPTS . modhat && CLIENT . rank >= Rank . Moderator ) {
meta . modflair = CLIENT . rank ;
}
if ( CLIENT . rank >= 2 && msg . indexOf ( "/m " ) === 0 ) {
meta . modflair = CLIENT . rank ;
msg = msg . substring ( 3 ) ;
}
socket . emit ( "pm" , {
to : user ,
msg : msg ,
meta : meta
} ) ;
input . val ( "" ) ;
2020-03-14 00:43:25 +00:00
} else if ( ev . keyCode == 9 ) { // Tab completion
try {
chatTabComplete ( ev . target ) ;
} catch ( error ) {
console . error ( error ) ;
}
ev . preventDefault ( ) ;
return false ;
2014-02-15 07:40:14 +00:00
}
} ) ;
return pm ;
}
2014-03-08 02:37:20 +00:00
2018-08-18 20:14:37 +00:00
function checkScriptAccess ( viewSource , type , cb ) {
2014-07-02 03:11:54 +00:00
var pref = JSPREF [ CHANNEL . name . toLowerCase ( ) + "_" + type ] ;
if ( pref === "ALLOW" ) {
return cb ( "ALLOW" ) ;
} else if ( pref !== "DENY" ) {
var div = $ ( "#chanjs-allow-prompt" ) ;
if ( div . length > 0 ) {
setTimeout ( function ( ) {
2018-08-18 20:14:37 +00:00
checkScriptAccess ( viewSource , type , cb ) ;
2014-07-02 03:11:54 +00:00
} , 500 ) ;
return ;
}
div = $ ( "<div/>" ) . attr ( "id" , "chanjs-allow-prompt" ) ;
var close = $ ( "<button/>" ) . addClass ( "close pull-right" )
. html ( "×" )
. appendTo ( div ) ;
var form = $ ( "<form/>" )
2014-07-02 03:29:12 +00:00
. attr ( "action" , "javascript:void(0)" )
2014-07-02 03:11:54 +00:00
. attr ( "id" , "chanjs-allow-prompt" )
. attr ( "style" , "text-align: center" )
. appendTo ( div ) ;
2018-08-18 20:14:37 +00:00
if ( type === "embedded" ) {
form . append ( "<span>This channel has special features that require your permission to run.</span><br>" ) ;
} else {
form . append ( "<span>This channel has special features that require your permission to run. This script is hosted on a third-party website and is not endorsed by the owners of the website hosting this channel.</span><br>" ) ;
}
$ ( viewSource ) . appendTo ( form ) ;
2014-07-02 03:11:54 +00:00
form . append ( "<div id='chanjs-allow-prompt-buttons'>" +
"<button id='chanjs-allow' class='btn btn-xs btn-danger'>Allow</button>" +
"<button id='chanjs-deny' class='btn btn-xs btn-danger'>Deny</button>" +
"</div>" ) ;
form . append ( "<div class='checkbox'><label><input type='checkbox' " +
"id='chanjs-save-pref'/>Remember my choice for this channel" +
"</label></div>" ) ;
var dialog = chatDialog ( div ) ;
close . click ( function ( ) {
dialog . remove ( ) ;
/* Implicit denial of script access */
cb ( "DENY" ) ;
} ) ;
$ ( "#chanjs-allow" ) . click ( function ( ) {
var save = $ ( "#chanjs-save-pref" ) . is ( ":checked" ) ;
dialog . remove ( ) ;
if ( save ) {
JSPREF [ CHANNEL . name . toLowerCase ( ) + "_" + type ] = "ALLOW" ;
setOpt ( "channel_js_pref" , JSPREF ) ;
}
cb ( "ALLOW" ) ;
} ) ;
$ ( "#chanjs-deny" ) . click ( function ( ) {
var save = $ ( "#chanjs-save-pref" ) . is ( ":checked" ) ;
dialog . remove ( ) ;
if ( save ) {
JSPREF [ CHANNEL . name . toLowerCase ( ) + "_" + type ] = "DENY" ;
setOpt ( "channel_js_pref" , JSPREF ) ;
}
cb ( "DENY" ) ;
} ) ;
}
}
function formatScriptAccessPrefs ( ) {
var tbl = $ ( "#us-scriptcontrol table" ) ;
tbl . find ( "tbody" ) . remove ( ) ;
var channels = Object . keys ( JSPREF ) . sort ( ) ;
channels . forEach ( function ( channel ) {
2017-01-10 05:02:42 +00:00
var idx = String ( channel ) . lastIndexOf ( "_" ) ;
if ( idx < 0 ) {
// Invalid
console . error ( "Channel JS pref: invalid key '" + channel + "', deleting it" ) ;
delete JSPREF [ channel ] ;
setOpt ( "channel_js_pref" , JSPREF ) ;
return ;
}
var channelName = channel . substring ( 0 , idx ) ;
var prefType = channel . substring ( idx + 1 ) ;
console . log ( channelName , prefType ) ;
if ( prefType !== "external" && prefType !== "embedded" ) {
// Invalid
console . error ( "Channel JS pref: invalid key '" + channel + "', deleting it" ) ;
delete JSPREF [ channel ] ;
setOpt ( "channel_js_pref" , JSPREF ) ;
2014-07-02 03:11:54 +00:00
return ;
}
var pref = JSPREF [ channel ] ;
var tr = $ ( "<tr/>" ) . appendTo ( tbl ) ;
2017-01-10 05:02:42 +00:00
$ ( "<td/>" ) . text ( channelName ) . appendTo ( tr ) ;
$ ( "<td/>" ) . text ( prefType ) . appendTo ( tr ) ;
2014-07-02 03:11:54 +00:00
var pref _td = $ ( "<td/>" ) . appendTo ( tr ) ;
var allow _label = $ ( "<label/>" ) . addClass ( "radio-inline" )
. text ( "Allow" ) . appendTo ( pref _td ) ;
var allow = $ ( "<input/>" ) . attr ( "type" , "radio" )
. prop ( "checked" , pref === "ALLOW" ) .
prependTo ( allow _label ) ;
allow . change ( function ( ) {
if ( allow . is ( ":checked" ) ) {
JSPREF [ channel ] = "ALLOW" ;
setOpt ( "channel_js_pref" , JSPREF ) ;
deny . prop ( "checked" , false ) ;
}
} ) ;
var deny _label = $ ( "<label/>" ) . addClass ( "radio-inline" )
. text ( "Deny" ) . appendTo ( pref _td ) ;
var deny = $ ( "<input/>" ) . attr ( "type" , "radio" )
. prop ( "checked" , pref === "DENY" ) .
prependTo ( deny _label ) ;
deny . change ( function ( ) {
if ( deny . is ( ":checked" ) ) {
JSPREF [ channel ] = "DENY" ;
setOpt ( "channel_js_pref" , JSPREF ) ;
allow . prop ( "checked" , false ) ;
}
} ) ;
var clearpref = $ ( "<button/>" ) . addClass ( "btn btn-sm btn-danger" )
. text ( "Clear Preference" )
. appendTo ( $ ( "<td/>" ) . appendTo ( tr ) )
. click ( function ( ) {
delete JSPREF [ channel ] ;
setOpt ( "channel_js_pref" , JSPREF ) ;
tr . remove ( ) ;
} ) ;
} ) ;
}
2014-07-10 04:55:49 +00:00
2016-05-05 03:52:55 +00:00
function EmoteList ( selector , emoteClickCallback ) {
2016-03-30 06:31:02 +00:00
this . elem = $ ( selector ) ;
this . initSearch ( ) ;
2018-02-02 01:39:45 +00:00
this . initSortOption ( ) ;
2016-03-30 06:31:02 +00:00
this . table = this . elem . find ( ".emotelist-table" ) [ 0 ] ;
this . paginatorContainer = this . elem . find ( ".emotelist-paginator-container" ) ;
2015-05-12 18:50:59 +00:00
this . cols = 5 ;
this . itemsPerPage = 25 ;
this . emotes = [ ] ;
this . page = 0 ;
2016-05-05 03:52:55 +00:00
this . emoteClickCallback = emoteClickCallback || function ( ) { } ;
2015-05-12 18:50:59 +00:00
}
2016-03-30 06:31:02 +00:00
EmoteList . prototype . initSearch = function ( ) {
this . searchbar = this . elem . find ( ".emotelist-search" ) ;
var self = this ;
this . searchbar . keyup ( function ( ) {
var value = this . value . toLowerCase ( ) ;
if ( value ) {
self . filter = function ( emote ) {
return emote . name . toLowerCase ( ) . indexOf ( value ) >= 0 ;
} ;
} else {
self . filter = null ;
}
self . handleChange ( ) ;
self . loadPage ( 0 ) ;
} ) ;
} ;
2018-02-02 01:39:45 +00:00
EmoteList . prototype . initSortOption = function ( ) {
this . sortOption = this . elem . find ( ".emotelist-alphabetical" ) ;
this . sortAlphabetical = false ;
var self = this ;
this . sortOption . change ( function ( ) {
self . sortAlphabetical = this . checked ;
self . handleChange ( ) ;
self . loadPage ( 0 ) ;
} ) ;
} ;
2015-05-13 17:17:32 +00:00
EmoteList . prototype . handleChange = function ( ) {
this . emotes = CHANNEL . emotes . slice ( ) ;
2016-03-30 06:31:02 +00:00
if ( this . sortAlphabetical ) {
2015-05-13 17:17:32 +00:00
this . emotes . sort ( function ( a , b ) {
2015-05-12 18:50:59 +00:00
var x = a . name . toLowerCase ( ) ;
var y = b . name . toLowerCase ( ) ;
if ( x < y ) {
return - 1 ;
} else if ( x > y ) {
return 1 ;
} else {
return 0 ;
}
} ) ;
2015-05-13 17:17:32 +00:00
}
if ( this . filter ) {
this . emotes = this . emotes . filter ( this . filter ) ;
}
this . paginator = new NewPaginator ( this . emotes . length , this . itemsPerPage ,
this . loadPage . bind ( this ) ) ;
2016-03-30 06:31:02 +00:00
this . paginatorContainer . html ( "" ) ;
this . paginatorContainer . append ( this . paginator . elem ) ;
2015-05-13 17:17:32 +00:00
this . paginator . loadPage ( this . page ) ;
2015-05-12 18:50:59 +00:00
} ;
EmoteList . prototype . loadPage = function ( page ) {
var tbody = this . table . children [ 0 ] ;
tbody . innerHTML = "" ;
var row ;
var start = page * this . itemsPerPage ;
if ( start >= this . emotes . length ) return ;
2015-05-13 17:17:32 +00:00
var end = Math . min ( start + this . itemsPerPage , this . emotes . length ) ;
2015-05-12 18:50:59 +00:00
var _this = this ;
for ( var i = start ; i < end ; i ++ ) {
if ( ( i - start ) % this . cols === 0 ) {
row = document . createElement ( "tr" ) ;
tbody . appendChild ( row ) ;
}
( function ( emote ) {
var td = document . createElement ( "td" ) ;
td . className = "emote-preview-container" ;
// Trick element to vertically align the emote within the container
var hax = document . createElement ( "span" ) ;
hax . className = "emote-preview-hax" ;
td . appendChild ( hax ) ;
var img = document . createElement ( "img" ) ;
img . src = emote . image ;
img . className = "emote-preview" ;
img . title = emote . name ;
2016-05-05 03:52:55 +00:00
img . onclick = _this . emoteClickCallback . bind ( null , emote ) ;
2015-05-12 18:50:59 +00:00
td . appendChild ( img ) ;
row . appendChild ( td ) ;
} ) ( this . emotes [ i ] ) ;
}
this . page = page ;
} ;
2016-05-05 03:52:55 +00:00
function onEmoteClicked ( emote ) {
var val = chatline . value ;
if ( ! val ) {
chatline . value = emote . name ;
} else {
if ( ! val . charAt ( val . length - 1 ) . match ( /\s/ ) ) {
chatline . value += " " ;
}
chatline . value += emote . name ;
}
window . EMOTELISTMODAL . modal ( "hide" ) ;
chatline . focus ( ) ;
}
window . EMOTELIST = new EmoteList ( "#emotelist" , onEmoteClicked ) ;
2018-02-02 01:39:45 +00:00
window . EMOTELIST . sortAlphabetical = USEROPTS . emotelist _sort ;
2015-07-17 04:43:21 +00:00
2016-07-12 06:55:07 +00:00
function CSEmoteList ( selector ) {
EmoteList . call ( this , selector ) ;
}
CSEmoteList . prototype = Object . create ( EmoteList . prototype ) ;
CSEmoteList . prototype . loadPage = function ( page ) {
var tbody = this . table . children [ 1 ] ;
tbody . innerHTML = "" ;
var start = page * this . itemsPerPage ;
if ( start >= this . emotes . length ) {
return ;
}
var end = Math . min ( start + this . itemsPerPage , this . emotes . length ) ;
var self = this ;
2016-07-13 06:04:07 +00:00
this . page = page ;
2016-07-12 06:55:07 +00:00
for ( var i = start ; i < end ; i ++ ) {
var row = document . createElement ( "tr" ) ;
tbody . appendChild ( row ) ;
2017-05-16 16:56:37 +00:00
( function ( emote , row ) {
2016-07-12 06:55:07 +00:00
// Add delete button
var tdDelete = document . createElement ( "td" ) ;
var btnDelete = document . createElement ( "button" ) ;
btnDelete . className = "btn btn-xs btn-danger" ;
var pennJillette = document . createElement ( "span" ) ;
pennJillette . className = "glyphicon glyphicon-trash" ;
btnDelete . appendChild ( pennJillette ) ;
tdDelete . appendChild ( btnDelete ) ;
row . appendChild ( tdDelete ) ;
2016-07-13 06:04:07 +00:00
btnDelete . onclick = function deleteEmote ( ) {
document . getElementById ( "cs-emotes-newname" ) . value = emote . name ;
document . getElementById ( "cs-emotes-newimage" ) . value = emote . image ;
socket . emit ( "removeEmote" , emote ) ;
} ;
2016-07-12 06:55:07 +00:00
// Add emote name
var tdName = document . createElement ( "td" ) ;
var nameDisplay = document . createElement ( "code" ) ;
nameDisplay . textContent = emote . name ;
tdName . appendChild ( nameDisplay ) ;
row . appendChild ( tdName ) ;
2017-05-16 16:56:37 +00:00
var $nameDisplay = $ ( nameDisplay ) ;
$nameDisplay . click ( function ( clickEvent ) {
$nameDisplay . detach ( ) ;
var editInput = document . createElement ( "input" ) ;
editInput . className = "form-control" ;
editInput . type = "text" ;
editInput . value = emote . name ;
tdName . appendChild ( editInput ) ;
editInput . focus ( ) ;
function save ( ) {
var val = editInput . value ;
tdName . removeChild ( editInput ) ;
tdName . appendChild ( nameDisplay ) ;
// Nothing was changed
if ( val === emote . name ) { return }
// Emote name already exists
2017-05-17 02:15:12 +00:00
if ( CHANNEL . emotes . filter ( function ( emote ) { return emote . name === val } ) . length ) {
2017-05-16 16:56:37 +00:00
/ *
* Since we are already in a modal
* and Bootstrap doesn ' t have supermodals
* we will make a self destructing warning
* as a row in the table
* /
var wrow = document . createElement ( "tr" ) ;
var tdBlankDel = document . createElement ( "td" ) ; wrow . appendChild ( tdBlankDel ) ;
var tdWarnMess = document . createElement ( "td" ) ; wrow . appendChild ( tdWarnMess ) ;
var warnSpan = document . createElement ( "p" ) ; tdWarnMess . appendChild ( warnSpan ) ;
warnSpan . className = "text-warning" ;
warnSpan . textContent = "An emote of that name already exists." ;
tdWarnMess . colSpan = "2" ;
row . insertAdjacentElement ( "beforebegin" , wrow )
$ ( wrow ) . delay ( 2500 ) . fadeOut ( 'slow' , function ( ) { $ ( this ) . remove ( ) } ) ;
return ;
}
socket . emit ( "renameEmote" , {
old : emote . name ,
image : emote . image ,
name : val
} ) ;
}
editInput . onblur = save ;
editInput . onkeyup = function ( event ) {
if ( event . keyCode === 13 ) {
save ( ) ;
}
} ;
} ) ;
2016-07-12 06:55:07 +00:00
// Add emote image
var tdImage = document . createElement ( "td" ) ;
var urlDisplay = document . createElement ( "code" ) ;
urlDisplay . textContent = emote . image ;
tdImage . appendChild ( urlDisplay ) ;
row . appendChild ( tdImage ) ;
// Add popover to display the image
var $urlDisplay = $ ( urlDisplay ) ;
$urlDisplay . popover ( {
html : true ,
trigger : "hover" ,
content : '<img src="' + emote . image + '" class="channel-emote">'
} ) ;
// Change the image for an emote
$urlDisplay . click ( function ( clickEvent ) {
$ ( tdImage ) . find ( ".popover" ) . remove ( ) ;
$urlDisplay . detach ( ) ;
var editInput = document . createElement ( "input" ) ;
editInput . className = "form-control" ;
editInput . type = "text" ;
editInput . value = emote . image ;
tdImage . appendChild ( editInput ) ;
editInput . focus ( ) ;
function save ( ) {
var val = editInput . value ;
tdImage . removeChild ( editInput ) ;
tdImage . appendChild ( urlDisplay ) ;
socket . emit ( "updateEmote" , {
name : emote . name ,
image : val
} ) ;
}
editInput . onblur = save ;
editInput . onkeyup = function ( event ) {
if ( event . keyCode === 13 ) {
save ( ) ;
}
} ;
} ) ;
2017-05-16 16:56:37 +00:00
} ) ( this . emotes [ i ] , row ) ;
2016-07-12 06:55:07 +00:00
}
} ;
window . CSEMOTELIST = new CSEmoteList ( "#cs-emotes" ) ;
2018-02-02 01:39:45 +00:00
window . CSEMOTELIST . sortAlphabetical = USEROPTS . emotelist _sort ;
2016-07-13 06:04:07 +00:00
function showChannelSettings ( ) {
$ ( "#channeloptions" ) . modal ( ) ;
}
// There is a point where this file needed to stop and we have clearly passed
// it but let's keep going and see what happens
function startQueueSpinner ( data ) {
if ( $ ( "#queueprogress" ) . length > 0 ) {
return ;
}
var id = data . id ;
if ( data . type === "yp" ) {
id = "$any" ;
}
var progress = $ ( "<div/>" ) . addClass ( "progress" ) . attr ( "id" , "queueprogress" )
. data ( "queue-id" , id ) ;
var progressBar = $ ( "<div/>" ) . addClass ( "progress-bar progress-bar-striped active" )
. attr ( {
role : "progressbar" ,
"aria-valuenow" : "100" ,
"aria-valuemin" : "0" ,
"aria-valuemax" : "100" ,
} ) . css ( {
width : "100%"
} ) . appendTo ( progress ) ;
progress . appendTo ( $ ( "#addfromurl" ) ) ;
}
function stopQueueSpinner ( data ) {
var shouldRemove = ( data !== null &&
typeof data === 'object' &&
$ ( "#queueprogress" ) . data ( "queue-id" ) === data . id ) ;
shouldRemove = shouldRemove || data === null ;
shouldRemove = shouldRemove || $ ( "#queueprogress" ) . data ( "queue-id" ) === "$any" ;
if ( shouldRemove ) {
$ ( "#queueprogress" ) . remove ( ) ;
}
}
2016-10-08 02:55:41 +00:00
function maybePromptToUpgradeUserscript ( ) {
if ( document . getElementById ( 'prompt-upgrade-drive-userscript' ) ) {
return ;
}
if ( ! window . hasDriveUserscript ) {
return ;
}
2017-11-28 07:56:21 +00:00
var currentVersion = GS _VERSION . toString ( ) ; // data.js
2016-10-08 02:55:41 +00:00
var userscriptVersion = window . driveUserscriptVersion ;
if ( ! userscriptVersion ) {
userscriptVersion = '1.0' ;
}
2017-11-28 07:56:21 +00:00
currentVersion = currentVersion . split ( '.' ) . map ( function ( part ) {
return parseInt ( part , 10 ) ;
} ) ;
2016-10-08 02:55:41 +00:00
userscriptVersion = userscriptVersion . split ( '.' ) . map ( function ( part ) {
return parseInt ( part , 10 ) ;
} ) ;
var older = false ;
for ( var i = 0 ; i < currentVersion . length ; i ++ ) {
if ( userscriptVersion [ i ] < currentVersion [ i ] ) {
older = true ;
}
}
if ( ! older ) {
return ;
}
var alertBox = document . createElement ( 'div' ) ;
alertBox . id = 'prompt-upgrade-drive-userscript' ;
alertBox . className = 'alert alert-info'
alertBox . innerHTML = 'A newer version of the Google Drive userscript is available.' ;
alertBox . appendChild ( document . createElement ( 'br' ) ) ;
var infoLink = document . createElement ( 'a' ) ;
infoLink . className = 'btn btn-info' ;
infoLink . href = '/google_drive_userscript' ;
infoLink . textContent = 'Click here for installation instructions' ;
infoLink . target = '_blank' ;
alertBox . appendChild ( infoLink ) ;
var closeButton = document . createElement ( 'button' ) ;
closeButton . className = 'close pull-right' ;
closeButton . innerHTML = '×' ;
closeButton . onclick = function ( ) {
alertBox . parentNode . removeChild ( alertBox ) ;
}
alertBox . insertBefore ( closeButton , alertBox . firstChild )
document . getElementById ( 'videowrap' ) . appendChild ( alertBox ) ;
}
2016-10-08 17:33:18 +00:00
function backoffRetry ( fn , cb , options ) {
var jitter = options . jitter || 0 ;
var factor = options . factor || 1 ;
var isRetryable = options . isRetryable || function ( ) { return true ; } ;
var tries = 0 ;
function callback ( error , result ) {
tries ++ ;
factor *= factor ;
if ( error ) {
if ( tries >= options . maxTries ) {
console . log ( 'Max tries exceeded' ) ;
cb ( error , result ) ;
} else if ( isRetryable ( error ) ) {
var offset = Math . random ( ) * jitter ;
var delay = options . delay * factor + offset ;
console . log ( 'Retrying on error: ' + error ) ;
console . log ( 'Waiting ' + delay + ' ms before retrying' ) ;
setTimeout ( function ( ) {
fn ( callback ) ;
} , delay ) ;
}
} else {
cb ( error , result ) ;
}
}
fn ( callback ) ;
}
2017-01-24 05:47:21 +00:00
CyTube . ui . changeVideoWidth = function uiChangeVideoWidth ( direction ) {
var body = document . body ;
if ( /hd/ . test ( body . className ) ) {
throw new Error ( "ui::changeVideoWidth does not work with the 'hd' layout" ) ;
}
var videoWrap = document . getElementById ( "videowrap" ) ;
var leftControls = document . getElementById ( "leftcontrols" ) ;
var leftPane = document . getElementById ( "leftpane" ) ;
var chatWrap = document . getElementById ( "chatwrap" ) ;
var rightControls = document . getElementById ( "rightcontrols" ) ;
var rightPane = document . getElementById ( "rightpane" ) ;
var match = videoWrap . className . match ( /col-md-(\d+)/ ) ;
if ( ! match ) {
throw new Error ( "ui::changeVideoWidth: videowrap is missing bootstrap class!" ) ;
}
var videoWidth = parseInt ( match [ 1 ] , 10 ) + direction ;
if ( videoWidth < 3 || videoWidth > 9 ) {
return ;
}
var chatWidth = 12 - videoWidth ;
videoWrap . className = "col-md-" + videoWidth + " col-lg-" + videoWidth ;
rightControls . className = "col-md-" + videoWidth + " col-lg-" + videoWidth ;
rightPane . className = "col-md-" + videoWidth + " col-lg-" + videoWidth ;
chatWrap . className = "col-md-" + chatWidth + " col-lg-" + chatWidth ;
leftControls . className = "col-md-" + chatWidth + " col-lg-" + chatWidth ;
leftPane . className = "col-md-" + chatWidth + " col-lg-" + chatWidth ;
handleVideoResize ( ) ;
} ;
2018-05-25 06:49:28 +00:00
2018-06-07 05:45:08 +00:00
CyTube . _internal _do _not _use _or _you _will _be _banned . addUserToList = function ( data , removePrev ) {
if ( removePrev ) {
var user = findUserlistItem ( data . name ) ;
// Remove previous instance of user, if there was one
if ( user !== null )
user . remove ( ) ;
}
2018-05-25 06:49:28 +00:00
var div = $ ( "<div/>" )
. addClass ( "userlist_item" ) ;
var icon = $ ( "<span/>" ) . appendTo ( div ) ;
var nametag = $ ( "<span/>" ) . text ( data . name ) . appendTo ( div ) ;
div . data ( "name" , data . name ) ;
div . data ( "rank" , data . rank ) ;
div . data ( "leader" , Boolean ( data . leader ) ) ;
div . data ( "profile" , data . profile ) ;
div . data ( "meta" , data . meta ) ;
if ( data . meta . muted || data . meta . smuted ) {
div . data ( "icon" , "glyphicon-volume-off" ) ;
} else {
div . data ( "icon" , false ) ;
}
formatUserlistItem ( div ) ;
addUserDropdown ( div , data ) ;
div . appendTo ( $ ( "#userlist" ) ) ;
} ;