2015-01-12 17:58:07 -05:00
( function ( window ) { ( function e ( t , n , r ) { function s ( o , u ) { if ( ! n [ o ] ) { if ( ! t [ o ] ) { var a = typeof require == "function" && require ; if ( ! u && a ) return a ( o , ! 0 ) ; if ( i ) return i ( o , ! 0 ) ; throw new Error ( "Cannot find module '" + o + "'" ) } var f = n [ o ] = { exports : { } } ; t [ o ] [ 0 ] . call ( f . exports , function ( e ) { var n = t [ o ] [ 1 ] [ e ] ; return s ( n ? n : e ) } , f , f . exports , e , t , n , r ) } return n [ o ] . exports } var i = typeof require == "function" && require ; for ( var o = 0 ; o < r . length ; o ++ ) s ( r [ o ] ) ; return s } ) ( { 1 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ,
constants = require ( './constants' ) ,
utils = require ( './utils' ) ;
// --------------------
// Initialization
// --------------------
FFZ . prototype . setup _badges = function ( ) {
this . log ( "Preparing badge system." ) ;
this . badges = { } ;
this . log ( "Creating badge style element." ) ;
var s = this . _badge _style = document . createElement ( 'style' ) ;
s . id = "ffz-badge-css" ;
document . head . appendChild ( s ) ;
this . log ( "Adding legacy donor badges." ) ;
this . _legacy _add _donors ( ) ;
}
// --------------------
// Badge CSS
// --------------------
var badge _css = function ( badge ) {
return ".badges .ffz-badge-" + badge . id + " { background-color: " + badge . color + '; background-image: url("' + badge . image + '"); ' + ( badge . extra _css || "" ) + '}' ;
}
// --------------------
// Render Badge
// --------------------
FFZ . prototype . bttv _badges = function ( data ) {
var user _id = data . sender ,
user = this . users [ user _id ] ,
badges _out = [ ] ,
insert _at = - 1 ;
if ( ! user || ! user . badges )
return ;
// Determine where in the list to insert these badges.
for ( var i = 0 ; i < data . badges . length ; i ++ ) {
var badge = data . badges [ i ] ;
if ( badge . type == "subscriber" || badge . type == "turbo" ) {
insert _at = i ;
break ;
}
}
for ( var slot in user . badges ) {
if ( ! user . badges . hasOwnProperty ( slot ) )
continue ;
var badge = user . badges [ slot ] ,
full _badge = this . badges [ badge . id ] || { } ,
desc = badge . title || full _badge . title ,
style = "" ,
alpha = BetterTTV . settings . get ( 'alphaTags' ) ;
if ( badge . image )
style += 'background-image: url(\\"' + badge . image + '\\"); ' ;
if ( badge . color && ! alpha )
style += 'background-color: ' + badge . color + '; ' ;
if ( badge . extra _css )
style += badge . extra _css ;
if ( style )
desc += '" style="' + style ;
badges _out . push ( [ ( insert _at == - 1 ? 1 : - 1 ) * slot , { type : "ffz-badge-" + badge . id + ( alpha ? " alpha" : "" ) , name : "" , description : desc } ] ) ;
}
badges _out . sort ( function ( a , b ) { return a [ 0 ] - b [ 0 ] } ) ;
if ( insert _at == - 1 ) {
while ( badges _out . length )
data . badges . push ( badges _out . shift ( ) [ 1 ] ) ;
} else {
while ( badges _out . length )
data . badges . insertAt ( insert _at , badges _out . shift ( ) [ 1 ] ) ;
}
}
FFZ . prototype . render _badge = function ( view ) {
var user = view . get ( 'context.model.from' ) ,
room _id = view . get ( 'context.parentController.content.id' ) ,
badges = view . $ ( '.badges' ) ;
var data = this . users [ user ] ;
if ( ! data || ! data . badges )
return ;
// Figure out where to place our badge(s).
var before = badges . find ( '.badge' ) . filter ( function ( i ) {
var t = this . title . toLowerCase ( ) ;
return t == "subscriber" || t == "turbo" ;
} ) . first ( ) ;
var badges _out = [ ] , reverse = ! ( ! before . length ) ;
for ( var slot in data . badges ) {
if ( ! data . badges . hasOwnProperty ( slot ) )
continue ;
var badge = data . badges [ slot ] ,
full _badge = this . badges [ badge . id ] || { } ;
var el = document . createElement ( 'div' ) ;
el . className = 'badge float-left tooltip ffz-badge-' + badge . id ;
el . setAttribute ( 'title' , badge . title || full _badge . title ) ;
if ( badge . image )
el . style . backgroundImage = 'url("' + badge . image + '")' ;
if ( badge . color )
el . style . backgroundColor = badge . color ;
if ( badge . extra _css )
el . style . cssText += badge . extra _css ;
badges _out . push ( [ ( ( reverse ? 1 : - 1 ) * slot ) , el ] ) ;
}
badges _out . sort ( function ( a , b ) { return a [ 0 ] - b [ 0 ] } ) ;
if ( reverse ) {
while ( badges _out . length )
before . before ( badges _out . shift ( ) [ 1 ] ) ;
} else {
while ( badges _out . length )
badges . append ( badges _out . shift ( ) [ 1 ] ) ;
}
}
// --------------------
// Legacy Support
// --------------------
FFZ . prototype . _legacy _add _donors = function ( tries ) {
this . badges [ 1 ] = { id : 1 , title : "FFZ Donor" , color : "#755000" , image : "//cdn.frankerfacez.com/channel/global/donoricon.png" } ;
utils . update _css ( this . _badge _style , 1 , badge _css ( this . badges [ 1 ] ) ) ;
// Developer Badges
// TODO: Upload the badge to the proper CDN.
this . badges [ 0 ] = { id : 0 , title : "FFZ Developer" , color : "#FAAF19" , image : "//cdn.frankerfacez.com/channel/global/devicon.png" } ;
utils . update _css ( this . _badge _style , 0 , badge _css ( this . badges [ 0 ] ) ) ;
this . users . sirstendec = { badges : { 0 : { id : 0 } } } ;
jQuery . ajax ( constants . SERVER + "script/donors.txt" , { cache : false , context : this } )
. done ( function ( data ) {
this . _legacy _parse _donors ( data ) ;
} ) . fail ( function ( data ) {
if ( data . status == 404 )
return ;
tries = ( tries || 0 ) + 1 ;
if ( tries < 10 )
return this . _legacy _add _donors ( tries ) ;
} ) ;
}
FFZ . prototype . _legacy _parse _donors = function ( data ) {
var count = 0 ;
if ( data != null ) {
var lines = data . trim ( ) . split ( /\W+/ ) ;
for ( var i = 0 ; i < lines . length ; i ++ ) {
var user _id = lines [ i ] ,
user = this . users [ user _id ] = this . users [ user _id ] || { } ,
badges = user . badges = user . badges || { } ;
if ( badges [ 0 ] )
continue ;
badges [ 0 ] = { id : 1 } ;
count += 1 ;
}
}
this . log ( "Added donor badge to " + utils . number _commas ( count ) + " users." ) ;
2014-03-27 23:24:57 -04:00
}
2015-02-08 02:01:09 -05:00
} , { "./constants" : 3 , "./utils" : 25 } ] , 2 : [ function ( require , module , exports ) {
2015-01-16 17:45:37 -05:00
var FFZ = window . FrankerFaceZ ;
2015-01-19 15:27:10 -05:00
2015-01-16 17:45:37 -05:00
// -----------------
2015-01-19 15:27:10 -05:00
// Mass Moderation
2015-01-16 17:45:37 -05:00
// -----------------
FFZ . chat _commands . massunmod = function ( room , args ) {
args = args . join ( " " ) . trim ( ) ;
if ( ! args . length )
return "You must provide a list of users to unmod." ;
args = args . split ( /\W*,\W*/ ) ;
var user = this . get _user ( ) ;
if ( ! user || ! user . login == room . id )
return "You must be the broadcaster to use massunmod." ;
if ( args . length > 50 )
return "Each user you unmod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses." ;
var count = args . length ;
while ( args . length ) {
var name = args . shift ( ) ;
room . room . tmiRoom . sendMessage ( "/unmod " + name ) ;
}
return "Sent unmod command for " + count + " users." ;
}
FFZ . chat _commands . massunmod . help = "Usage: /ffz massunmod <list, of, users>\nBroadcaster only. Unmod all the users in the provided list." ;
FFZ . chat _commands . massmod = function ( room , args ) {
args = args . join ( " " ) . trim ( ) ;
if ( ! args . length )
return "You must provide a list of users to mod." ;
args = args . split ( /\W*,\W*/ ) ;
var user = this . get _user ( ) ;
if ( ! user || ! user . login == room . id )
return "You must be the broadcaster to use massmod." ;
if ( args . length > 50 )
return "Each user you mod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses." ;
var count = args . length ;
while ( args . length ) {
var name = args . shift ( ) ;
room . room . tmiRoom . sendMessage ( "/mod " + name ) ;
}
return "Sent mod command for " + count + " users." ;
}
FFZ . chat _commands . massmod . help = "Usage: /ffz massmod <list, of, users>\nBroadcaster only. Mod all the users in the provided list." ;
2015-01-19 15:27:10 -05:00
} , { } ] , 3 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var SVGPATH = '<path d="m120.95 1.74c4.08-0.09 8.33-0.84 12.21 0.82 3.61 1.8 7 4.16 11.01 5.05 2.08 3.61 6.12 5.46 8.19 9.07 3.6 5.67 7.09 11.66 8.28 18.36 1.61 9.51 7.07 17.72 12.69 25.35 3.43 7.74 1.97 16.49 3.6 24.62 2.23 5.11 4.09 10.39 6.76 15.31 1.16 2 4.38 0.63 4.77-1.32 1.2-7.1-2.39-13.94-1.97-21.03 0.38-3.64-0.91-7.48 0.25-10.99 2.74-3.74 4.57-8.05 7.47-11.67 3.55-5.47 10.31-8.34 16.73-7.64 2.26 2.89 5.13 5.21 7.58 7.92 2.88 4.3 6.52 8.01 9.83 11.97 1.89 2.61 3.06 5.64 4.48 8.52 2.81 4.9 4 10.5 6.63 15.49 2.16 6.04 5.56 11.92 5.37 18.5 0.65 1.95 0.78 4 0.98 6.03 1.01 3.95 2.84 8.55 0.63 12.42-2.4 5.23-7.03 8.97-11.55 12.33-6.06 4.66-11.62 10.05-18.37 13.75-4.06 2.65-8.24 5.17-12.71 7.08-3.59 1.57-6.06 4.94-9.85 6.09-2.29 1.71-3.98 4.51-6.97 5.02-4.56 1.35-8.98-3.72-13.5-1.25-2.99 1.83-6.19 3.21-9.39 4.6-8.5 5.61-18.13 9.48-28.06 11.62-8.36-0.2-16.69 0.62-25.05 0.47-3.5-1.87-7.67-1.08-11.22-2.83-6.19-1.52-10.93-6.01-16.62-8.61-2.87-1.39-5.53-3.16-8.11-4.99-2.58-1.88-4.17-4.85-6.98-6.44-3.83-0.11-6.54 3.42-10.24 3.92-2.31 0.28-4.64 0.32-6.96 0.31-3.5-3.65-5.69-8.74-10.59-10.77-5.01-3.68-10.57-6.67-14.84-11.25-2.52-2.55-5.22-4.87-8.24-6.8-4.73-4.07-7.93-9.51-11.41-14.62-3.08-4.41-5.22-9.73-4.6-15.19 0.65-8.01 0.62-16.18 2.55-24.02 4.06-10.46 11.15-19.34 18.05-28.06 3.71-5.31 9.91-10.21 16.8-8.39 3.25 1.61 5.74 4.56 7.14 7.89 1.19 2.7 3.49 4.93 3.87 7.96 0.97 5.85 1.6 11.86 0.74 17.77-1.7 6.12-2.98 12.53-2.32 18.9 0.01 2.92 2.9 5.36 5.78 4.57 3.06-0.68 3.99-4.07 5.32-6.48 1.67-4.06 4.18-7.66 6.69-11.23 3.61-5.28 5.09-11.57 7.63-17.37 2.07-4.56 1.7-9.64 2.56-14.46 0.78-7.65-0.62-15.44 0.7-23.04 1.32-3.78 1.79-7.89 3.8-11.4 3.01-3.66 6.78-6.63 9.85-10.26 1.72-2.12 4.21-3.32 6.55-4.6 7.89-2.71 15.56-6.75 24.06-7z"/>' ,
DEBUG = localStorage . ffzDebugMode == "true" && document . body . classList . contains ( 'ffz-dev' ) ;
module . exports = {
DEBUG : DEBUG ,
SERVER : DEBUG ? "//localhost:8000/" : "//cdn.frankerfacez.com/" ,
SVGPATH : SVGPATH ,
ZREKNARF : '<svg style="padding:1.75px 0" class="svg-glyph_views" width="16px" viewBox="0 0 249 195" version="1.1" height="12.5px">' + SVGPATH + '</svg>' ,
2015-02-08 02:01:09 -05:00
CHAT _BUTTON : '<svg class="svg-emoticons ffz-svg" height="18px" width="24px" viewBox="0 0 249 195" version="1.1">' + SVGPATH + '</svg>' ,
GEAR : '<svg class="svg-gear" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M15,7v2h-2.115c-0.125,0.615-0.354,1.215-0.713,1.758l1.484,1.484l-1.414,1.414l-1.484-1.484C10.215,12.531,9.615,12.76,9,12.885V15H7v-2.12c-0.614-0.126-1.21-0.356-1.751-0.714l-1.491,1.49l-1.414-1.414l1.491-1.49C3.477,10.211,3.247,9.613,3.12,9H1V7h2.116C3.24,6.384,3.469,5.785,3.829,5.242L2.343,3.757l1.414-1.414l1.485,1.485C5.785,3.469,6.384,3.24,7,3.115V1h2v2.12c0.613,0.126,1.211,0.356,1.752,0.714l1.49-1.491l1.414,1.414l-1.49,1.492C12.523,5.79,12.754,6.387,12.88,7H15z M8,6C6.896,6,6,6.896,6,8s0.896,2,2,2s2-0.896,2-2S9.104,6,8,6z" fill-rule="evenodd"></path></svg>' ,
HEART : '<svg class="svg-heart" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M8,13.5L1.5,7V4l2-2h3L8,3.5L9.5,2h3l2,2v3L8,13.5z" fill-rule="evenodd"></path></svg>'
2014-03-27 22:44:11 -04:00
}
2015-01-19 15:27:10 -05:00
} , { } ] , 4 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ;
// -----------------------
// Developer Mode Command
// -----------------------
FFZ . chat _commands . developer _mode = function ( room , args ) {
var enabled , args = args && args . length ? args [ 0 ] . toLowerCase ( ) : null ;
if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true ;
else if ( args == "n" || args == "no" || args == "false" || args == "off" )
enabled = false ;
if ( enabled === undefined )
return "Developer Mode is currently " + ( localStorage . ffzDebugMode == "true" ? "enabled." : "disabled." ) ;
localStorage . ffzDebugMode = enabled ;
return "Developer Mode is now " + ( enabled ? "enabled" : "disabled" ) + ". Please refresh your browser." ;
}
FFZ . chat _commands . developer _mode . help = "Usage: /ffz developer_mode <on|off>\nEnable or disable Developer Mode. When Developer Mode is enabled, the script will be reloaded from //localhost:8000/script.js instead of from the CDN." ;
2015-01-15 13:58:42 -05:00
2015-01-19 15:27:10 -05:00
} , { } ] , 5 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ;
// --------------------
// Initialization
// --------------------
FFZ . prototype . setup _chatview = function ( ) {
this . log ( "Hooking the Ember Chat view." ) ;
var Chat = App . _ _container _ _ . resolve ( 'view:chat' ) ;
this . _modify _cview ( Chat ) ;
// For some reason, this doesn't work unless we create an instance of the
// chat view and then destroy it immediately.
Chat . create ( ) . destroy ( ) ;
// Modify all existing Chat views.
for ( var key in Ember . View . views ) {
if ( ! Ember . View . views . hasOwnProperty ( key ) )
continue ;
var view = Ember . View . views [ key ] ;
if ( ! ( view instanceof Chat ) )
continue ;
this . log ( "Adding UI link manually to Chat view." , view ) ;
view . $ ( '.textarea-contain' ) . append ( this . build _ui _link ( view ) ) ;
}
}
// --------------------
// Modify Chat View
// --------------------
FFZ . prototype . _modify _cview = function ( view ) {
var f = this ;
view . reopen ( {
didInsertElement : function ( ) {
this . _super ( ) ;
this . $ ( ) && this . $ ( '.textarea-contain' ) . append ( f . build _ui _link ( this ) ) ;
} ,
willClearRender : function ( ) {
this . _super ( ) ;
this . $ ( ".ffz-ui-toggle" ) . remove ( ) ;
} ,
ffzUpdateLink : Ember . observer ( 'controller.currentRoom' , function ( ) {
f . update _ui _link ( ) ;
} )
} ) ;
2014-03-27 23:24:57 -04:00
}
2015-01-19 15:27:10 -05:00
} , { } ] , 6 : [ function ( require , module , exports ) {
2015-01-20 20:25:26 -05:00
var FFZ = window . FrankerFaceZ ,
reg _escape = function ( str ) {
return str . replace ( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g , "\\$&" ) ;
} ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
// ---------------------
// Settings
// ---------------------
FFZ . settings _info . capitalize = {
type : "boolean" ,
value : true ,
visible : function ( ) { return ! this . has _bttv } ,
name : "Username Capitalization" ,
help : "Display names in chat with proper capitalization."
} ;
FFZ . settings _info . keywords = {
type : "button" ,
value : [ ] ,
visible : function ( ) { return ! this . has _bttv } ,
name : "Highlight Keywords" ,
help : "Set additional keywords that will be highlighted in chat." ,
method : function ( ) {
var old _val = this . settings . keywords . join ( ", " ) ,
new _val = prompt ( "Highlight Keywords\n\nPlease enter a comma-separated list of words that you would like to be highlighted in chat." , old _val ) ;
if ( ! new _val )
return ;
// Split them up.
new _val = new _val . trim ( ) . split ( /\W*,\W*/ ) ;
if ( new _val . length == 1 && ( new _val [ 0 ] == "" || new _val [ 0 ] == "disable" ) )
new _val = [ ] ;
this . settings . set ( "keywords" , new _val ) ;
}
} ;
FFZ . settings _info . chat _rows = {
type : "boolean" ,
value : false ,
visible : function ( ) { return ! this . has _bttv } ,
name : "Chat Line Backgrounds" ,
help : "Display alternating background colors for lines in chat." ,
on _update : function ( val ) {
document . querySelector ( ".app-main" ) . classList . toggle ( "ffz-chat-background" , val ) ;
}
} ;
2015-01-20 01:53:18 -05:00
// ---------------------
// Initialization
// ---------------------
FFZ . prototype . setup _line = function ( ) {
2015-02-08 02:01:09 -05:00
// Alternating Background
document . querySelector ( '.app-main' ) . classList . toggle ( 'ffz-chat-background' , this . settings . chat _rows ) ;
this . _last _row = { } ;
2015-01-20 01:53:18 -05:00
this . log ( "Hooking the Ember Line controller." ) ;
var Line = App . _ _container _ _ . resolve ( 'controller:line' ) ,
f = this ;
Line . reopen ( {
tokenizedMessage : function ( ) {
// Add our own step to the tokenization procedure.
2015-01-20 20:25:26 -05:00
var tokens = f . _emoticonize ( this , this . _super ( ) ) ,
user = f . get _user ( ) ;
if ( ! user || this . get ( "model.from" ) != user . login )
tokens = f . _mentionize ( this , tokens ) ;
return tokens ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
} . property ( "model.message" , "isModeratorOrHigher" )
2015-01-20 01:53:18 -05:00
} ) ;
this . log ( "Hooking the Ember Line view." ) ;
var Line = App . _ _container _ _ . resolve ( 'view:line' ) ;
Line . reopen ( {
didInsertElement : function ( ) {
this . _super ( ) ;
var el = this . get ( 'element' ) ,
2015-02-08 02:01:09 -05:00
user = this . get ( 'context.model.from' ) ,
room = this . get ( 'context.parentController.content.id' ) ,
row _type = this . get ( 'context.model.ffzAlternate' ) ;
if ( row _type === undefined ) {
row _type = f . _last _row [ room ] = f . _last _row . hasOwnProperty ( room ) ? ! f . _last _row [ room ] : false ;
this . set ( "context.model.ffzAlternate" , row _type ) ;
}
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
el . classList . toggle ( 'ffz-alternate' , row _type ) ;
el . setAttribute ( 'data-room' , room ) ;
2015-01-20 01:53:18 -05:00
el . setAttribute ( 'data-sender' , user ) ;
f . render _badge ( this ) ;
2015-02-08 02:01:09 -05:00
if ( f . settings . capitalize )
2015-01-20 01:53:18 -05:00
f . capitalize ( this , user ) ;
2015-02-08 02:01:09 -05:00
// Check for any mentions.
var mentioned = el . querySelector ( 'span.mentioned' ) ;
if ( mentioned ) {
el . classList . add ( "ffz-mentioned" ) ;
if ( ! document . hasFocus ( ) && ! this . get ( 'context.model.ffzNotified' ) && f . settings . highlight _notifications ) {
var cap _room = FFZ . get _capitalization ( room ) ,
cap _user = FFZ . get _capitalization ( user ) ,
room _name = cap _room ,
msg = this . get ( "context.model.message" ) ;
if ( this . get ( "context.parentController.content.isGroupRoom" ) )
room _name = this . get ( "context.parentController.content.tmiRoom.displayName" ) ;
if ( this . get ( "context.model.style" ) == "action" )
msg = "* " + cap _user + " " + msg ;
else
msg = cap _user + ": " + msg ;
f . show _notification (
msg ,
"Twitch Chat Mention in " + room _name ,
cap _room ,
60000 ,
window . focus . bind ( window )
) ;
}
}
// Mark that we've checked this message for mentions.
this . set ( 'context.model.ffzNotified' , true ) ;
2015-01-20 01:53:18 -05:00
}
} ) ;
// Store the capitalization of our own name.
var user = this . get _user ( ) ;
if ( user && user . name )
FFZ . capitalization [ user . login ] = [ user . name , Date . now ( ) ] ;
}
// ---------------------
// Capitalization
// ---------------------
FFZ . capitalization = { } ;
FFZ . _cap _fetching = 0 ;
FFZ . get _capitalization = function ( name , callback ) {
2015-02-08 02:01:09 -05:00
// Use the BTTV code if it's present.
if ( window . BetterTTV )
return BetterTTV . chat . helpers . lookupDisplayName ( name ) ;
2015-02-08 02:59:20 -05:00
if ( ! name )
return name ;
2015-01-20 01:53:18 -05:00
name = name . toLowerCase ( ) ;
if ( name == "jtv" || name == "twitchnotify" )
return name ;
var old _data = FFZ . capitalization [ name ] ;
if ( old _data ) {
if ( Date . now ( ) - old _data [ 1 ] < 3600000 )
return old _data [ 0 ] ;
}
if ( FFZ . _cap _fetching < 5 ) {
FFZ . _cap _fetching ++ ;
Twitch . api . get ( "users/" + name )
. always ( function ( data ) {
var cap _name = data . display _name || name ;
FFZ . capitalization [ name ] = [ cap _name , Date . now ( ) ] ;
FFZ . _cap _fetching -- ;
2015-01-20 20:25:26 -05:00
typeof callback === "function" && callback ( cap _name ) ;
2015-01-20 01:53:18 -05:00
} ) ;
}
return old _data ? old _data [ 0 ] : name ;
}
FFZ . prototype . capitalize = function ( view , user ) {
var name = FFZ . get _capitalization ( user , this . capitalize . bind ( this , view ) ) ;
if ( name )
view . $ ( '.from' ) . text ( name ) ;
}
FFZ . chat _commands . capitalization = function ( room , args ) {
var enabled , args = args && args . length ? args [ 0 ] . toLowerCase ( ) : null ;
if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true ;
else if ( args == "n" || args == "no" || args == "false" || args == "off" )
enabled = false ;
if ( enabled === undefined )
2015-02-08 02:01:09 -05:00
return "Chat Name Capitalization is currently " + ( this . settings . capitalize ? "enabled." : "disabled." ) ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
this . settings . set ( "capitalize" , enabled ) ;
2015-01-20 01:53:18 -05:00
return "Chat Name Capitalization is now " + ( enabled ? "enabled." : "disabled." ) ;
}
FFZ . chat _commands . capitalization . help = "Usage: /ffz capitalization <on|off>\nEnable or disable Chat Name Capitalization. This setting does not work with BetterTTV." ;
2015-01-20 20:25:26 -05:00
// ---------------------
// Extra Mentions
// ---------------------
FFZ . _regex _cache = { } ;
2015-02-08 02:01:09 -05:00
FFZ . _get _rex = function ( word ) {
return FFZ . _regex _cache [ word ] = FFZ . _regex _cache [ word ] || RegExp ( "\\b" + reg _escape ( word ) + "\\b" , "ig" ) ;
}
FFZ . _mentions _to _regex = function ( list ) {
return FFZ . _regex _cache [ list ] = FFZ . _regex _cache [ list ] || RegExp ( "\\b(?:" + _ . chain ( list ) . map ( reg _escape ) . value ( ) . join ( "|" ) + ")\\b" , "ig" ) ;
2015-01-20 20:25:26 -05:00
}
FFZ . prototype . _mentionize = function ( controller , tokens ) {
2015-02-08 02:01:09 -05:00
var mention _words = this . settings . keywords ;
2015-02-08 02:59:20 -05:00
if ( ! mention _words || ! mention _words . length )
2015-01-20 20:25:26 -05:00
return tokens ;
if ( typeof tokens == "string" )
tokens = [ tokens ] ;
2015-02-08 02:01:09 -05:00
var regex = FFZ . _mentions _to _regex ( mention _words ) ;
2015-01-20 20:25:26 -05:00
2015-02-08 02:01:09 -05:00
return _ . chain ( tokens ) . map ( function ( token ) {
if ( ! _ . isString ( token ) )
return token ;
else if ( ! token . match ( regex ) )
return [ token ] ;
2015-01-20 20:25:26 -05:00
2015-02-08 02:01:09 -05:00
return _ . zip (
_ . map ( token . split ( regex ) , _ . identity ) ,
_ . map ( token . match ( regex ) , function ( e ) {
return {
mentionedUser : e ,
own : false
} ;
} )
) ;
} ) . flatten ( ) . compact ( ) . value ( ) ;
2015-01-20 20:25:26 -05:00
}
FFZ . chat _commands . mentionize = function ( room , args ) {
if ( args && args . length ) {
2015-02-08 02:01:09 -05:00
var mention _words = args . join ( " " ) . trim ( ) . split ( /\W*,\W*/ ) ;
if ( mention _words . length == 1 && mention _words [ 0 ] == "disable" )
mention _words = [ ] ;
2015-01-20 20:25:26 -05:00
2015-02-08 02:01:09 -05:00
this . settings . set ( "keywords" , mention _words ) ;
2015-01-20 20:25:26 -05:00
}
2015-02-08 02:01:09 -05:00
var mention _words = this . settings . keywords ;
if ( mention _words . length )
return "The following words will be highlighted: " + mention _words . join ( ", " ) ;
2015-01-20 20:25:26 -05:00
else
2015-02-08 02:01:09 -05:00
return "There are no words set that will be highlighted." ;
2015-01-20 20:25:26 -05:00
}
2015-02-08 02:01:09 -05:00
FFZ . chat _commands . mentionize . help = "Usage: /ffz mentionize <comma, separated, word, list|disable>\nSet a list of words that will also be highlighted in chat." ;
2015-01-20 20:25:26 -05:00
2015-01-20 01:53:18 -05:00
// ---------------------
// Emoticon Replacement
// ---------------------
FFZ . prototype . _emoticonize = function ( controller , tokens ) {
var room _id = controller . get ( "parentController.model.id" ) ,
user _id = controller . get ( "model.from" ) ,
f = this ;
// Get our sets.
var sets = this . getEmotes ( user _id , room _id ) ,
emotes = [ ] ;
// Build a list of emotes that match.
_ . each ( sets , function ( set _id ) {
var set = f . emote _sets [ set _id ] ;
if ( ! set )
return ;
_ . each ( set . emotes , function ( emote ) {
_ . any ( tokens , function ( token ) {
return _ . isString ( token ) && token . match ( emote . regex ) ;
} ) && emotes . push ( emote ) ;
} ) ;
} ) ;
// Don't bother proceeding if we have no emotes.
if ( ! emotes . length )
return tokens ;
// Now that we have all the matching tokens, do crazy stuff.
if ( typeof tokens == "string" )
tokens = [ tokens ] ;
// This is weird stuff I basically copied from the old Twitch code.
// Here, for each emote, we split apart every text token and we
// put it back together with the matching bits of text replaced
// with an object telling Twitch's line template how to render the
// emoticon.
_ . each ( emotes , function ( emote ) {
//var eo = {isEmoticon:true, cls: emote.klass};
var eo = { isEmoticon : true , cls : emote . klass , emoticonSrc : emote . url , altText : ( emote . hidden ? "???" : emote . name ) } ;
tokens = _ . compact ( _ . flatten ( _ . map ( tokens , function ( token ) {
if ( _ . isObject ( token ) )
return token ;
var tbits = token . split ( emote . regex ) , bits = [ ] ;
tbits . forEach ( function ( val , ind ) {
bits . push ( val ) ;
if ( ind !== tbits . length - 1 )
bits . push ( eo ) ;
} ) ;
return bits ;
} ) ) ) ;
} ) ;
return tokens ;
2014-03-27 23:24:57 -04:00
}
2015-01-19 15:27:10 -05:00
} , { } ] , 7 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ,
CSS = /\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)px;\s*?margin:([^;}]+);?([^}]*)\}/mg ,
MOD _CSS = /[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/ ,
GROUP _CHAT = /^_([^_]+)_\d+$/ ,
constants = require ( '../constants' ) ,
utils = require ( '../utils' ) ,
moderator _css = function ( room ) {
if ( ! room . moderator _badge )
return "" ;
return '.chat-line[data-room="' + room . id + '"] .badges .moderator { background-image:url("' + room . moderator _badge + '") !important; }' ;
}
// --------------------
// Initialization
// --------------------
FFZ . prototype . setup _room = function ( ) {
this . rooms = { } ;
this . log ( "Creating room style element." ) ;
var s = this . _room _style = document . createElement ( "style" ) ;
s . id = "ffz-room-css" ;
document . head . appendChild ( s ) ;
this . log ( "Hooking the Ember Room model." ) ;
var Room = App . _ _container _ _ . resolve ( 'model:room' ) ;
this . _modify _room ( Room ) ;
// Modify all current instances of Room, as the changes to the base
// class won't be inherited automatically.
var instances = Room . instances ;
for ( var key in instances ) {
if ( ! instances . hasOwnProperty ( key ) )
continue ;
var inst = instances [ key ] ;
this . add _room ( inst . id , inst ) ;
this . _modify _room ( inst ) ;
}
}
// --------------------
// Command System
// --------------------
FFZ . chat _commands = { } ;
FFZ . prototype . room _message = function ( room , text ) {
var lines = text . split ( "\n" ) ;
if ( this . has _bttv ) {
for ( var i = 0 ; i < lines . length ; i ++ )
BetterTTV . chat . handlers . onPrivmsg ( room . id , { style : 'admin' , date : new Date ( ) , from : 'jtv' , message : lines [ i ] } ) ;
} else {
for ( var i = 0 ; i < lines . length ; i ++ )
room . room . addMessage ( { style : 'ffz admin' , date : new Date ( ) , from : 'FFZ' , message : lines [ i ] } ) ;
}
}
FFZ . prototype . run _command = function ( text , room _id ) {
var room = this . rooms [ room _id ] ;
if ( ! room || ! room . room )
return ;
if ( ! text ) {
// Try to pop-up the menu.
var link = document . querySelector ( 'a.ffz-ui-toggle' ) ;
if ( link )
return link . click ( ) ;
text = "help" ;
}
var args = text . split ( " " ) ,
cmd = args . shift ( ) . toLowerCase ( ) ;
this . log ( "Received Command: " + cmd , args , true ) ;
var command = FFZ . chat _commands [ cmd ] , output ;
if ( command ) {
try {
output = command . bind ( this ) ( room , args ) ;
} catch ( err ) {
this . log ( "Error Running Command - " + cmd + ": " + err , room ) ;
output = "There was an error running the command." ;
}
} else
output = 'There is no "' + cmd + '" command.' ;
if ( output )
this . room _message ( room , output ) ;
}
FFZ . chat _commands . help = function ( room , args ) {
if ( args && args . length ) {
var command = FFZ . chat _commands [ args [ 0 ] . toLowerCase ( ) ] ;
if ( ! command )
return 'There is no "' + args [ 0 ] + '" command.' ;
else if ( ! command . help )
return 'No help is available for the command "' + args [ 0 ] + '".' ;
else
return command . help ;
}
var cmds = [ ] ;
for ( var c in FFZ . chat _commands )
FFZ . chat _commands . hasOwnProperty ( c ) && cmds . push ( c ) ;
return "The available commands are: " + cmds . join ( ", " ) ;
}
FFZ . chat _commands . help . help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command." ;
// --------------------
// Room Management
// --------------------
FFZ . prototype . add _room = function ( id , room ) {
if ( this . rooms [ id ] )
return this . log ( "Tried to add existing room: " + id ) ;
this . log ( "Adding Room: " + id ) ;
// Create a basic data table for this room.
this . rooms [ id ] = { id : id , room : room , menu _sets : [ ] , sets : [ ] , css : null } ;
// Let the server know where we are.
this . ws _send ( "sub" , id ) ;
// For now, we use the legacy function to grab the .css file.
this . _legacy _add _room ( id ) ;
}
FFZ . prototype . remove _room = function ( id ) {
var room = this . rooms [ id ] ;
if ( ! room )
return ;
this . log ( "Removing Room: " + id ) ;
// Remove the CSS
if ( room . css || room . moderator _badge )
utils . update _css ( this . _room _style , id , null ) ;
// Let the server know we're gone and delete our data for this room.
this . ws _send ( "unsub" , id ) ;
delete this . rooms [ id ] ;
// Clean up sets we aren't using any longer.
for ( var i = 0 ; i < room . sets . length ; i ++ ) {
var set _id = room . sets [ i ] , set = this . emote _sets [ set _id ] ;
if ( ! set )
continue ;
set . users . removeObject ( id ) ;
if ( ! set . global && ! set . users . length )
this . unload _set ( set _id ) ;
}
}
// --------------------
// Receiving Set Info
// --------------------
FFZ . prototype . load _room = function ( room _id , callback ) {
return this . _legacy _load _room ( room _id , callback ) ;
}
FFZ . prototype . _load _room _json = function ( room _id , callback , data ) {
// Preserve the pointer to the Room instance.
if ( this . rooms [ room _id ] )
data . room = this . rooms [ room _id ] . room ;
this . rooms [ room _id ] = data ;
if ( data . css || data . moderator _badge )
utils . update _css ( this . _room _style , room _id , moderator _css ( data ) + ( data . css || "" ) ) ;
for ( var i = 0 ; i < data . sets . length ; i ++ ) {
var set _id = data . sets [ i ] ;
if ( ! this . emote _sets . hasOwnProperty ( set _id ) )
this . load _set ( set _id ) ;
}
this . update _ui _link ( ) ;
if ( callback )
callback ( true , data ) ;
}
// --------------------
// Ember Modifications
// --------------------
FFZ . prototype . _modify _room = function ( room ) {
var f = this ;
room . reopen ( {
2015-01-20 20:25:26 -05:00
// Track which rooms the user is currently in.
2015-01-20 01:53:18 -05:00
init : function ( ) {
this . _super ( ) ;
f . add _room ( this . id , this ) ;
} ,
willDestroy : function ( ) {
this . _super ( ) ;
f . remove _room ( this . id ) ;
} ,
2015-01-20 20:25:26 -05:00
getSuggestions : function ( ) {
// This returns auto-complete suggestions for use in chat. We want
// to apply our capitalizations here. Overriding the
// filteredSuggestions property of the chat-input component would
// be even better, but I was already hooking the room model.
var suggestions = this . _super ( ) ;
2015-02-08 02:01:09 -05:00
if ( this . settings . capitalize )
2015-01-20 20:25:26 -05:00
suggestions = _ . map ( suggestions , FFZ . get _capitalization ) ;
return suggestions ;
} ,
2015-01-20 01:53:18 -05:00
send : function ( text ) {
var cmd = text . split ( ' ' , 1 ) [ 0 ] . toLowerCase ( ) ;
if ( cmd === "/ffz" ) {
this . set ( "messageToSend" , "" ) ;
f . run _command ( text . substr ( 5 ) , this . get ( 'id' ) ) ;
} else
return this . _super ( text ) ;
}
} ) ;
}
// --------------------
// Legacy Data Support
// --------------------
FFZ . prototype . _legacy _add _room = function ( room _id , callback , tries ) {
jQuery . ajax ( constants . SERVER + "channel/" + room _id + ".css" , { cache : false , context : this } )
. done ( function ( data ) {
this . _legacy _load _room _css ( room _id , callback , data ) ;
} ) . fail ( function ( data ) {
if ( data . status == 404 )
return this . _legacy _load _room _css ( room _id , callback , null ) ;
tries = tries || 0 ;
tries ++ ;
if ( tries < 10 )
return this . _legacy _add _room ( room _id , callback , tries ) ;
} ) ;
}
FFZ . prototype . _legacy _load _room _css = function ( room _id , callback , data ) {
var set _id = room _id ,
match = set _id . match ( GROUP _CHAT ) ;
if ( match && match [ 1 ] )
set _id = match [ 1 ] ;
var output = { id : room _id , menu _sets : [ set _id ] , sets : [ set _id ] , moderator _badge : null , css : null } ;
if ( data )
data = data . replace ( CSS , "" ) . trim ( ) ;
if ( data ) {
data = data . replace ( MOD _CSS , function ( match , url ) {
if ( output . moderator _badge || url . substr ( - 11 ) !== 'modicon.png' )
return match ;
output . moderator _badge = url ;
return "" ;
} ) ;
}
output . css = data || null ;
return this . _load _room _json ( room _id , callback , output ) ;
2014-03-27 22:44:11 -04:00
}
2015-02-08 02:01:09 -05:00
} , { "../constants" : 3 , "../utils" : 25 } ] , 8 : [ function ( require , module , exports ) {
2015-01-16 17:45:37 -05:00
var FFZ = window . FrankerFaceZ ;
// --------------------
// Initialization
// --------------------
FFZ . prototype . setup _router = function ( ) {
this . log ( "Hooking the Ember router." ) ;
var f = this ;
App . _ _container _ _ . lookup ( 'router:main' ) . reopen ( {
ffzTransition : function ( ) {
f . track _page ( ) ;
} . on ( 'didTransition' )
} ) ;
}
2015-01-19 15:27:10 -05:00
} , { } ] , 9 : [ function ( require , module , exports ) {
2015-01-15 16:42:21 -05:00
var FFZ = window . FrankerFaceZ ;
// --------------------
// Initialization
// --------------------
FFZ . prototype . setup _viewers = function ( ) {
this . log ( "Hooking the Ember Viewers controller." ) ;
var Viewers = App . _ _container _ _ . resolve ( 'controller:viewers' ) ;
this . _modify _viewers ( Viewers ) ;
}
FFZ . prototype . _modify _viewers = function ( controller ) {
var f = this ;
controller . reopen ( {
lines : function ( ) {
var viewers = this . _super ( ) ,
categories = [ ] ,
data = { } ,
last _category = null ;
// Get the broadcaster name.
var Channel = App . _ _container _ _ . lookup ( 'controller:channel' ) ,
room _id = this . get ( 'parentController.model.id' ) ,
broadcaster = Channel && Channel . get ( 'id' ) ;
// We can get capitalization for the broadcaster from the channel.
if ( broadcaster ) {
var display _name = Channel . get ( 'display_name' ) ;
if ( display _name )
FFZ . capitalization [ broadcaster ] = [ display _name , Date . now ( ) ] ;
}
// If the current room isn't the channel's chat, then we shouldn't
// display them as the broadcaster.
if ( room _id != broadcaster )
broadcaster = null ;
// Now, break the viewer array down into something we can use.
for ( var i = 0 ; i < viewers . length ; i ++ ) {
var entry = viewers [ i ] ;
if ( entry . category ) {
last _category = entry . category ;
categories . push ( last _category ) ;
data [ last _category ] = [ ] ;
2015-01-20 01:53:18 -05:00
} else {
var viewer = entry . chatter . toLowerCase ( ) ;
if ( ! viewer )
continue ;
// If the viewer is the broadcaster, give them their own
// group. Don't put them with normal mods!
if ( viewer == broadcaster ) {
categories . unshift ( "Broadcaster" ) ;
data [ "Broadcaster" ] = [ viewer ] ;
} else if ( data . hasOwnProperty ( last _category ) )
data [ last _category ] . push ( viewer ) ;
}
}
// Now, rebuild the viewer list. However, we're going to actually
// sort it this time.
viewers = [ ] ;
for ( var i = 0 ; i < categories . length ; i ++ ) {
var category = categories [ i ] ,
chatters = data [ category ] ;
if ( ! chatters || ! chatters . length )
continue ;
viewers . push ( { category : category } ) ;
viewers . push ( { chatter : "" } ) ;
// Push the chatters, capitalizing them as we go.
chatters . sort ( ) ;
while ( chatters . length ) {
var viewer = chatters . shift ( ) ;
2015-02-08 02:01:09 -05:00
viewer = FFZ . get _capitalization ( viewer ) ;
2015-01-20 01:53:18 -05:00
viewers . push ( { chatter : viewer } ) ;
}
}
return viewers ;
} . property ( "content.chatters" )
} ) ;
}
} , { } ] , 10 : [ function ( require , module , exports ) {
var FFZ = window . FrankerFaceZ ,
CSS = /\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)px;\s*?margin:([^;}]+);?([^}]*)\}/mg ,
MOD _CSS = /[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/ ,
constants = require ( './constants' ) ,
utils = require ( './utils' ) ,
loaded _global = function ( set _id , success , data ) {
if ( ! success )
return ;
data . global = true ;
this . global _sets . push ( set _id ) ;
} ,
check _margins = function ( margins , height ) {
var mlist = margins . split ( / +/ ) ;
if ( mlist . length != 2 )
return margins ;
mlist [ 0 ] = parseFloat ( mlist [ 0 ] ) ;
mlist [ 1 ] = parseFloat ( mlist [ 1 ] ) ;
if ( mlist [ 0 ] == ( height - 18 ) / - 2 && mlist [ 1 ] == 0 )
return null ;
return margins ;
} ,
build _legacy _css = function ( emote ) {
var margin = emote . margins ;
if ( ! margin )
margin = ( ( emote . height - 18 ) / - 2 ) + "px 0" ;
return ".ffz-emote-" + emote . id + ' { background-image: url("' + emote . url + '"); height: ' + emote . height + "px; width: " + emote . width + "px; margin: " + margin + ( emote . extra _css ? "; " + emote . extra _css : "" ) + "}\n" ;
} ,
build _new _css = function ( emote ) {
if ( ! emote . margins && ! emote . extra _css )
return build _legacy _css ( emote ) ;
return build _legacy _css ( emote ) + 'img[src="' + emote . url + '"] { ' + ( emote . margins ? "margin: " + emote . margins + ";" : "" ) + ( emote . extra _css || "" ) + " }\n" ;
} ,
build _css = build _new _css ;
// ---------------------
// Initialization
// ---------------------
FFZ . prototype . setup _emoticons = function ( ) {
this . log ( "Preparing emoticon system." ) ;
this . emote _sets = { } ;
this . global _sets = [ ] ;
this . _last _emote _id = 0 ;
this . log ( "Creating emoticon style element." ) ;
var s = this . _emote _style = document . createElement ( 'style' ) ;
s . id = "ffz-emoticon-css" ;
document . head . appendChild ( s ) ;
this . log ( "Loading global emote set." ) ;
this . load _set ( "global" , loaded _global . bind ( this , "global" ) ) ;
}
// ---------------------
// Set Management
// ---------------------
FFZ . prototype . getEmotes = function ( user _id , room _id ) {
var user = this . users [ user _id ] ,
room = this . rooms [ room _id ] ;
return _ . union ( user && user . sets || [ ] , room && room . sets || [ ] , this . global _sets ) ;
}
// ---------------------
// Commands
// ---------------------
FFZ . ws _commands . reload _set = function ( set _id ) {
this . load _set ( set _id ) ;
}
// ---------------------
// Set Loading
// ---------------------
FFZ . prototype . load _set = function ( set _id , callback ) {
return this . _legacy _load _set ( set _id , callback ) ;
}
FFZ . prototype . unload _set = function ( set _id ) {
var set = this . emote _sets [ set _id ] ;
if ( ! set )
return ;
this . log ( "Unloading emoticons for set: " + set _id ) ;
utils . update _css ( this . _emote _style , set _id , null ) ;
delete this . emote _sets [ set _id ] ;
for ( var i = 0 ; i < set . users . length ; i ++ ) {
var room = this . rooms [ set . users [ i ] ] ;
if ( room )
room . sets . removeObject ( set _id ) ;
}
}
FFZ . prototype . _load _set _json = function ( set _id , callback , data ) {
// Store our set.
this . emote _sets [ set _id ] = data ;
data . users = [ ] ;
data . global = false ;
data . count = 0 ;
// Iterate through all the emoticons, building CSS and regex objects as appropriate.
var output _css = "" ;
for ( var key in data . emotes ) {
if ( ! data . emotes . hasOwnProperty ( key ) )
continue ;
var emote = data . emotes [ key ] ;
emote . klass = "ffz-emote-" + emote . id ;
if ( emote . name [ emote . name . length - 1 ] === "!" )
emote . regex = new RegExp ( "\\b" + emote . name + "(?=\\W|$)" , "g" ) ;
else
emote . regex = new RegExp ( "\\b" + emote . name + "\\b" , "g" ) ;
output _css += build _css ( emote ) ;
data . count ++ ;
}
2015-01-15 16:42:21 -05:00
2015-01-20 01:53:18 -05:00
utils . update _css ( this . _emote _style , set _id , output _css + ( data . extra _css || "" ) ) ;
this . log ( "Updated emoticons for set: " + set _id , data ) ;
this . update _ui _link ( ) ;
2015-01-15 16:42:21 -05:00
2015-01-20 01:53:18 -05:00
if ( callback )
callback ( true , data ) ;
}
2015-01-15 16:42:21 -05:00
2015-01-20 01:53:18 -05:00
FFZ . prototype . _legacy _load _set = function ( set _id , callback , tries ) {
jQuery . ajax ( constants . SERVER + "channel/" + set _id + ".css" , { cache : false , context : this } )
. done ( function ( data ) {
this . _legacy _load _css ( set _id , callback , data ) ;
2015-01-15 16:42:21 -05:00
2015-01-20 01:53:18 -05:00
} ) . fail ( function ( data ) {
if ( data . status == 404 )
2015-01-20 20:25:26 -05:00
return typeof callback == "function" && callback ( false ) ;
2015-01-15 16:42:21 -05:00
2015-01-20 01:53:18 -05:00
tries = tries || 0 ;
tries ++ ;
if ( tries < 10 )
return this . _legacy _load _set ( set _id , callback , tries ) ;
2015-01-15 16:42:21 -05:00
2015-01-20 20:25:26 -05:00
return typeof callback == "function" && callback ( false ) ;
2015-01-20 01:53:18 -05:00
} ) ;
2015-01-15 16:42:21 -05:00
}
2015-01-20 01:53:18 -05:00
FFZ . prototype . _legacy _load _css = function ( set _id , callback , data ) {
var emotes = { } , output = { id : set _id , emotes : emotes , extra _css : null } , f = this ;
data = data . replace ( CSS , function ( match , klass , name , path , height , width , margins , extra ) {
height = parseInt ( height ) ; width = parseInt ( width ) ;
margins = check _margins ( margins , height ) ;
var hidden = path . substr ( path . lastIndexOf ( "/" ) + 1 , 1 ) === "." ,
id = ++ f . _last _emote _id ,
emote = { id : id , hidden : hidden , name : name , height : height , width : width , url : path , margins : margins , extra _css : extra } ;
emotes [ id ] = emote ;
return "" ;
} ) . trim ( ) ;
if ( data )
data . replace ( MOD _CSS , function ( match , url ) {
if ( output . icon || url . substr ( - 11 ) !== 'modicon.png' )
return ;
output . icon = url ;
} ) ;
this . _load _set _json ( set _id , callback , output ) ;
2014-03-27 22:44:11 -04:00
}
2015-02-08 02:01:09 -05:00
} , { "./constants" : 3 , "./utils" : 25 } ] , 11 : [ function ( require , module , exports ) {
2015-01-19 15:27:10 -05:00
var FFZ = window . FrankerFaceZ ,
SENDER _REGEX = /(\sdata-sender="[^"]*"(?=>))/ ;
// --------------------
// Initialization
// --------------------
FFZ . prototype . find _bttv = function ( increment , delay ) {
this . has _bttv = false ;
if ( window . BTTVLOADED )
return this . setup _bttv ( delay || 0 ) ;
if ( delay >= 60000 )
this . log ( "BetterTTV was not detected after 60 seconds." ) ;
else
setTimeout ( this . find _bttv . bind ( this , increment , ( delay || 0 ) + increment ) ,
increment ) ;
}
FFZ . prototype . setup _bttv = function ( delay ) {
this . log ( "BetterTTV was detected after " + delay + "ms. Hooking." ) ;
this . has _bttv = true ;
this . track ( 'setCustomVariable' , '3' , 'BetterTTV' , BetterTTV . info . versionString ( ) ) ;
2015-02-08 02:14:52 -05:00
// Disable Dark if it's enabled.
document . querySelector ( ".app-main" ) . classList . remove ( "ffz-dark" ) ;
if ( this . _dark _style ) {
this . _dark _style . parentElement . removeChild ( this . _dark _style ) ;
delete this . _dark _style ;
}
2015-01-19 15:27:10 -05:00
// Send Message Behavior
var original _send = BetterTTV . chat . helpers . sendMessage , f = this ;
BetterTTV . chat . helpers . sendMessage = function ( message ) {
var cmd = message . split ( ' ' , 1 ) [ 0 ] . toLowerCase ( ) ;
if ( cmd === "/ffz" )
f . run _command ( message . substr ( 5 ) , BetterTTV . chat . store . currentRoom ) ;
else
return original _send ( message ) ;
}
// Ugly Hack for Current Room
var original _handler = BetterTTV . chat . handlers . privmsg ,
received _room ;
BetterTTV . chat . handlers . privmsg = function ( room , data ) {
received _room = room ;
var output = original _handler ( room , data ) ;
received _room = null ;
return output ;
}
// Message Display Behavior
var original _privmsg = BetterTTV . chat . templates . privmsg ;
BetterTTV . chat . templates . privmsg = function ( highlight , action , server , isMod , data ) {
// Handle badges.
f . bttv _badges ( data ) ;
var output = original _privmsg ( highlight , action , server , isMod , data ) ;
return output . replace ( SENDER _REGEX , '$1 data-room="' + received _room + '"' ) ;
}
// Ugly Hack for Current Sender
var original _template = BetterTTV . chat . templates . message ,
received _sender ;
BetterTTV . chat . templates . message = function ( sender , message , emotes , colored ) {
received _sender = sender ;
var output = original _template ( sender , message , emotes , colored ) ;
received _sender = null ;
return output ;
}
// Emoticonize
var original _emoticonize = BetterTTV . chat . templates . emoticonize ;
BetterTTV . chat . templates . emoticonize = function ( message , emotes ) {
var tokens = original _emoticonize ( message , emotes ) ,
sets = f . getEmotes ( received _sender , received _room ) ,
emotes = [ ] ;
// Build a list of emotes that match.
_ . each ( sets , function ( set _id ) {
var set = f . emote _sets [ set _id ] ;
if ( ! set )
return ;
_ . each ( set . emotes , function ( emote ) {
_ . any ( tokens , function ( token ) {
return _ . isString ( token ) && token . match ( emote . regex ) ;
} ) && emotes . push ( emote ) ;
} ) ;
} ) ;
// Don't bother proceeding if we have no emotes.
if ( ! emotes . length )
return tokens ;
// Why is emote parsing so bad? ;_;
_ . each ( emotes , function ( emote ) {
var eo = [ '<img class="emoticon" src="' + emote . url + ( emote . hidden ? "" : '" alt="' + emote . name + '" title="' + emote . name ) + '" />' ] ,
old _tokens = tokens ;
tokens = [ ] ;
if ( ! old _tokens || ! old _tokens . length )
return tokens ;
for ( var i = 0 ; i < old _tokens . length ; i ++ ) {
var token = old _tokens [ i ] ;
if ( typeof token != "string" ) {
tokens . push ( token ) ;
continue ;
}
var tbits = token . split ( emote . regex ) ;
tbits . forEach ( function ( val , ind ) {
if ( val && val . length )
tokens . push ( val ) ;
if ( ind !== tbits . length - 1 )
tokens . push ( eo ) ;
} ) ;
}
} ) ;
return tokens ;
}
this . update _ui _link ( ) ;
}
} , { } ] , 12 : [ function ( require , module , exports ) {
var FFZ = window . FrankerFaceZ ;
// --------------------
// Initialization
// --------------------
FFZ . prototype . find _emote _menu = function ( increment , delay ) {
this . has _emote _menu = false ;
if ( window . emoteMenu && emoteMenu . registerEmoteGetter )
return this . setup _emote _menu ( delay || 0 ) ;
if ( delay >= 60000 )
this . log ( "Emote Menu for Twitch was not detected after 60 seconds." ) ;
else
setTimeout ( this . find _emote _menu . bind ( this , increment , ( delay || 0 ) + increment ) ,
increment ) ;
}
FFZ . prototype . setup _emote _menu = function ( delay ) {
this . log ( "Emote Menu for Twitch was detected after " + delay + "ms. Registering emote enumerator." ) ;
emoteMenu . registerEmoteGetter ( "FrankerFaceZ" , this . _emote _menu _enumerator . bind ( this ) ) ;
}
// --------------------
// Emote Enumerator
// --------------------
FFZ . prototype . _emote _menu _enumerator = function ( ) {
var twitch _user = this . get _user ( ) ,
user _id = twitch _user ? twitch _user . login : null ,
controller = App . _ _container _ _ . lookup ( 'controller:chat' ) ,
room _id = controller ? controller . get ( 'currentRoom.id' ) : null ,
sets = this . getEmotes ( user _id , room _id ) ,
emotes = [ ] ;
for ( var x = 0 ; x < sets . length ; x ++ ) {
var set = this . emote _sets [ sets [ x ] ] ;
if ( ! set || ! set . emotes )
continue ;
for ( var emote _id in set . emotes ) {
if ( ! set . emotes . hasOwnProperty ( emote _id ) )
continue ;
var emote = set . emotes [ emote _id ] ;
if ( emote . hidden )
continue ;
// TODO: Stop having to calculate this here.
var title = set . title , badge = set . icon || null ;
if ( ! title ) {
if ( set . id == "global" )
title = "FrankerFaceZ Global Emotes" ;
else if ( set . id == "globalevent" )
title = "FrankerFaceZ Event Emotes" ;
else if ( this . feature _friday && set . id == this . feature _friday . set )
title = "FrankerFaceZ Feature Friday: " + this . feature _friday . channel ;
else
title = "FrankerFaceZ Set: " + FFZ . get _capitalization ( set . id ) ;
}
emotes . push ( { text : emote . name , url : emote . url ,
hidden : false , channel : title , badge : badge } ) ;
}
}
return emotes ;
}
} , { } ] , 13 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
// Modify Array and others.
require ( './shims' ) ;
// ----------------
// The Constructor
// ----------------
var FFZ = window . FrankerFaceZ = function ( ) {
FFZ . instance = this ;
// Get things started.
this . initialize ( ) ;
}
FFZ . get = function ( ) { return FFZ . instance ; }
// Version
var VER = FFZ . version _info = {
major : 3 , minor : 0 , revision : 0 ,
toString : function ( ) {
return [ VER . major , VER . minor , VER . revision ] . join ( "." ) + ( VER . extra || "" ) ;
}
}
// Logging
FFZ . prototype . log = function ( msg , data , to _json ) {
msg = "FFZ: " + msg + ( to _json ? " -- " + JSON . stringify ( data ) : "" ) ;
if ( data !== undefined && console . groupCollapsed && console . dir ) {
console . groupCollapsed ( msg ) ;
if ( navigator . userAgent . indexOf ( "Firefox/" ) !== - 1 )
console . log ( data ) ;
else
console . dir ( data ) ;
console . groupEnd ( msg ) ;
} else
console . log ( msg ) ;
}
// -------------------
// User Data
// -------------------
FFZ . prototype . get _user = function ( ) {
if ( window . PP && PP . login ) {
return PP ;
} else if ( window . App ) {
var nc = App . _ _container _ _ . lookup ( "controller:navigation" ) ;
return nc ? nc . get ( "userData" ) : undefined ;
}
}
// -------------------
// Import Everything!
// -------------------
2015-02-08 02:01:09 -05:00
//require('./templates');
require ( './settings' ) ;
2015-01-20 01:53:18 -05:00
require ( './socket' ) ;
require ( './emoticons' ) ;
require ( './badges' ) ;
require ( './ember/router' ) ;
require ( './ember/room' ) ;
require ( './ember/line' ) ;
require ( './ember/chatview' ) ;
require ( './ember/viewers' ) ;
//require('./ember/teams');
require ( './tracking' ) ;
require ( './debug' ) ;
require ( './ext/betterttv' ) ;
require ( './ext/emote_menu' ) ;
require ( './featurefriday' ) ;
require ( './ui/styles' ) ;
2015-02-08 02:01:09 -05:00
//require('./ui/dark');
2015-01-20 01:53:18 -05:00
require ( './ui/notifications' ) ;
require ( './ui/viewer_count' ) ;
require ( './ui/menu_button' ) ;
require ( './ui/menu' ) ;
2015-02-08 02:01:09 -05:00
require ( './ui/races' ) ;
2015-01-20 01:53:18 -05:00
require ( './commands' ) ;
// ---------------
// Initialization
// ---------------
FFZ . prototype . initialize = function ( increment , delay ) {
// Make sure that FrankerFaceZ doesn't start setting itself up until the
// Twitch ember application is ready.
// TODO: Special Dashboard check.
var loaded = window . App != undefined &&
App . _ _container _ _ != undefined &&
App . _ _container _ _ . resolve ( 'model:room' ) != undefined ;
if ( ! loaded ) {
increment = increment || 10 ;
if ( delay >= 60000 )
this . log ( "Twitch application not detected in \"" + location . toString ( ) + "\". Aborting." ) ;
else
setTimeout ( this . initialize . bind ( this , increment , ( delay || 0 ) + increment ) ,
increment ) ;
return ;
}
this . setup _ember ( delay ) ;
}
FFZ . prototype . setup _ember = function ( delay ) {
var start = ( window . performance && performance . now ) ? performance . now ( ) : Date . now ( ) ;
this . log ( "Found Twitch application after " + ( delay || 0 ) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ . version _info ) ;
this . users = { } ;
// Initialize all the modules.
2015-02-08 02:01:09 -05:00
this . load _settings ( ) ;
// Start this early, for quick loading.
//this.setup_dark();
2015-01-20 01:53:18 -05:00
this . ws _create ( ) ;
this . setup _emoticons ( ) ;
this . setup _badges ( ) ;
this . setup _piwik ( ) ;
this . setup _router ( ) ;
this . setup _room ( ) ;
this . setup _line ( ) ;
this . setup _chatview ( ) ;
this . setup _viewers ( ) ;
//this.setup_teams();
2015-02-08 02:01:09 -05:00
this . setup _notifications ( ) ;
2015-01-20 01:53:18 -05:00
this . setup _css ( ) ;
this . setup _menu ( ) ;
2015-02-08 02:01:09 -05:00
this . setup _races ( ) ;
2015-01-20 01:53:18 -05:00
this . find _bttv ( 10 ) ;
this . find _emote _menu ( 10 ) ;
this . check _ff ( ) ;
var end = ( window . performance && performance . now ) ? performance . now ( ) : Date . now ( ) ,
duration = end - start ;
this . log ( "Initialization complete in " + duration + "ms" ) ;
2014-03-27 22:44:11 -04:00
}
2015-02-08 02:01:09 -05:00
} , { "./badges" : 1 , "./commands" : 2 , "./debug" : 4 , "./ember/chatview" : 5 , "./ember/line" : 6 , "./ember/room" : 7 , "./ember/router" : 8 , "./ember/viewers" : 9 , "./emoticons" : 10 , "./ext/betterttv" : 11 , "./ext/emote_menu" : 12 , "./featurefriday" : 14 , "./settings" : 15 , "./shims" : 16 , "./socket" : 17 , "./tracking" : 18 , "./ui/menu" : 19 , "./ui/menu_button" : 20 , "./ui/notifications" : 21 , "./ui/races" : 22 , "./ui/styles" : 23 , "./ui/viewer_count" : 24 } ] , 14 : [ function ( require , module , exports ) {
2015-01-15 22:19:05 -05:00
var FFZ = window . FrankerFaceZ ,
constants = require ( './constants' ) ;
// --------------------
// Initialization
// --------------------
FFZ . prototype . feature _friday = null ;
// --------------------
// Check FF
// --------------------
FFZ . prototype . check _ff = function ( tries ) {
if ( ! tries )
this . log ( "Checking for Feature Friday data..." ) ;
2015-01-16 17:45:37 -05:00
jQuery . ajax ( constants . SERVER + "script/event.json" , { cache : false , dataType : "json" , context : this } )
2015-01-15 22:19:05 -05:00
. done ( function ( data ) {
2015-01-16 17:45:37 -05:00
return this . _load _ff ( data ) ;
2015-01-15 22:19:05 -05:00
} ) . fail ( function ( data ) {
if ( data . status == 404 )
2015-01-16 17:45:37 -05:00
return this . _load _ff ( null ) ;
2015-01-15 22:19:05 -05:00
tries = tries || 0 ;
tries ++ ;
if ( tries < 10 )
2015-01-16 17:45:37 -05:00
return setTimeout ( this . check _ff . bind ( this , tries ) , 250 ) ;
2015-01-15 22:19:05 -05:00
2015-01-16 17:45:37 -05:00
return this . _load _ff ( null ) ;
2015-01-15 22:19:05 -05:00
} ) ;
}
FFZ . ws _commands . reload _ff = function ( ) {
this . check _ff ( ) ;
}
// --------------------
// Rendering UI
// --------------------
FFZ . prototype . _feature _friday _ui = function ( room _id , parent , view ) {
if ( ! this . feature _friday || this . feature _friday . channel == room _id )
return ;
this . _emotes _for _sets ( parent , view , [ this . feature _friday . set ] , "Feature Friday" ) ;
// Before we add the button, make sure the channel isn't the
// current channel.
var Channel = App . _ _container _ _ . lookup ( 'controller:channel' ) ;
if ( Channel && Channel . get ( 'id' ) == this . feature _friday . channel )
return ;
2015-01-16 17:45:37 -05:00
var ff = this . feature _friday , f = this ,
2015-01-15 22:19:05 -05:00
btnc = document . createElement ( 'div' ) ,
btn = document . createElement ( 'a' ) ;
btnc . className = 'chat-menu-content' ;
btnc . style . textAlign = 'center' ;
var message = ff . display _name + ( ff . live ? " is live now!" : "" ) ;
btn . className = 'button primary' ;
btn . classList . toggle ( 'live' , ff . live ) ;
btn . classList . toggle ( 'blue' , this . has _bttv && BetterTTV . settings . get ( 'showBlueButtons' ) ) ;
btn . href = "http://www.twitch.tv/" + ff . channel ;
btn . title = message ;
btn . target = "_new" ;
btn . innerHTML = "<span>" + message + "</span>" ;
2015-01-16 17:45:37 -05:00
// Track the number of users to click this button.
btn . addEventListener ( 'click' , function ( ) { f . track ( 'trackLink' , this . href , 'link' ) ; } ) ;
2015-01-15 22:19:05 -05:00
btnc . appendChild ( btn ) ;
parent . appendChild ( btnc ) ;
}
// --------------------
// Loading Data
// --------------------
FFZ . prototype . _load _ff = function ( data ) {
// Check for previous Feature Friday data and remove it.
if ( this . feature _friday ) {
// Remove the global set, delete the data, and reset the UI link.
this . global _sets . removeObject ( this . feature _friday . set ) ;
var set = this . emote _sets [ this . feature _friday . set ] ;
if ( set )
set . global = false ;
this . feature _friday = null ;
this . update _ui _link ( ) ;
}
// If there's no data, just leave.
if ( ! data || ! data . set || ! data . channel )
return ;
// We have our data! Set it up.
this . feature _friday = { set : data . set , channel : data . channel , live : false ,
display _name : FFZ . get _capitalization ( data . channel , this . _update _ff _name . bind ( this ) ) } ;
// Add the set.
this . global _sets . push ( data . set ) ;
this . load _set ( data . set , this . _update _ff _set . bind ( this ) ) ;
// Check to see if the channel is live.
this . _update _ff _live ( ) ;
}
FFZ . prototype . _update _ff _live = function ( ) {
if ( ! this . feature _friday )
return ;
var f = this ;
Twitch . api . get ( "streams/" + this . feature _friday . channel )
. done ( function ( data ) {
f . feature _friday . live = data . stream != null ;
f . update _ui _link ( ) ;
} )
. always ( function ( ) {
f . feature _friday . timer = setTimeout ( f . _update _ff _live . bind ( f ) , 120000 ) ;
} ) ;
}
FFZ . prototype . _update _ff _set = function ( success , set ) {
// Prevent the set from being unloaded.
if ( set )
set . global = true ;
}
FFZ . prototype . _update _ff _name = function ( name ) {
if ( this . feature _friday )
this . feature _friday . display _name = name ;
}
2015-01-19 15:27:10 -05:00
} , { "./constants" : 3 } ] , 15 : [ function ( require , module , exports ) {
2015-02-08 02:01:09 -05:00
var FFZ = window . FrankerFaceZ ,
make _ls = function ( key ) {
return "ffz_setting_" + key ;
} ;
// --------------------
// Initializer
// --------------------
FFZ . settings _info = { } ;
FFZ . prototype . load _settings = function ( ) {
this . log ( "Loading settings." ) ;
// Build a settings object.
this . settings = { } ;
for ( var key in FFZ . settings _info ) {
var ls _key = make _ls ( key ) ,
info = FFZ . settings _info [ key ] ,
val = info . hasOwnProperty ( "value" ) ? info . value : undefined ;
if ( localStorage . hasOwnProperty ( ls _key ) ) {
try {
val = JSON . parse ( localStorage . getItem ( ls _key ) ) ;
} catch ( err ) {
this . log ( 'Error loading value for "' + key + '": ' + err ) ;
}
}
this . settings [ key ] = val ;
}
// Helpers
this . settings . get = this . _setting _get . bind ( this ) ;
this . settings . set = this . _setting _set . bind ( this ) ;
this . settings . del = this . _setting _del . bind ( this ) ;
// Listen for Changes
window . addEventListener ( "storage" , this . _setting _update . bind ( this ) ) ;
}
// --------------------
// Tracking Updates
// --------------------
FFZ . prototype . _setting _update = function ( e ) {
if ( ! e )
e = window . event ;
this . log ( "Storage Event" , e ) ;
if ( ! e . key || e . key . substr ( 0 , 12 ) !== "ffz_setting_" )
return ;
var ls _key = e . key ,
key = ls _key . substr ( 12 ) ,
val = undefined ,
info = FFZ . settings _info [ key ] ;
this . log ( "Updated Setting: " + key ) ;
try {
val = JSON . parse ( e . newValue ) ;
} catch ( err ) {
this . log ( 'Error loading new value for "' + key + '": ' + err ) ;
val = info . value || undefined ;
}
this . settings [ key ] = val ;
if ( info . on _update )
try {
info . on _update . bind ( this ) ( val , false ) ;
} catch ( err ) {
this . log ( 'Error running updater for setting "' + key + '": ' + err ) ;
}
}
// --------------------
// Settings Access
// --------------------
FFZ . prototype . _setting _get = function ( key ) {
return this . settings [ key ] ;
}
FFZ . prototype . _setting _set = function ( key , val ) {
var ls _key = make _ls ( key ) ,
info = FFZ . settings _info [ key ] ,
jval = JSON . stringify ( val ) ;
this . settings [ key ] = val ;
localStorage . setItem ( ls _key , jval ) ;
this . log ( 'Changed Setting "' + key + '" to: ' + jval ) ;
if ( info . on _update )
try {
info . on _update . bind ( this ) ( val , true ) ;
} catch ( err ) {
this . log ( 'Error running updater for setting "' + key + '": ' + err ) ;
}
}
FFZ . prototype . _setting _del = function ( key ) {
var ls _key = make _ls ( key ) ,
info = FFZ . settings _info [ key ] ,
val = undefined ;
if ( localStorage . hasOwnProperty ( ls _key ) )
localStorage . removeItem ( ls _key ) ;
delete this . settings [ key ] ;
if ( info )
val = this . settings [ key ] = info . hasOwnProperty ( "value" ) ? info . value : undefined ;
if ( info . on _update )
try {
info . on _update . bind ( this ) ( val , true ) ;
} catch ( err ) {
this . log ( 'Error running updater for setting "' + key + '": ' + err ) ;
}
}
} , { } ] , 16 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
Array . prototype . equals = function ( array ) {
// if the other array is a falsy value, return
if ( ! array )
return false ;
// compare lengths - can save a lot of time
if ( this . length != array . length )
return false ;
for ( var i = 0 , l = this . length ; i < l ; i ++ ) {
// Check if we have nested arrays
if ( this [ i ] instanceof Array && array [ i ] instanceof Array ) {
// recurse into the nested arrays
if ( ! this [ i ] . equals ( array [ i ] ) )
return false ;
}
else if ( this [ i ] != array [ i ] ) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false ;
}
}
return true ;
}
2015-01-12 17:58:07 -05:00
2015-02-08 02:01:09 -05:00
} , { } ] , 17 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ;
FFZ . prototype . _ws _open = false ;
FFZ . prototype . _ws _delay = 0 ;
FFZ . ws _commands = { } ;
2015-02-08 02:01:09 -05:00
FFZ . ws _on _close = [ ] ;
2015-01-20 01:53:18 -05:00
// ----------------
// Socket Creation
// ----------------
FFZ . prototype . ws _create = function ( ) {
2015-01-27 17:05:51 -05:00
var f = this , ws ;
2015-01-20 01:53:18 -05:00
this . _ws _last _req = 0 ;
this . _ws _callbacks = { } ;
this . _ws _pending = this . _ws _pending || [ ] ;
2015-01-27 17:05:51 -05:00
try {
ws = this . _ws _sock = new WebSocket ( "ws://ffz.stendec.me/" ) ;
} catch ( err ) {
this . _ws _exists = false ;
return this . log ( "Error Creating WebSocket: " + err ) ;
}
this . _ws _exists = true ;
2015-01-20 01:53:18 -05:00
ws . onopen = function ( e ) {
f . _ws _open = true ;
f . _ws _delay = 0 ;
f . log ( "Socket connected." ) ;
var user = f . get _user ( ) ;
if ( user )
f . ws _send ( "setuser" , user . login ) ;
// Send the current rooms.
for ( var room _id in f . rooms )
f . ws _send ( "sub" , room _id ) ;
// Send any pending commands.
var pending = f . _ws _pending ;
f . _ws _pending = [ ] ;
for ( var i = 0 ; i < pending . length ; i ++ ) {
var d = pending [ i ] ;
f . ws _send ( d [ 0 ] , d [ 1 ] , d [ 2 ] ) ;
}
}
ws . onclose = function ( e ) {
f . log ( "Socket closed." ) ;
f . _ws _open = false ;
2015-02-08 02:01:09 -05:00
// When the connection closes, run our callbacks.
for ( var i = 0 ; i < FFZ . ws _on _close . length ; i ++ ) {
try {
FFZ . ws _on _close [ i ] . bind ( f ) ( ) ;
} catch ( err ) {
f . log ( "Error on Socket Close Callback: " + err ) ;
}
}
2015-01-20 01:53:18 -05:00
// We never ever want to not have a socket.
if ( f . _ws _delay < 30000 )
f . _ws _delay += 5000 ;
setTimeout ( f . ws _create . bind ( f ) , f . _ws _delay ) ;
}
ws . onmessage = function ( e ) {
// Messages are formatted as REQUEST_ID SUCCESS/FUNCTION_NAME[ JSON_DATA]
var cmd , data , ind = e . data . indexOf ( " " ) ,
msg = e . data . substr ( ind + 1 ) ,
request = parseInt ( e . data . slice ( 0 , ind ) ) ;
ind = msg . indexOf ( " " ) ;
if ( ind === - 1 )
ind = msg . length ;
cmd = msg . slice ( 0 , ind ) ;
msg = msg . substr ( ind + 1 ) ;
if ( msg )
data = JSON . parse ( msg ) ;
if ( request === - 1 ) {
// It's a command from the server.
var command = FFZ . ws _commands [ cmd ] ;
if ( command )
command . bind ( f ) ( data ) ;
else
f . log ( "Invalid command: " + cmd , data ) ;
} else {
var success = cmd === 'True' ,
callback = f . _ws _callbacks [ request ] ;
f . log ( "Socket Reply to " + request + " - " + ( success ? "SUCCESS" : "FAIL" ) , data ) ;
if ( callback ) {
delete f . _ws _callbacks [ request ] ;
callback ( success , data ) ;
}
}
}
}
FFZ . prototype . ws _send = function ( func , data , callback , can _wait ) {
if ( ! this . _ws _open ) {
if ( can _wait ) {
var pending = this . _ws _pending = this . _ws _pending || [ ] ;
pending . push ( [ func , data , callback ] ) ;
return true ;
} else
return false ;
}
var request = ++ this . _ws _last _req ;
data = data !== undefined ? " " + JSON . stringify ( data ) : "" ;
if ( callback )
this . _ws _callbacks [ request ] = callback ;
this . _ws _sock . send ( request + " " + func + data ) ;
return request ;
2014-03-27 22:44:11 -04:00
}
2015-02-08 02:01:09 -05:00
} , { } ] , 18 : [ function ( require , module , exports ) {
2015-01-16 17:45:37 -05:00
var FFZ = window . FrankerFaceZ ,
constants = require ( './constants' ) ,
PIWIK = ( "https:" == document . location . protocol ? 'https:' : 'http:' ) + '//sir.stendec.me/ffz_piwik/' ;
// --------------------
// Initialization
// --------------------
FFZ . prototype . setup _piwik = function ( ) {
if ( window . _paq != undefined ) {
this . log ( "Piwik is already present. Disabling analytics." ) ;
this . _tracking = false ;
return ;
}
if ( localStorage [ 'ffzTracking' ] == "false" ) {
this . log ( "The user has opted out of tracking. Disabling analytics." ) ;
this . _tracking = false ;
return ;
}
this . log ( "Initializing Piwik." ) ;
this . _tracking = true ;
var _paq = window . _paq = [ ] ;
_paq . push ( [ 'setSiteId' , 1 ] ) ;
_paq . push ( [ 'setTrackerUrl' , PIWIK + 'piwik.php' ] ) ;
if ( this . has _bttv )
_paq . push ( [ 'setCustomVariable' , '3' , 'BetterTTV' , BetterTTV . info . versionString ( ) ] ) ;
var user = this . get _user ( ) , f = this ;
if ( user ) {
_paq . push ( [ 'setCustomVariable' , '1' , 'Partnered' , user . is _partner ? "Yes" : "No" ] )
_paq . push ( [ 'setCustomVariable' , '2' , 'User Type' , user . is _staff ? "Staff" : ( user . is _admin ? "Admin" : "User" ) ] ) ;
_paq . push ( [ 'setUserId' , user . login ] ) ;
Twitch . api . get ( "channels/" + user . login )
. done ( function ( data ) {
if ( data . logo )
f . track ( 'setCustomVariable' , '4' , 'Avatar' , data . logo ) ;
} ) . always ( function ( ) { f . track _page ( ) ; } ) ;
} else
this . track _page ( ) ;
// If someone turned analytics back ON, track that.
if ( localStorage [ 'ffzTracking' ] == "true" ) {
this . track ( 'trackEvent' , 'Analytics' , 'Enable' ) ;
localStorage . removeItem ( 'ffzTracking' ) ;
}
var script = document . createElement ( 'script' ) ;
script . type = 'text/javascript' ;
script . defer = true ;
script . async = true ;
script . src = PIWIK + 'piwik.js' ;
document . head . appendChild ( script ) ;
}
// --------------------
// Command
// --------------------
FFZ . chat _commands . analytics = function ( room , args ) {
var enabled , args = args && args . length ? args [ 0 ] . toLowerCase ( ) : null ;
if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true ;
else if ( args == "n" || args == "no" || args == "false" || args == "off" )
enabled = false ;
if ( enabled === undefined )
return "Analytics are currently " + ( localStorage . ffzTracking != "false" ? "enabled." : "disabled." ) ;
// Track that someone turned off analytics.
if ( this . _tracking && ! enabled && localStorage . ffzTracking != "false" )
this . track ( 'trackEvent' , 'Analytics' , 'Disable' ) ;
localStorage . ffzTracking = enabled ;
return "Analytics are now " + ( enabled ? "enabled" : "disabled" ) + ". Please refresh your browser." ;
}
FFZ . chat _commands . analytics . help = "Usage: /ffz analytics <on|off>\nEnable or disable FrankerFaceZ analytics. We collect some data about your browser and how you use FrankerFaceZ to help us improve the script. Turn off analytics if you'd rather we not." ;
// --------------------
// Tracking Helpers
// --------------------
FFZ . prototype . track = function ( ) {
if ( ! this . _tracking )
return ;
window . _paq && _paq . push ( Array . prototype . slice . call ( arguments ) ) ;
}
FFZ . prototype . track _page = function ( ) {
if ( ! this . _tracking )
return ;
if ( this . _old _url )
this . track ( 'setReferrerUrl' , this . _old _url ) ;
this . _old _url = document . location . toString ( ) ;
this . track ( 'setCustomUrl' , this . _old _url ) ;
this . track ( 'deleteCustomVariable' , '1' , 'page' ) ;
this . track ( 'deleteCustomVariable' , '3' , 'page' ) ;
var routes = App . _ _container _ _ . resolve ( 'router:main' ) . router . currentHandlerInfos ;
if ( ! routes || routes . length == 0 )
return ;
var last = routes [ routes . length - 1 ] ;
if ( last . name == "channel.index" && last . context ) {
var following = last . context . get ( "isFollowing.isFollowing" ) ;
if ( following !== undefined && following !== null )
this . track ( 'setCustomVariable' , '1' , 'Following' , ( following ? "Yes" : "No" ) , 'page' ) ;
var game = last . context . get ( "game" ) ;
if ( game )
this . track ( "setCustomVariable" , "3" , "Game" , game , "page" ) ;
this . track ( "trackPageView" , document . title ) ;
}
}
2015-02-08 02:01:09 -05:00
} , { "./constants" : 3 } ] , 19 : [ function ( require , module , exports ) {
var FFZ = window . FrankerFaceZ ,
constants = require ( '../constants' ) ;
2015-01-20 01:53:18 -05:00
// --------------------
// Initializer
// --------------------
FFZ . prototype . setup _menu = function ( ) {
this . log ( "Installing mouse-up event to auto-close menus." ) ;
var f = this ;
jQuery ( document ) . mouseup ( function ( e ) {
var popup = f . _popup , parent ;
if ( ! popup ) return ;
popup = jQuery ( popup ) ;
parent = popup . parent ( ) ;
if ( ! parent . is ( e . target ) && parent . has ( e . target ) . length === 0 ) {
popup . remove ( ) ;
delete f . _popup ;
2015-02-08 02:01:09 -05:00
f . _popup _kill && f . _popup _kill ( ) ;
delete f . _popup _kill ;
2015-01-20 01:53:18 -05:00
}
} ) ;
}
2015-02-08 02:01:09 -05:00
FFZ . menu _pages = { } ;
2015-01-20 01:53:18 -05:00
// --------------------
// Create Menu
// --------------------
FFZ . prototype . build _ui _popup = function ( view ) {
var popup = this . _popup ;
if ( popup ) {
popup . parentElement . removeChild ( popup ) ;
delete this . _popup ;
2015-02-08 02:01:09 -05:00
this . _popup _kill && this . _popup _kill ( ) ;
delete this . _popup _kill ;
2015-01-20 01:53:18 -05:00
return ;
}
// Start building the DOM.
var container = document . createElement ( 'div' ) ,
2015-02-08 02:01:09 -05:00
inner = document . createElement ( 'div' ) ,
menu = document . createElement ( 'ul' ) ,
dark = ( this . has _bttv ? BetterTTV . settings . get ( 'darkenedMode' ) : false ) ;
2015-01-20 01:53:18 -05:00
container . className = 'emoticon-selector chat-menu ffz-ui-popup' ;
inner . className = 'emoticon-selector-box dropmenu' ;
container . appendChild ( inner ) ;
2015-02-08 02:01:09 -05:00
container . classList . toggle ( 'dark' , dark ) ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
// Render Menu
menu . className = 'menu clearfix' ;
inner . appendChild ( menu ) ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
var el = document . createElement ( 'li' ) ;
el . className = 'title' ;
el . innerHTML = "<span>FrankerFaceZ</span>" ;
menu . appendChild ( el ) ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
el . addEventListener ( "click" , this . _add _emote . bind ( this , view , "To use custom emoticons in tons of channels, get FrankerFaceZ from http://www.frankerfacez.com" ) ) ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
var sub _container = document . createElement ( 'div' ) ;
sub _container . className = 'ffz-ui-menu-page' ;
inner . appendChild ( sub _container ) ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
for ( var key in FFZ . menu _pages ) {
var page = FFZ . menu _pages [ key ] ;
if ( ! page || ( page . hasOwnProperty ( "visible" ) && ( ! page . visible || ( typeof page . visible == "function" && ! page . visible . bind ( this ) ( ) ) ) ) )
continue ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
var el = document . createElement ( 'li' ) ,
link = document . createElement ( 'a' ) ;
2015-01-27 17:05:51 -05:00
2015-02-08 02:01:09 -05:00
el . className = 'item' ;
el . id = "ffz-menu-page-" + key ;
link . title = page . name ;
link . innerHTML = page . icon ;
link . addEventListener ( "click" , this . _ui _change _page . bind ( this , view , menu , sub _container , key ) ) ;
el . appendChild ( link ) ;
menu . appendChild ( el ) ;
2015-01-27 17:05:51 -05:00
}
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
// Render Current Page
this . _ui _change _page ( view , menu , sub _container , this . _last _page || "channel" ) ;
2015-01-20 01:53:18 -05:00
// Add the menu to the DOM.
this . _popup = container ;
2015-02-08 02:01:09 -05:00
sub _container . style . maxHeight = Math . max ( 300 , view . $ ( ) . height ( ) - 212 ) + "px" ;
2015-01-20 01:53:18 -05:00
view . $ ( '.chat-interface' ) . append ( container ) ;
}
2015-02-08 02:01:09 -05:00
FFZ . prototype . _ui _change _page = function ( view , menu , container , page ) {
this . _last _page = page ;
container . innerHTML = "" ;
var els = menu . querySelectorAll ( 'li.active' ) ;
for ( var i = 0 ; i < els . length ; i ++ )
els [ i ] . classList . remove ( 'active' ) ;
var el = menu . querySelector ( '#ffz-menu-page-' + page ) ;
if ( el )
el . classList . add ( 'active' ) ;
else
this . log ( "No matching page: " + page ) ;
FFZ . menu _pages [ page ] . render . bind ( this ) ( view , container ) ;
}
// --------------------
// Settings Page
// --------------------
FFZ . menu _pages . settings = {
render : function ( view , container ) {
var menu = document . createElement ( 'div' ) ;
menu . className = 'chat-menu-content' ;
var settings = [ ] ;
for ( var key in FFZ . settings _info )
settings . push ( [ key , FFZ . settings _info [ key ] ] ) ;
settings . sort ( function ( a , b ) {
var ai = a [ 1 ] ,
bi = b [ 1 ] ,
an = ai . name . toLowerCase ( ) ,
bn = bi . name . toLowerCase ( ) ;
if ( an < bn ) return - 1 ;
else if ( an > bn ) return 1 ;
return 0 ;
} ) ;
for ( var i = 0 ; i < settings . length ; i ++ ) {
var key = settings [ i ] [ 0 ] ,
info = settings [ i ] [ 1 ] ,
el = document . createElement ( 'p' ) ,
val = this . settings . get ( key ) ;
if ( info . visible !== undefined && info . visible !== null ) {
var visible = info . visible ;
if ( typeof info . visible == "function" )
visible = info . visible . bind ( this ) ( ) ;
if ( ! visible )
continue ;
}
el . className = 'clearfix' ;
if ( info . type == "boolean" ) {
var swit = document . createElement ( 'a' ) ,
label = document . createElement ( 'span' ) ;
swit . className = 'switch' ;
swit . classList . toggle ( 'active' , val ) ;
swit . innerHTML = "<span></span>" ;
label . className = 'switch-label' ;
label . innerHTML = info . name ;
el . appendChild ( swit ) ;
el . appendChild ( label ) ;
swit . addEventListener ( "click" , this . _ui _toggle _setting . bind ( this , swit , key ) ) ;
} else {
el . classList . add ( "option" ) ;
var link = document . createElement ( 'a' ) ;
link . innerHTML = info . name ;
link . href = "#" ;
el . appendChild ( link ) ;
link . addEventListener ( "click" , info . method . bind ( this ) ) ;
}
if ( info . help ) {
var help = document . createElement ( 'span' ) ;
help . className = 'help' ;
help . innerHTML = info . help ;
el . appendChild ( help ) ;
}
menu . appendChild ( el ) ;
}
container . appendChild ( menu ) ;
} ,
name : "Settings" ,
icon : constants . GEAR
} ;
FFZ . prototype . _ui _toggle _setting = function ( swit , key ) {
var val = ! this . settings . get ( key ) ;
this . settings . set ( key , val ) ;
swit . classList . toggle ( 'active' , val ) ;
}
// --------------------
// Favorites Page
// --------------------
/ * F F Z . m e n u _ p a g e s . f a v o r i t e s = {
render : function ( view , container ) {
} ,
name : "Favorites" ,
icon : constants . HEART
} ; * /
// --------------------
// Channel Page
// --------------------
FFZ . menu _pages . channel = {
render : function ( view , inner ) {
// Get the current room.
var room _id = view . get ( 'controller.currentRoom.id' ) ,
room = this . rooms [ room _id ] ;
this . log ( "Menu for Room: " + room _id , room ) ;
this . track ( 'trackEvent' , 'Menu' , 'Open' , room _id ) ;
// Add the header and ad button.
/ * v a r b t n = d o c u m e n t . c r e a t e E l e m e n t ( ' a ' ) ;
btn . className = 'button glyph-only ffz-button' ;
btn . title = 'Advertise for FrankerFaceZ in chat!' ;
btn . href = '#' ;
btn . innerHTML = '<svg class="svg-followers" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M8,13.5L1.5,7V4l2-2h3L8,3.5L9.5,2h3l2,2v3L8,13.5z" fill-rule="evenodd"></path></svg>' ;
var hdr = document . createElement ( 'div' ) ;
hdr . className = 'list-header first' ;
hdr . appendChild ( btn ) ;
hdr . appendChild ( document . createTextNode ( 'FrankerFaceZ' ) ) ;
inner . appendChild ( hdr ) ; * /
var c = this . _emotes _for _sets ( inner , view , room && room . menu _sets || [ ] ) ;
/ * i f ( ! t h i s . _ w s _ e x i s t s ) {
btn . className = "button ffz-button primary" ;
btn . innerHTML = "Server Error" ;
btn . title = "FFZ Server Error" ;
btn . addEventListener ( 'click' , alert . bind ( window , "The FrankerFaceZ client was unable to create a WebSocket to communicate with the FrankerFaceZ server.\n\nThis is most likely due to your browser's configuration either disabling WebSockets entirely or limiting the number of simultaneous connections. Please ensure that WebSockets have not been disabled." ) ) ;
} else {
if ( c === 0 )
btn . addEventListener ( 'click' , this . _add _emote . bind ( this , view , "To use custom emoticons in tons of channels, get FrankerFaceZ from http://www.frankerfacez.com" ) ) ;
else
btn . addEventListener ( 'click' , this . _add _emote . bind ( this , view , "To view this channel's emoticons, get FrankerFaceZ from http://www.frankerfacez.com" ) ) ;
} * /
// Feature Friday!
this . _feature _friday _ui ( room _id , inner , view ) ;
} ,
name : "Channel" ,
icon : constants . ZREKNARF
} ;
2015-01-20 01:53:18 -05:00
// --------------------
// Emotes for Sets
// --------------------
FFZ . prototype . _emotes _for _sets = function ( parent , view , sets , header , btn ) {
if ( header != null ) {
var el _header = document . createElement ( 'div' ) ;
el _header . className = 'list-header' ;
el _header . appendChild ( document . createTextNode ( header ) ) ;
if ( btn )
el _header . appendChild ( btn ) ;
parent . appendChild ( el _header ) ;
}
var grid = document . createElement ( 'div' ) , c = 0 ;
grid . className = 'emoticon-grid' ;
for ( var i = 0 ; i < sets . length ; i ++ ) {
var set = this . emote _sets [ sets [ i ] ] ;
if ( ! set || ! set . emotes )
continue ;
for ( var eid in set . emotes ) {
var emote = set . emotes [ eid ] ;
if ( ! set . emotes . hasOwnProperty ( eid ) || emote . hidden )
continue ;
c ++ ;
var s = document . createElement ( 'span' ) ;
s . className = 'emoticon tooltip' ;
s . style . backgroundImage = 'url("' + emote . url + '")' ;
s . style . width = emote . width + "px" ;
s . style . height = emote . height + "px" ;
s . title = emote . name ;
s . addEventListener ( 'click' , this . _add _emote . bind ( this , view , emote . name ) ) ;
grid . appendChild ( s ) ;
}
}
if ( ! c ) {
grid . innerHTML = "This channel has no emoticons." ;
grid . className = "chat-menu-content ffz-no-emotes center" ;
}
parent . appendChild ( grid ) ;
}
FFZ . prototype . _add _emote = function ( view , emote ) {
var room = view . get ( 'controller.currentRoom' ) ,
current _text = room . get ( 'messageToSend' ) || '' ;
if ( current _text && current _text . substr ( - 1 ) !== " " )
current _text += ' ' ;
room . set ( 'messageToSend' , current _text + ( emote . name || emote ) ) ;
2014-03-27 22:44:11 -04:00
}
2015-02-08 02:01:09 -05:00
} , { "../constants" : 3 } ] , 20 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ,
constants = require ( '../constants' ) ;
// --------------------
// Initialization
// --------------------
FFZ . prototype . build _ui _link = function ( view ) {
var link = document . createElement ( 'a' ) ;
link . className = 'ffz-ui-toggle' ;
link . innerHTML = constants . CHAT _BUTTON ;
link . addEventListener ( 'click' , this . build _ui _popup . bind ( this , view ) ) ;
this . update _ui _link ( link ) ;
return link ;
}
FFZ . prototype . update _ui _link = function ( link ) {
var controller = App . _ _container _ _ . lookup ( 'controller:chat' ) ;
link = link || document . querySelector ( 'a.ffz-ui-toggle' ) ;
if ( ! link || ! controller )
return ;
var room _id = controller . get ( 'currentRoom.id' ) ,
room = this . rooms [ room _id ] ,
has _emotes = false ,
dark = ( this . has _bttv ? BetterTTV . settings . get ( 'darkenedMode' ) : false ) ,
blue = ( this . has _bttv ? BetterTTV . settings . get ( 'showBlueButtons' ) : false ) ,
live = ( this . feature _friday && this . feature _friday . live ) ;
// Check for emoticons.
if ( room && room . sets . length ) {
for ( var i = 0 ; i < room . sets . length ; i ++ ) {
var set = this . emote _sets [ room . sets [ i ] ] ;
if ( set && set . count > 0 ) {
has _emotes = true ;
break ;
}
}
}
link . classList . toggle ( 'no-emotes' , ! has _emotes ) ;
link . classList . toggle ( 'live' , live ) ;
link . classList . toggle ( 'dark' , dark ) ;
link . classList . toggle ( 'blue' , blue ) ;
2014-03-27 22:44:11 -04:00
}
2015-02-08 02:01:09 -05:00
} , { "../constants" : 3 } ] , 21 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ;
2015-02-08 02:01:09 -05:00
// ---------------------
// Initialization
// ---------------------
FFZ . prototype . setup _notifications = function ( ) {
this . log ( "Adding event handler for window focus." ) ;
window . addEventListener ( "focus" , this . clear _notifications . bind ( this ) ) ;
}
// ---------------------
// Settings
// ---------------------
FFZ . settings _info . highlight _notifications = {
type : "boolean" ,
value : false ,
visible : function ( ) { return ! this . has _bttv } ,
name : "Highlight Notifications" ,
help : "Display notifications when a highlighted word appears in chat in an unfocused tab." ,
on _update : function ( val , direct ) {
// Check to see if we have notification permission. If this is
// enabled, at least.
if ( ! val || ! direct )
return ;
if ( Notification . permission === "denied" ) {
this . log ( "Notifications have been denied by the user." ) ;
this . settings . set ( "highlight_notifications" , false ) ;
return ;
} else if ( Notification . permission === "granted" )
return ;
var f = this ;
Notification . requestPermission ( function ( e ) {
if ( e === "denied" ) {
f . log ( "Notifications have been denied by the user." ) ;
f . settings . set ( "highlight_notifications" , false ) ;
}
} ) ;
}
} ;
// ---------------------
// Socket Commands
// ---------------------
FFZ . ws _commands . message = function ( message ) {
this . show _mesage ( message ) ;
}
// ---------------------
// Notifications
// ---------------------
FFZ . _notifications = { } ;
FFZ . _last _notification = 0 ;
FFZ . prototype . clear _notifications = function ( ) {
for ( var k in FFZ . _notifications ) {
var n = FFZ . _notifications [ k ] ;
if ( n )
try {
n . close ( ) ;
} catch ( err ) { }
}
FFZ . _notifications = { } ;
FFZ . _last _notification = 0 ;
}
FFZ . prototype . show _notification = function ( message , title , tag , timeout , on _click , on _close ) {
var perm = Notification . permission ;
if ( perm === "denied " )
return false ;
if ( perm === "granted" ) {
title = title || "FrankerFaceZ" ;
timeout = timeout || 10000 ;
var options = {
lang : "en-US" ,
dir : "ltr" ,
body : message ,
tag : tag || "FrankerFaceZ" ,
icon : "http://cdn.frankerfacez.com/icon32.png"
} ;
var f = this ,
n = new Notification ( title , options ) ,
nid = FFZ . _last _notification ++ ;
FFZ . _notifications [ nid ] = n ;
n . addEventListener ( "click" , function ( ) {
delete FFZ . _notifications [ nid ] ;
if ( on _click )
on _click . bind ( f ) ( ) ;
} ) ;
n . addEventListener ( "close" , function ( ) {
delete FFZ . _notifications [ nid ] ;
if ( on _close )
on _close . bind ( f ) ( ) ;
} ) ;
if ( typeof timeout == "number" )
n . addEventListener ( "show" , function ( ) {
setTimeout ( function ( ) {
delete FFZ . _notifications [ nid ] ;
n . close ( ) ;
} , timeout ) ;
} ) ;
return ;
}
var f = this ;
Notification . requestPermission ( function ( e ) {
f . show _notification ( message , title , tag ) ;
} ) ;
}
// ---------------------
// Noty Notification
// ---------------------
FFZ . prototype . show _message = function ( message ) {
2015-01-20 01:53:18 -05:00
window . noty ( {
text : message ,
theme : "ffzTheme" ,
layout : "bottomCenter" ,
closeWith : [ "button" ]
} ) . show ( ) ;
}
2015-02-08 02:01:09 -05:00
} , { } ] , 22 : [ function ( require , module , exports ) {
var FFZ = window . FrankerFaceZ ,
utils = require ( '../utils' ) ;
2015-01-20 01:53:18 -05:00
2015-02-08 02:01:09 -05:00
// ---------------
// Initialization
// ---------------
FFZ . prototype . setup _races = function ( ) {
this . log ( "Initializing race support." ) ;
this . srl _races = { } ;
2014-03-27 22:44:11 -04:00
}
2015-02-08 02:01:09 -05:00
// ---------------
// Settings
// ---------------
FFZ . settings _info . srl _races = {
type : "boolean" ,
value : true ,
name : "SRL Race Information" ,
help : 'Display information about <a href="http://www.speedrunslive.com/" target="_new">SpeedRunsLive</a> races under channels.' ,
on _update : function ( val ) {
this . rebuild _race _ui ( ) ;
}
} ;
// ---------------
// Socket Handler
// ---------------
FFZ . ws _on _close . push ( function ( ) {
var controller = App . _ _container _ _ . lookup ( 'controller:channel' ) ,
current _id = controller . get ( 'id' ) ,
need _update = false ;
for ( var chan in this . srl _races ) {
delete this . srl _races [ chan ] ;
if ( chan == current _id )
need _update = true ;
}
if ( need _update )
this . rebuild _race _ui ( ) ;
} ) ;
FFZ . ws _commands . srl _race = function ( data ) {
var controller = App . _ _container _ _ . lookup ( 'controller:channel' ) ,
current _id = controller . get ( 'id' ) ,
need _update = false ;
for ( var i = 0 ; i < data [ 0 ] . length ; i ++ ) {
var channel _id = data [ 0 ] [ i ] ;
this . srl _races [ channel _id ] = data [ 1 ] ;
if ( channel _id == current _id )
need _update = true ;
}
if ( data [ 1 ] ) {
var race = data [ 1 ] ,
tte = race . twitch _entrants = { } ;
for ( var ent in race . entrants ) {
if ( ! race . entrants . hasOwnProperty ( ent ) ) continue ;
if ( race . entrants [ ent ] . channel )
tte [ race . entrants [ ent ] . channel ] = ent ;
race . entrants [ ent ] . name = ent ;
}
}
if ( need _update )
this . rebuild _race _ui ( ) ;
}
// ---------------
// Race UI
// ---------------
FFZ . prototype . rebuild _race _ui = function ( ) {
var controller = App . _ _container _ _ . lookup ( 'controller:channel' ) ,
channel _id = controller . get ( 'id' ) ,
race = this . srl _races [ channel _id ] ,
enable _ui = this . settings . srl _races ,
actions = document . querySelector ( '.stats-and-actions .channel-actions' ) ,
race _container = actions . querySelector ( '#ffz-ui-race' ) ;
if ( ! race || ! enable _ui ) {
if ( race _container )
race _container . parentElement . removeChild ( race _container ) ;
if ( this . _popup && this . _popup . id == "ffz-race-popup" ) {
delete this . _popup ;
this . _popup _kill && this . _popup _kill ( ) ;
delete this . _popup _kill ;
}
return ;
}
if ( race _container )
return this . _update _race ( true ) ;
race _container = document . createElement ( 'span' ) ;
race _container . setAttribute ( 'data-channel' , channel _id ) ;
race _container . id = 'ffz-ui-race' ;
var btn = document . createElement ( 'span' ) ;
btn . className = 'button drop action' ;
btn . title = "SpeedRunsLive Race" ;
btn . innerHTML = '<span class="logo"><span>' ;
btn . addEventListener ( 'click' , this . build _race _popup . bind ( this ) ) ;
race _container . appendChild ( btn ) ;
actions . appendChild ( race _container ) ;
this . _update _race ( true ) ;
}
// ---------------
// Race Popup
// ---------------
FFZ . prototype . _race _kill = function ( ) {
if ( this . _race _timer ) {
clearTimeout ( this . _race _timer ) ;
delete this . _race _timer ;
}
delete this . _race _game ;
delete this . _race _goal ;
}
FFZ . prototype . build _race _popup = function ( ) {
var popup = this . _popup ;
if ( popup ) {
popup . parentElement . removeChild ( popup ) ;
delete this . _popup ;
this . _popup _kill && this . _popup _kill ( ) ;
delete this . _popup _kill ;
if ( popup . id == "ffz-race-popup" )
return ;
}
var container = document . querySelector ( '#ffz-ui-race' ) ;
if ( ! container )
return ;
var el = container . querySelector ( '.button' ) ,
pos = el . offsetLeft + el . offsetWidth ,
channel _id = container . getAttribute ( 'data-channel' ) ,
race = this . srl _races [ channel _id ] ;
var popup = document . createElement ( 'div' ) , out = '' ;
popup . id = 'ffz-race-popup' ;
popup . className = ( pos >= 300 ? 'right' : 'left' ) + ' share dropmenu' ;
this . _popup _kill = this . _race _kill . bind ( this ) ;
this . _popup = popup ;
var link = 'http://kadgar.net/live' ,
has _entrant = false ;
for ( var ent in race . entrants ) {
var state = race . entrants [ ent ] . state ;
if ( race . entrants . hasOwnProperty ( ent ) && race . entrants [ ent ] . channel && ( state == "racing" || state == "entered" ) ) {
link += "/" + race . entrants [ ent ] . channel ;
has _entrant = true ;
}
}
var height = document . querySelector ( '.app-main.theatre' ) ? document . body . clientHeight - 300 : container . parentElement . offsetTop - 175 ,
controller = App . _ _container _ _ . lookup ( 'controller:channel' ) ,
display _name = controller ? controller . get ( 'display_name' ) : FFZ . get _capitalization ( channel _id ) ,
tweet = encodeURIComponent ( "I'm watching " + display _name + " race " + race . goal + " in " + race . game + " on SpeedRunsLive!" ) ;
out = '<div class="heading"><div></div><span></span></div>' ;
out += '<div class="table" style="max-height:' + height + 'px"><table><thead><tr><th>#</th><th>Entrant</th><th> </th><th>Time</th></tr></thead>' ;
out += '<tbody></tbody></table></div>' ;
out += '<div class="divider"></div>' ;
out += '<iframe class="twitter_share_button" style="width:110px; height:20px" src="https://platform.twitter.com/widgets/tweet_button.html?text=' + tweet + '%20Watch%20at&via=Twitch&url=http://www.twitch.tv/' + channel _id + '"></iframe>' ;
out += '<p class="right"><a target="_new" href="http://www.speedrunslive.com/race/?id=' + race . id + '">SRL</a>' ;
if ( has _entrant )
out += ' <a target="_new" href="' + link + '">Multitwitch</a>' ;
out += '</p>' ;
popup . innerHTML = out ;
container . appendChild ( popup ) ;
this . _update _race ( true ) ;
}
FFZ . prototype . _update _race = function ( not _timer ) {
if ( this . _race _timer && not _timer ) {
clearTimeout ( this . _race _timer ) ;
delete this . _race _timer ;
}
var container = document . querySelector ( '#ffz-ui-race' ) ;
if ( ! container )
return ;
var channel _id = container . getAttribute ( 'data-channel' ) ,
race = this . srl _races [ channel _id ] ;
if ( ! race ) {
// No race. Abort.
container . parentElement . removeChild ( container ) ;
this . _popup _kill && this . _popup _kill ( ) ;
if ( this . _popup ) {
delete this . _popup ;
delete this . _popup _kill ;
}
return ;
}
var entrant _id = race . twitch _entrants [ channel _id ] ,
entrant = race . entrants [ entrant _id ] ,
popup = container . querySelector ( '#ffz-race-popup' ) ,
now = Date . now ( ) / 1000 ,
elapsed = Math . floor ( now - race . time ) ;
container . querySelector ( '.logo' ) . innerHTML = utils . placement ( entrant ) ;
if ( popup ) {
var tbody = popup . querySelector ( 'tbody' ) ,
timer = popup . querySelector ( '.heading span' ) ,
info = popup . querySelector ( '.heading div' ) ;
tbody . innerHTML = '' ;
var entrants = [ ] , done = true ;
for ( var ent in race . entrants ) {
if ( ! race . entrants . hasOwnProperty ( ent ) ) continue ;
if ( race . entrants [ ent ] . state == "racing" )
done = false ;
entrants . push ( race . entrants [ ent ] ) ;
}
entrants . sort ( function ( a , b ) {
var a _place = a . place || 9999 ,
b _place = b . place || 9999 ,
a _time = a . time || elapsed ,
b _time = b . time || elapsed ;
if ( a . state == "forfeit" || a . state == "dq" )
a _place = 10000 ;
if ( b . state == "forfeit" || b . state == "dq" )
b _place = 10000 ;
if ( a _place < b _place ) return - 1 ;
else if ( a _place > b _place ) return 1 ;
else if ( a . name < b . name ) return - 1 ;
else if ( a . name > b . name ) return 1 ;
else if ( a _time < b _time ) return - 1 ;
else if ( a _time > b _time ) return 1 ;
} ) ;
for ( var i = 0 ; i < entrants . length ; i ++ ) {
var ent = entrants [ i ] ,
name = '<a target="_new" href="http://www.speedrunslive.com/profiles/#!/' + utils . sanitize ( ent . name ) + '">' + ent . display _name + '</a>' ,
twitch _link = ent . channel ? '<a target="_new" class="twitch" href="http://www.twitch.tv/' + utils . sanitize ( ent . channel ) + '"></a>' : '' ,
hitbox _link = ent . hitbox ? '<a target="_new" class="hitbox" href="http://www.hitbox.tv/' + utils . sanitize ( ent . hitbox ) + '"></a>' : '' ,
time = elapsed ? utils . time _to _string ( ent . time || elapsed ) : "" ,
place = utils . place _string ( ent . place ) ,
comment = ent . comment ? utils . sanitize ( ent . comment ) : "" ;
tbody . innerHTML += '<tr' + ( comment ? ' title="' + comment + '"' : '' ) + ' class="' + ent . state + '"><td>' + place + '</td><td>' + name + '</td><td>' + twitch _link + hitbox _link + '</td><td class="time">' + ( ent . state == "forfeit" ? "Forfeit" : time ) + '</td></tr>' ;
}
if ( this . _race _game != race . game || this . _race _goal != race . goal ) {
this . _race _game = race . game ;
this . _race _goal = race . goal ;
var game = utils . sanitize ( race . game ) ,
goal = utils . sanitize ( race . goal ) ;
info . innerHTML = '<h2 title="' + game + '">' + game + "</h2><b>Goal: </b>" + goal ;
}
if ( ! elapsed )
timer . innerHTML = "Entry Open" ;
else if ( done )
timer . innerHTML = "Done" ;
else {
timer . innerHTML = utils . time _to _string ( elapsed ) ;
this . _race _timer = setTimeout ( this . _update _race . bind ( this ) , 1000 ) ;
}
}
}
} , { "../utils" : 25 } ] , 23 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ,
constants = require ( '../constants' ) ;
FFZ . prototype . setup _css = function ( ) {
this . log ( "Injecting main FrankerFaceZ CSS." ) ;
var s = this . _main _style = document . createElement ( 'link' ) ;
s . id = "ffz-ui-css" ;
s . setAttribute ( 'rel' , 'stylesheet' ) ;
s . setAttribute ( 'href' , constants . SERVER + "script/style.css" ) ;
document . head . appendChild ( s ) ;
jQuery . noty . themes . ffzTheme = {
name : "ffzTheme" ,
style : function ( ) {
this . $bar . removeClass ( ) . addClass ( "noty_bar" ) . addClass ( "ffz-noty" ) . addClass ( this . options . type ) ;
} ,
callback : {
onShow : function ( ) { } ,
onClose : function ( ) { }
}
} ;
2014-03-27 22:44:11 -04:00
}
2015-02-08 02:01:09 -05:00
} , { "../constants" : 3 } ] , 24 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ,
constants = require ( '../constants' ) ,
utils = require ( '../utils' ) ;
// ------------
// Set Viewers
// ------------
FFZ . ws _commands . viewers = function ( data ) {
var channel = data [ 0 ] , count = data [ 1 ] ;
var controller = App . _ _container _ _ . lookup ( 'controller:channel' ) ,
id = controller && controller . get && controller . get ( 'id' ) ;
if ( id !== channel )
return ;
var view _count = document . querySelector ( '.channel-stats .ffz.stat' ) ,
content = constants . ZREKNARF + ' ' + utils . number _commas ( count ) ;
if ( view _count )
view _count . innerHTML = content ;
else {
var parent = document . querySelector ( '.channel-stats' ) ;
if ( ! parent )
return ;
view _count = document . createElement ( 'span' ) ;
view _count . className = 'ffz stat' ;
view _count . title = 'Viewers with FrankerFaceZ' ;
view _count . innerHTML = content ;
parent . appendChild ( view _count ) ;
jQuery ( view _count ) . tipsy ( ) ;
}
2014-03-27 22:44:11 -04:00
}
2015-02-08 02:01:09 -05:00
} , { "../constants" : 3 , "../utils" : 25 } ] , 25 : [ function ( require , module , exports ) {
2015-01-20 01:53:18 -05:00
var FFZ = window . FrankerFaceZ ,
constants = require ( './constants' ) ;
2015-02-08 02:01:09 -05:00
var sanitize _cache = { } ,
sanitize _el = document . createElement ( 'span' ) ,
place _string = function ( num ) {
if ( num == 1 ) return '1st' ;
else if ( num == 2 ) return '2nd' ;
else if ( num == 3 ) return '3rd' ;
else if ( num == null ) return '---' ;
return num + "th" ;
} ;
2015-01-20 01:53:18 -05:00
module . exports = {
update _css : function ( element , id , css ) {
var all = element . innerHTML ,
start = "/*BEGIN " + id + "*/" ,
end = "/*END " + id + "*/" ,
s _ind = all . indexOf ( start ) ,
e _ind = all . indexOf ( end ) ,
found = s _ind !== - 1 && e _ind !== - 1 && e _ind > s _ind ;
if ( ! found && ! css )
return ;
if ( found )
all = all . substr ( 0 , s _ind ) + all . substr ( e _ind + end . length ) ;
if ( css )
all += start + css + end ;
element . innerHTML = all ;
} ,
number _commas : function ( x ) {
var parts = x . toString ( ) . split ( "." ) ;
parts [ 0 ] = parts [ 0 ] . replace ( /\B(?=(\d{3})+(?!\d))/g , "," ) ;
return parts . join ( "." ) ;
2015-02-08 02:01:09 -05:00
} ,
place _string : place _string ,
placement : function ( entrant ) {
if ( entrant . state == "forfeit" ) return "Forfeit" ;
else if ( entrant . state == "dq" ) return "DQed" ;
else if ( entrant . place ) return place _string ( entrant . place ) ;
return "" ;
} ,
sanitize : function ( msg ) {
var m = sanitize _cache [ msg ] ;
if ( ! m ) {
sanitize _el . textContent = msg ;
m = sanitize _cache [ msg ] = sanitize _el . innerHTML ;
sanitize _el . innerHTML = "" ;
}
return m ;
} ,
time _to _string : function ( elapsed ) {
var seconds = elapsed % 60 ,
minutes = Math . floor ( elapsed / 60 ) ,
hours = Math . floor ( minutes / 60 ) ;
minutes = minutes % 60 ;
return ( hours < 10 ? "0" : "" ) + hours + ":" + ( minutes < 10 ? "0" : "" ) + minutes + ":" + ( seconds < 10 ? "0" : "" ) + seconds ;
2015-01-20 01:53:18 -05:00
}
2014-03-27 22:44:11 -04:00
}
2015-01-19 15:27:10 -05:00
} , { "./constants" : 3 } ] } , { } , [ 13 ] ) ; window . ffz = new FrankerFaceZ ( ) } ( window ) ) ;