2014-03-27 22:44:11 -04:00
( function wrapper ( window , injectNeeded ) {
'use strict' ;
// Script injection as necessary.
if ( injectNeeded ) {
var script = document . createElement ( 'script' ) ;
script . textContent = '(' + wrapper + ')(window, false)' ;
document . body . appendChild ( script ) ;
document . body . removeChild ( script ) ;
return ;
}
// -----------------
// Global Variables
// -----------------
2014-04-06 02:20:26 -04:00
var CSS = /\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)[^}]*\}/mg ,
2014-03-27 22:44:11 -04:00
IMGUR _KEY = 'e48d122e3437051' , CACHE _LENGTH = 10800000 ,
2014-04-06 02:20:26 -04:00
SERVER = '//commondatastorage.googleapis.com/frankerfacez/' ,
2014-03-27 22:44:11 -04:00
DEBUG = location . search . indexOf ( 'frankerfacez' ) !== - 1 ;
// -----------------
// The Constructor
// -----------------
var ffz = function ( ) {
this . alive = true ;
this . donors = { } ;
this . getting = { } ;
// Master Emoticon Storage
this . emoticons = [ ] ;
this . emotesets = { } ;
// Channel Storage
this . channels = { } ;
// Global Sets Storage
this . collections = { } ;
this . globals = { } ;
this . global _sets = [ ] ;
this . styles = { } ;
// Pending Styles -- For Super Early Initialization
this . pending _styles = [ ] ;
// Keep track of all logging too.
this . _log = [ ] ;
this . _log2 = [ ] ;
// Now, let's do this!
this . init ( 10 ) ;
} ;
ffz . prototype . last _set = 0 ;
ffz . prototype . last _emote = 0 ;
ffz . prototype . manger = null ;
2014-03-28 01:34:38 -04:00
ffz . prototype . has _bttv = false ;
2014-03-27 22:44:11 -04:00
ffz . commands = { } ;
// -----------------
// Logging
// -----------------
ffz . prototype . log = function ( msg ) {
this . _log . push ( msg ) ;
msg = "FFZ" + ( this . alive ? ": " : " (Dead): " ) + msg ;
console . log ( msg ) ;
// Don't echo to chat if we're not debugging.
if ( ! DEBUG ) return ;
var chan ;
for ( var name in this . channels ) {
if ( this . channels [ name ] && this . channels [ name ] . room ) {
chan = this . channels [ name ] ;
break ;
}
}
if ( chan )
chan . room . addTmiMessage ( msg ) ;
else
this . _log2 . push ( msg ) ;
} ;
// -----------------
// Initialization
// -----------------
ffz . prototype . init = function ( increment , delay ) {
// This function exists to ensure FFZ doesn't run until it can properly
// hook into the Twitch Ember application.
if ( ! this . alive ) return ;
var loaded = window . Ember != undefined &&
window . App != undefined &&
App . EmoticonsController != undefined &&
App . Room != undefined ;
if ( ! loaded ) {
// Only try loading for 60 seconds.
if ( delay >= 60000 )
this . log ( "Twitch API not detected in \"" + location . toString ( ) + "\". Aborting." ) ;
else
setTimeout ( this . init . bind ( this , increment , ( delay || 0 ) + increment ) ,
increment ) ;
return ;
}
this . setup ( ) ;
} ;
ffz . prototype . setup = function ( ) {
if ( ! this . alive ) return ;
// Hook into the Ember application.
this . log ( "Hooking Ember application." ) ;
this . modify _room ( ) ;
this . modify _viewers ( ) ;
this . modify _emotes ( ) ;
this . modify _lines ( ) ;
this . log ( "Loading data." ) ;
this . load _donors ( ) ;
this . load _emotes ( 'global' ) ;
if ( ! document . body ) {
// We need to listen for the DOM to load in case any style elements
// get created before we can add them.
this . listen _dom = this . listen _dom . bind ( this ) ;
document . addEventListener ( "DOMContentLoaded" , this . listen _dom , false ) ;
}
2014-03-28 01:34:38 -04:00
// Detect BetterTTV
this . find _bttv ( 10 ) ;
2014-03-27 22:44:11 -04:00
this . log ( "Initialization complete." ) ;
} ;
ffz . prototype . destroy = function ( ) {
if ( ! this . alive ) return ;
// TODO: Teardown stuff.
// Mark us as dead and remove our reference.
alive = false ;
if ( window . ffz === this )
window . ffz = undefined ;
// And, before the door hits us... delete the log.
delete this . _log ;
delete this . _log2 ;
}
// -----------------
// DOM Listening
// -----------------
ffz . prototype . listen _dom = function ( ) {
document . removeEventListener ( "DOMContentLoaded" , this . listen _dom , false ) ;
// Check for waiting styles.
while ( this . pending _styles . length )
document . body . appendChild ( this . pending _styles . pop ( ) ) ;
}
// -----------------
// Commands
// -----------------
2014-03-28 01:34:38 -04:00
ffz . prototype . _msg = function ( room , out ) {
if ( this . has _bttv )
return BetterTTV . chat . helpers . serverMessage ( out . replace ( /\n/g , "<br>" ) ) ;
2014-03-27 22:44:11 -04:00
out = out . split ( "\n" ) ;
for ( var i = 0 ; i < out . length ; i ++ )
room . addMessage ( { style : 'ffz admin' , from : 'FFZ' , message : out [ i ] } ) ;
}
ffz . prototype . run _command = function ( room , m ) {
var args = ( m . substr ( 5 ) || "list" ) . split ( ' ' ) ,
cmd = args . shift ( ) . toLowerCase ( ) ;
this . log ( "Got FFZ Command: " + cmd + " " + JSON . stringify ( args ) ) ;
var c = ffz . commands [ cmd ] , out ;
if ( c )
out = c . bind ( this ) ( room , args ) ;
else
out = "No such sub-command." ;
2014-03-28 01:34:38 -04:00
if ( out ) this . _msg ( room , out ) ;
2014-03-27 22:44:11 -04:00
}
ffz . commands [ 'help' ] = function ( room , args ) {
if ( args && args . length > 0 ) {
var c = ffz . commands [ args [ 0 ] . toLowerCase ( ) ] ;
if ( ! c )
return "No such sub-command: " + args [ 0 ] ;
else if ( c && c . help == undefined )
return "No help available for: " + args [ 0 ] ;
else
return c . help ;
}
var l = [ ] ;
for ( var c in ffz . commands )
ffz . commands . hasOwnProperty ( c ) ? l . push ( c ) : false ;
return "Available sub-commands are: " + l . join ( ", " ) ;
}
ffz . commands [ 'help' ] . help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command." ;
ffz . commands [ 'log' ] = function ( room , args ) {
var out = "FrankerFaceZ Session Log\n\n" + this . _log . join ( "\n" ) ;
out += "\n\n--------------------------------------------------------------------------------\n" +
"Internal State\n\n" ;
out += "Channels:\n" ;
for ( var id in this . channels ) {
if ( ! this . channels . hasOwnProperty ( id ) )
continue ;
var chan = this . channels [ id ] ;
if ( ! chan ) {
out += " " + id + " (Unloaded)\n" ;
continue ;
}
out += " " + id + ":\n" ;
out += " set_id: " + chan . set _id + "\n" ;
if ( ! chan . set ) {
out += " set (Unloaded)\n" ;
} else {
out += " set:\n" ;
for ( var i = 0 ; i < chan . set . length ; i ++ ) {
var e = chan . set [ i ] ;
out += " isEmoticon: " + e . isEmoticon + ", cls: " + JSON . stringify ( e . cls ) + ", regex: " + e . regex . toString ( ) + "\n" ;
}
out += "\n" ;
}
if ( ! chan . style ) {
out += " style (Unloaded)" ;
} else {
var s = chan . style . innerHTML . split ( "\n" ) ;
out += " style:\n" ;
for ( var i = 0 ; i < s . length ; i ++ )
out += " " + s [ i ] + "\n" ;
out += "\n" ;
}
}
out += "\nGlobal Sets:\n" ;
for ( var id in this . globals ) {
if ( ! this . globals . hasOwnProperty ( id ) )
continue ;
var set _id = this . globals [ id ] ;
if ( ! set _id ) {
out += " " + id + " (Unloaded)\n" ;
continue ;
}
out += " " + id + ":\n" ;
out += " set_id: " + set _id + "\n" ;
var set = this . emotesets [ set _id ] ;
if ( ! set ) {
out += " set (Unloaded)\n" ;
} else {
out += " set:\n" ;
for ( var i = 0 ; i < set . length ; i ++ ) {
var e = set [ i ] ;
out += " isEmoticon: " + e . isEmoticon + ", cls: " + JSON . stringify ( e . cls ) + ", regex: " + e . regex . toString ( ) + "\n" ;
}
out += "\n" ;
}
var style = this . styles [ id ] ;
if ( ! style ) {
out += " style (Unloaded)\n" ;
} else {
var s = style . innerHTML . split ( "\n" ) ;
out += " style:\n" ;
for ( var i = 0 ; i < s . length ; i ++ )
out += " " + s [ i ] + "\n" ;
out += "\n" ;
}
}
out += "\nEmotes:\n" ;
for ( var i = 0 ; i < this . emoticons . length ; i ++ ) {
var e = this . emoticons [ i ] ;
out += " " + e . text + " (" + e . image . id + ")\n" ;
out += " ffzset: " + e . ffzset + " (" + e . image . emoticon _set + ")\n" ;
out += " channel: " + e . channel + "\n" ;
out += " regex: " + e . regex . toString ( ) + "\n" ;
out += " height: " + e . image . height + ", width: " + e . image . width + "\n" ;
out += " url: " + e . image . url + "\n"
out += " html: " + e . image . html + "\n\n" ;
}
window . open ( "data:text/plain," + encodeURIComponent ( out ) , "_blank" ) ;
}
ffz . commands [ 'log' ] . help = "Usage: /ffz log\nOpen a window with FFZ's debugging output." ;
ffz . commands [ 'list' ] = function ( room , args ) {
2014-03-28 01:34:38 -04:00
var output = '' , filter , html = this . has _bttv ;
2014-03-27 22:44:11 -04:00
if ( args && args . length > 0 )
filter = args . join ( " " ) . toLowerCase ( ) ;
2014-03-28 01:34:38 -04:00
if ( html ) output += "<table style=\"width:100%\">" ;
2014-03-27 22:44:11 -04:00
for ( var name in this . collections ) {
if ( ! this . collections . hasOwnProperty ( name ) )
return ;
var include ;
if ( filter )
include = name . toLowerCase ( ) . indexOf ( filter ) !== - 1 ;
else
include = name !== "FFZ Global Emotes" ;
if ( ! include )
continue ;
2014-03-28 01:34:38 -04:00
if ( html )
output += "<thead><th colspan=\"2\">" + name + "</th></thead><tbody>" ;
else
output += name + "\n" ;
2014-03-27 22:44:11 -04:00
var em = this . collections [ name ] ;
for ( var e in em ) {
if ( em . hasOwnProperty ( e ) ) {
var emote = em [ e ] , t = emote . text ;
2014-03-28 01:34:38 -04:00
if ( html )
output += "<tr style=\"line-height:" + emote . image . height + "px\"><td>" + t + "</td><td>" + emote . image . html + "</td></tr>" ;
else {
t = t [ 0 ] + "\u200B" + t . substr ( 1 ) ;
output += " " + t + " = " + emote . text + "\n" ;
}
2014-03-27 22:44:11 -04:00
}
}
2014-03-28 01:34:38 -04:00
if ( html ) output += "</tbody>" ;
2014-03-27 22:44:11 -04:00
}
2014-03-28 01:34:38 -04:00
if ( html ) output += "</table>" ;
2014-03-27 22:44:11 -04:00
// Make sure we actually have output.
2014-03-28 01:34:38 -04:00
if ( output . indexOf ( html ? '<td>' : '\u200B' ) === - 1 )
2014-03-27 22:44:11 -04:00
return "There are no available FFZ channel emoticons. If this is in error, please try the /ffz reload command." ;
else
return "The following emotes are available:\n" + output ;
}
ffz . commands [ 'list' ] . help = "Usage: /ffz list [global]\nList available FFZ emoticons. Use the global parameter to list ALL FFZ emoticons, or filter for a specific set." ;
ffz . commands [ 'global' ] = function ( room , args ) {
return ffz . commands [ 'list' ] . bind ( this ) ( room , [ 'global' ] ) ; }
ffz . commands [ 'global' ] . help = "Usage: /ffz global\nShorthand for /ffz list global. List ALL FFZ emoticons, including FFZ global emoticons." ;
ffz . commands [ 'reload' ] = function ( room , args ) {
for ( var id in this . channels )
if ( this . channels . hasOwnProperty ( id ) && this . channels [ id ] )
this . load _emotes ( id , true ) ;
this . load _emotes ( 'global' ) ;
this . load _donors ( ) ;
return "Attempting to reload FFZ data from the server." ;
}
ffz . commands [ 'reload' ] . help = "Usage: /ffz reload\nAttempt to reload FFZ emoticons and donors." ;
ffz . commands [ 'inject' ] = function ( room , args ) {
if ( ! args || args . length !== 1 )
return "/ffz inject requires exactly 1 argument." ;
var album = args [ 0 ] . split ( '/' ) . pop ( ) . split ( '?' ) . shift ( ) . split ( '#' ) . shift ( ) ;
2014-03-28 01:34:38 -04:00
this . _msg ( room , "Attempting to load test emoticons from imgur album \"" + album + "\"..." ) ;
2014-03-27 22:44:11 -04:00
// Make sure there's no cache hits.
var res = "https://api.imgur.com/3/album/" + album ;
if ( window . localStorage )
localStorage . removeItem ( "ffz_" + res ) ;
this . get ( res , this . do _imgur . bind ( this , room , album ) , 1 ,
{ 'Accept' : 'application/json' , 'Authorization' : 'Client-ID ' + IMGUR _KEY } ,
5 ) ;
}
2014-03-28 01:34:38 -04:00
ffz . commands [ 'inject' ] . help = "Usage: /ffz inject [album-id]\nLoads emoticons from an imgur album for testing. album-id can simply be the album URL. Ex: /ffz inject http://imgur.com/a/v4aZr" ;
2014-03-27 22:44:11 -04:00
ffz . prototype . do _imgur = function ( room , album , data ) {
if ( data === undefined )
2014-03-28 01:34:38 -04:00
return this . _msg ( room , "An error occurred communicating with Imgur." ) ;
2014-03-27 22:44:11 -04:00
else if ( ! data )
2014-03-28 01:34:38 -04:00
return this . _msg ( room , "The named album does not exist or is private." ) ;
2014-03-27 22:44:11 -04:00
// Get our data structure.
data = JSON . parse ( data ) . data ;
var images = data . images , css = "" ;
for ( var i = 0 ; i < images . length ; i ++ ) {
var im = images [ i ] ,
name = im . title ? im . title : album + ( i + 1 ) ,
marg = im . height > 18 ? ( im . height - 18 ) / - 2 : 0 ,
desc = im . description ? im . description . trim ( ) . split ( /(?:\W*\n\W*)+/ ) : undefined ,
extra _css = '' ;
if ( desc ) {
for ( var q = 0 ; q < desc . length ; q ++ ) {
if ( desc [ q ] . substr ( 0 , 5 ) . toLowerCase ( ) === "css: " ) {
extra _css = desc [ q ] . substr ( 5 ) ;
break ;
}
}
}
css += ".imgur-" + album + "-" + ( i + 1 ) + ' {content: "' + name +
'"; background-image: url("' + im . link + '"); height: ' + im . height +
'px; width: ' + im . width + 'px; margin: ' + marg + 'px 0px; ' + extra _css + '}\n' ;
}
var count = this . process _css ( 'imgur-' + album , 'FFZ Global Emotes - Imgur Album: ' + album , css ) ;
2014-03-28 01:34:38 -04:00
this . _msg ( room , "Loaded " + count + " emoticons from Imgur." ) ;
this . _msg ( room , ffz . commands [ 'list' ] . bind ( this ) ( room , [ album ] ) ) ;
}
// -----------------
// BetterTTV Hooks
// -----------------
ffz . prototype . find _bttv = function ( increment , delay ) {
if ( ! this . alive ) return ;
if ( window . BTTVLOADED )
return this . setup _bttv ( ) ;
else if ( delay === undefined )
this . log ( "BetterTTV not yet loaded. Waiting..." ) ;
if ( delay >= 60000 )
this . log ( "BetterTTV not detected in \"" + location . toString ( ) + "\". Giving up." ) ;
else
setTimeout ( this . find _bttv . bind ( this , increment , ( delay || 0 ) + increment ) ,
increment ) ;
}
var donor _badge = { type : 'ffz-donor' , name : '' , description : 'FFZ Donor' } ;
ffz . prototype . setup _bttv = function ( ) {
this . log ( "BetterTTV was detected. Installing hook." ) ;
this . has _bttv = true ;
// Add badge handling to BetterTTV chat.
var privmsg = BetterTTV . chat . templates . privmsg , f = this ;
BetterTTV . chat . templates . privmsg = function ( highlight , action , server , isMod , data ) {
if ( f . check _donor ( data . sender ) ) {
2014-04-27 18:06:22 -04:00
var badge = _ . defaults ( { } , donor _badge ) ;
if ( BetterTTV . settings . get ( 'alphaTags' ) )
badge [ 'type' ] = badge [ 'type' ] + ' alpha' ;
2014-03-28 01:34:38 -04:00
var inserted = false ;
for ( var i = 0 ; i < data . badges . length ; i ++ ) {
var t = data . badges [ i ] . type ;
if ( t != 'turbo' && t != 'subscriber' )
continue ;
2014-04-27 18:06:22 -04:00
data . badges . insertAt ( i , badge ) ;
2014-03-28 01:34:38 -04:00
inserted = true ;
break ;
}
if ( ! inserted )
2014-04-27 18:06:22 -04:00
data . badges . push ( badge ) ;
2014-03-28 01:34:38 -04:00
}
return privmsg ( highlight , action , server , isMod , data ) ;
}
2014-03-27 22:44:11 -04:00
}
// -----------------
// Ember Hooks
// -----------------
2014-03-27 23:24:57 -04:00
ffz . prototype . add _badge = function ( sender , badges ) {
// Is the sender a donor?
if ( ! this . check _donor ( sender ) )
return ;
// Create the FFZ Donor badge.
var c = document . createElement ( 'span' ) ;
c . className = 'badge-container tooltip' ;
c . setAttribute ( 'title' , 'FFZ Donor' ) ;
var b = document . createElement ( 'div' ) ;
b . className = 'badge ffz-donor' ;
c . appendChild ( b ) ;
c . appendChild ( document . createTextNode ( ' ' ) ) ;
// Figure out where to place the badge.
var before = badges . find ( '.badge-container' ) . filter ( function ( i ) {
var t = this . title . toLowerCase ( ) ;
return t == "subscriber" || t == "turbo" ;
} ) . first ( ) ;
if ( before . length )
before . before ( c ) ;
else
badges . append ( c ) ;
}
2014-03-27 22:44:11 -04:00
ffz . prototype . modify _lines = function ( ) {
var f = this ;
App . LineView . reopen ( {
didInsertElement : function ( ) {
this . _super ( ) ;
2014-03-27 23:24:57 -04:00
f . add _badge ( this . get ( 'context.model.from' ) , this . $ ( '.badges' ) ) ;
2014-03-27 22:44:11 -04:00
}
} ) ;
}
2014-03-27 23:24:57 -04:00
ffz . prototype . _modify _room = function ( room ) {
2014-03-27 22:44:11 -04:00
var f = this ;
2014-03-27 23:24:57 -04:00
room . reopen ( {
2014-03-27 22:44:11 -04:00
init : function ( ) {
this . _super ( ) ;
if ( f . alive )
f . add _channel ( this . id , this ) ;
} ,
willDestroy : function ( ) {
this . _super ( ) ;
if ( f . alive )
f . remove _channel ( this . id ) ;
} ,
send : function ( e ) {
if ( f . alive && ( e . substr ( 0 , 5 ) == '/ffz ' || e == '/ffz' ) ) {
// Clear the input box.
this . set ( "messageToSend" , "" ) ;
f . run _command ( this , e ) ;
} else
return this . _super ( e ) ;
}
} ) ;
2014-03-27 23:24:57 -04:00
}
ffz . prototype . modify _room = function ( ) {
this . _modify _room ( App . Room ) ;
2014-03-27 22:44:11 -04:00
var inst = App . Room . instances ;
for ( var n in inst ) {
if ( ! inst . hasOwnProperty ( n ) ) continue ;
var i = inst [ n ] ;
2014-03-27 23:24:57 -04:00
if ( this . alive )
this . add _channel ( i . id , i ) ;
2014-03-27 22:44:11 -04:00
2014-03-27 23:24:57 -04:00
if ( i . tmiRoom && this . alive )
this . alter _tmi ( i . id , i . tmiRoom ) ;
else if ( i . viewers )
this . _modify _viewers ( i . viewers ) ;
this . _modify _room ( i ) ;
2014-03-27 22:44:11 -04:00
}
} ;
2014-03-27 23:24:57 -04:00
ffz . prototype . _modify _viewers = function ( vwrs ) {
2014-03-27 22:44:11 -04:00
var f = this ;
2014-03-27 23:24:57 -04:00
vwrs . reopen ( {
2014-03-27 22:44:11 -04:00
tmiRoom : Ember . computed ( function ( key , val ) {
if ( arguments . length > 1 ) {
this . tmiRoom = val ;
if ( f . alive )
f . alter _tmi ( this . id , val ) ;
}
return undefined ;
} )
} ) ;
2014-03-27 23:24:57 -04:00
}
ffz . prototype . modify _viewers = function ( ) {
this . _modify _viewers ( App . Room . Viewers ) ;
2014-03-27 22:44:11 -04:00
} ;
2014-03-27 23:24:57 -04:00
ffz . prototype . _modify _emotes = function ( ec ) {
2014-03-27 22:44:11 -04:00
var f = this ;
2014-03-27 23:24:57 -04:00
ec . reopen ( {
2014-04-06 02:20:26 -04:00
_emoticons : ec . emoticons || [ ] ,
2014-03-27 22:44:11 -04:00
init : function ( ) {
this . _super ( ) ;
2014-03-27 23:24:57 -04:00
if ( f . alive )
f . get _manager ( this ) ;
2014-03-27 22:44:11 -04:00
} ,
emoticons : Ember . computed ( function ( key , val ) {
if ( arguments . length > 1 ) {
this . _emoticons = val ;
f . log ( "Twitch standard emoticons loaded." ) ;
}
return f . alive ? _ . union ( this . _emoticons , f . emoticons ) : this . _emoticons ;
} )
} ) ;
2014-03-27 23:24:57 -04:00
}
ffz . prototype . modify _emotes = function ( ) {
this . _modify _emotes ( App . EmoticonsController ) ;
2014-03-27 22:44:11 -04:00
var ec = App . _ _container _ _ . lookup ( "controller:emoticons" ) ;
if ( ! ec ) return ;
2014-03-27 23:24:57 -04:00
this . _modify _emotes ( ec ) ;
this . get _manager ( ec ) ;
2014-03-27 22:44:11 -04:00
} ;
2014-03-27 23:24:57 -04:00
ffz . prototype . get _manager = function ( manager ) {
this . manager = manager ;
for ( var key in this . emotesets ) {
if ( this . emotesets . hasOwnProperty ( key ) )
manager . emoticonSets [ key ] = this . emotesets [ key ] ;
}
}
2014-03-27 22:44:11 -04:00
// -----------------
// Channel Management
// -----------------
ffz . prototype . add _channel = function ( id , room ) {
if ( ! this . alive ) return ;
this . log ( "Registered channel: " + id ) ;
var chan = this . channels [ id ] = { id : id , room : room , tmi : null , style : null } ;
// Do we have log messages?
if ( this . _log2 . length > 0 ) {
2014-03-28 01:34:38 -04:00
var func = this . has _bttv ? BetterTTV . chat . helpers . serverMessage : room . addTmiMessage ;
2014-03-27 22:44:11 -04:00
while ( this . _log2 . length )
2014-03-28 01:34:38 -04:00
func ( this . _log2 . shift ( ) ) ;
2014-03-27 22:44:11 -04:00
}
// Load the emotes for this channel.
this . load _emotes ( id ) ;
}
ffz . prototype . remove _channel = function ( id ) {
var chan = this . channels [ id ] ;
if ( ! chan ) return ;
this . log ( "Removing channel: " + id ) ;
// Unload the associated emotes.
this . unload _emotes ( id ) ;
// If we have a tmiRoom for this channel, restore its getEmotes function.
if ( chan . tmi )
delete chan . tmi . getEmotes ;
// Delete this channel.
this . channels [ id ] = false ;
}
ffz . prototype . alter _tmi = function ( id , tmi ) {
var chan = this . channels [ id ] , f = this ;
if ( ! chan || ! this . alive ) return ;
// Store the TMI instance.
if ( chan . tmi ) return ;
chan . tmi = tmi ;
var tp = tmi . _ _proto _ _ . getEmotes . bind ( tmi ) ;
tmi . getEmotes = function ( name ) {
return _ . union ( [ chan . set _id ] , f . global _sets , tp ( name ) || [ ] ) ;
}
}
// -----------------
// Emote Handling
// -----------------
ffz . prototype . load _emotes = function ( group , refresh ) {
2014-04-06 02:20:26 -04:00
// TEMPORARY GROUP CHAT
var m = /^_(.+)_\d+$/ . exec ( group ) , name = group ;
if ( m != null )
name = m [ 1 ] ;
this . get ( SERVER + name + ".css" ,
2014-03-27 22:44:11 -04:00
this . process _css . bind ( this , group , undefined ) , refresh ? 1 : CACHE _LENGTH ) ;
}
ffz . prototype . process _css = function ( group , channel , data ) {
if ( ! this . alive ) return 0 ;
if ( data === undefined ) return 0 ;
// Before we go anywhere, let's start clean.
this . unload _emotes ( group ) ;
// If data is null, we've got no emotes.
if ( data == null )
return 0 ;
// Let's look up this group to see where it goes! Is it a channel?
var chan = this . channels [ group ] ;
if ( chan === false )
// It's for an unloaded channel. Stop here.
return ;
// Get our new stuff.
var set _id = -- this . last _set , set = [ ] , channel ,
style = document . createElement ( 'style' ) ;
// Let's store our things right now.
if ( chan ) {
chan . set _id = set _id ;
chan . set = set ;
chan . style = style ;
channel = "FFZ Channel Emotes: " + group ;
} else {
this . globals [ group ] = set _id ;
this . global _sets . push ( set _id ) ;
this . styles [ group ] = style ;
if ( ! channel )
channel = "FFZ Global Emotes" + ( group != "global" ? ": " + group : "" ) ;
}
// Register this set with the manager.
this . emotesets [ set _id ] = set ;
if ( this . manager )
this . manager . emoticonSets [ set _id ] = set ;
// Update the style.
style . type = 'text/css' ;
style . innerHTML = data ;
if ( document . body )
document . body . appendChild ( style ) ;
else
this . pending _styles . push ( style ) ;
// Parse out the usable emoticons.
var count = 0 , f = this ;
// Store our emotes in an extra place.
var col = this . collections [ channel ] = [ ] ;
data . replace ( CSS , function ( match , klass , name , path , height , width ) {
height = parseInt ( height ) ; width = parseInt ( width ) ;
var image _data = {
emoticon _set : set _id , height : height , width : width , url : path ,
html : '<span class="' + klass + ' emoticon" title="' + name + '"></span>' ,
id : -- f . last _emote } , regex ;
if ( name [ name . length - 1 ] === '!' )
regex = new RegExp ( '\\b' + name + '(?=\\W|$)' , 'g' ) ;
else
regex = new RegExp ( '\\b' + name + '\\b' , 'g' ) ;
var emote = {
image : image _data , images : [ image _data ] , text : name ,
channel : channel , hidden : false , regex : regex , ffzset : group } ;
col . push ( emote ) ;
f . emoticons . push ( emote ) ;
set . push ( { isEmoticon : ! 0 , cls : klass , regex : regex } ) ;
count ++ ;
} ) ;
this . log ( "Loaded " + count + " emotes from collection: " + group ) ;
// Notify the manager that we've added emotes.
2014-03-28 01:34:38 -04:00
// Don't notify the manager for now because of BTTV.
//if ( this.manager && ! this.has_bttv )
// this.manager.notifyPropertyChange('emoticons');
2014-03-27 22:44:11 -04:00
return count ;
}
ffz . prototype . unload _emotes = function ( group ) {
if ( ! this . alive ) return ;
// Is it a channel?
var chan = this . channels [ group ] , set , set _id , style , channel ;
if ( chan === false )
return ;
else if ( chan ) {
// It's a channel.
set = chan . set ;
set _id = chan . set _id ;
style = chan . style ;
channel = "FFZ Channel Emotes: " + group ;
// Clear it out.
delete chan . set ;
delete chan . set _id ;
delete chan . style ;
} else {
// It must be global.
set _id = this . globals [ group ] ;
set = this . emotesets [ set _id ] ;
style = this . styles [ group ] ;
channel = "FFZ Global Emotes" + ( group != "global" ? ": " + group : "" ) ;
// Clear out the basics.
delete this . globals [ group ] ;
delete this . styles [ group ] ;
var ind = this . global _sets . indexOf ( set _id ) ;
if ( ind !== - 1 )
this . global _sets . splice ( ind , 1 ) ;
}
// Do we have a collection?
if ( this . collections [ channel ] )
delete this . collections [ channel ] ;
// Do we have a style?
if ( style )
// Remove it from its parent.
try { style . parentNode . removeChild ( style ) ; } catch ( err ) { }
// Remove the emoteset from circulation.
delete this . emotesets [ set _id ] ;
if ( this . manager )
delete this . manager . emoticonSets [ set _id ] ;
// Remove every emote from this group.
var filt = function ( e ) { return e . ffzgroup !== group ; }
this . emoticons = this . emoticons . filter ( filt ) ;
// Update the emoticons with the manager.
2014-03-28 01:34:38 -04:00
// Don't notify the manager for now for BTTV.
//if ( this.manager && ! this.has_bttv )
// this.manager.notifyPropertyChange('emoticons');
2014-03-27 22:44:11 -04:00
}
// -----------------
// Donor Processing
// -----------------
ffz . prototype . check _donor = function ( username ) { return this . donors [ username ] || false ; }
ffz . prototype . load _donors = function ( refresh ) {
2014-04-06 02:20:26 -04:00
this . get ( SERVER + "donors.txt" ,
2014-03-27 22:44:11 -04:00
this . process _donors . bind ( this ) , refresh ? 1 : CACHE _LENGTH ) ;
}
ffz . prototype . process _donors = function ( text ) {
if ( ! this . alive ) return ;
this . donors = { } ;
var count = 0 ;
if ( text != null ) {
var l = text . trim ( ) . split ( /\W+/ ) ;
for ( var i = 0 ; i < l . length ; i ++ )
this . donors [ l [ i ] ] = true ;
count += l . length ;
}
this . log ( "Loaded " + count + " donors." ) ;
}
// -----------------
// Networking
// -----------------
ffz . prototype . get = function ( resource , callback , expires , headers , max _attempts ) {
if ( ! this . alive ) return ;
if ( this . getting [ resource ] ) {
this . log ( "Already getting resource: " + resource ) ;
return ;
}
this . getting [ resource ] = true ;
max _attempts = max _attempts || 10 ;
var age = 0 , now = new Date ( ) . getTime ( ) ;
// First, immediately try using the resource from cache.
if ( window . localStorage ) {
var res = localStorage . getItem ( "ffz_" + resource ) ;
if ( res != null ) {
this . log ( "Found resource in localStorage: " + resource ) ;
try {
callback ( JSON . parse ( res ) ) ;
} catch ( err ) { this . log ( "Error in callback: " + err ) ; }
// Also, get the age to see if we need to fetch it again.
age = parseInt ( localStorage . getItem ( "ffz_age_" + resource ) || 0 ) ;
}
}
if ( DEBUG || ! age || ( expires !== undefined && expires !== null && ( now - age ) > expires ) ) {
// Try getting it again.
this . log ( "Resource expired. Fetching: " + resource ) ;
this . do _get ( resource , callback , 0 , headers , max _attempts ) ;
} else
this . getting [ resource ] = false ;
}
ffz . prototype . do _get = function ( resource , callback , attempts , headers , max _attempts ) {
if ( ! this . alive ) {
this . getting [ resource ] = false ;
return ;
}
var http = new XMLHttpRequest ( ) ;
http . open ( "GET" , resource ) ;
if ( headers ) {
for ( var hdr in headers ) {
if ( headers . hasOwnProperty ( hdr ) )
http . setRequestHeader ( hdr , headers [ hdr ] ) ;
}
}
var f = this ;
function try _again ( ) {
var attempt = ( attempts || 0 ) + 1 , delay = 1000 ;
if ( ! max _attempts || attempt <= max _attempts ) {
setTimeout ( f . do _get . bind ( f , resource , callback , attempt , headers , max _attempts ) , delay ) ;
return true ;
}
}
http . addEventListener ( "error" , function ( e ) {
if ( try _again ( ) )
return ;
f . getting [ resource ] = false ;
try {
callback ( undefined ) ;
} catch ( err ) { f . log ( "Error in callback: " + err ) ; }
} , false ) ;
http . addEventListener ( "load" , function ( e ) {
var result ;
if ( http . status === 200 ) {
// Success!
result = http . responseText ;
// Let's see if it was modified?
if ( window . localStorage ) {
var last = localStorage . getItem ( "ffz_last_" + resource ) ,
nl = http . getResponseHeader ( "Last-Modified" ) ;
if ( last && last == nl ) {
// No change! Let's go.
f . log ( "Resource not modified: " + resource ) ;
localStorage . setItem ( "ffz_age_" + resource , new Date ( ) . getTime ( ) ) ;
f . getting [ resource ] = false ;
return ;
} else
// Save it!
localStorage . setItem ( "ffz_last_" + resource , nl ) ;
}
} else if ( http . status === 304 ) {
// Not Modified!
f . log ( "Resource not modified: " + resource ) ;
if ( window . localStorage )
localStorage . setItem ( "ffz_age_" + resource , new Date ( ) . getTime ( ) ) ;
f . getting [ resource ] = false ;
return ;
} else if ( http . status === 404 ) {
// Not Found!
result = null ;
} else {
// Try Again
if ( try _again ( ) )
return ;
result = undefined ;
}
// Store it in localStorage if we can.
if ( window . localStorage && result !== undefined ) {
localStorage . setItem ( "ffz_" + resource , JSON . stringify ( result ) ) ;
localStorage . setItem ( "ffz_age_" + resource , new Date ( ) . getTime ( ) ) ;
}
// And send it along.
f . getting [ resource ] = false ;
try {
callback ( result ) ;
} catch ( err ) { f . log ( "Error in callback: " + err ) ; }
} , false ) ;
http . send ( ) ;
}
// Finally, initialize FFZ.
window . ffz = new ffz ( ) ;
} ) ( this . unsafeWindow || window , window . chrome ? true : false ) ;